diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/09-archive-freshness-gap.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/09-archive-freshness-gap.md new file mode 100644 index 000000000..df85e4b2a --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/09-archive-freshness-gap.md @@ -0,0 +1,18 @@ +Title: "[analysis 09] Raw-to-query freshness and false-empty audit" + +Job ID: `analysis-09` +Result ZIP: `analysis-09-archive-freshness-gap-r01.zip` + +Audit the exact source-to-query path for newly browser-captured sessions. The +live incident is concrete: a same-day ChatGPT capture exists in the raw capture +directory while the archive query for recent ChatGPT sessions returns zero. +Trace capture discovery, cursor/acquisition, raw storage, parse/materialize, +index/FTS convergence, and public query diagnostics. Classify the observed +state without mutating the archive. + +Adjudicate `polylogue-1xc.13`, `lkrc`, `yla8`, and `z9gh.3`: name the exact +source identity/receipt a model should receive, what an empty result can and +cannot mean, and whether current named-source surfaces actually expose that +truth. Produce an implementation ordering, anti-vacuity fixtures, and any +Bead corrections needed; do not propose archive-wide scans or manual repair as +normal operation. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/10-p0-p1-execution-shape.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/10-p0-p1-execution-shape.md new file mode 100644 index 000000000..f46d031d4 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/10-p0-p1-execution-shape.md @@ -0,0 +1,15 @@ +Title: "[analysis 10] P0/P1 program shape and dependency adjudication" + +Job ID: `analysis-10` +Result ZIP: `analysis-10-p0-p1-execution-shape-r01.zip` + +Audit the live P0/P1 portfolio as a dependency graph rather than a flat list. +Identify epics versus executable leaves, duplicated parent/child priority, +blocking edges that are genuine versus historical residue, and P1s that are +effectively required for P0 closure. Preserve project ambition: the goal is a +more tractable execution program, not demotion by convenience. + +Use current Beads notes, merged history, and source anchors. Deliver a +decision-ready active frontier, safe parallel partitions, shared-file hotspots, +promotion/demotion recommendations with evidence, and a clear account of which +items must not be launched until a prerequisite receipt exists. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/11-testdiet-foundation-certification.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/11-testdiet-foundation-certification.md new file mode 100644 index 000000000..596771d11 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/11-testdiet-foundation-certification.md @@ -0,0 +1,16 @@ +Title: "[analysis 11] Test Diet foundation completion audit" + +Job ID: `analysis-11` +Result ZIP: `analysis-11-testdiet-foundation-certification-r01.zip` + +Independently audit whether the local foundation required before the 16 Test +Diet worker fan-out is actually complete. Compare the foundation mission, +current master, PR #2934 workload-profile changes, `polylogue-b054.1.1` notes, +campaign dispatch gate, and Test Diet architecture corpus. Do not equate a +merged workload-profile slice with the whole foundation. + +Return a precise AC matrix: landed production symbols and verification, missing +fail-closed seeded-artifact publication/validation/clone/fact/receipt behavior, +the current dispatchability decision, and the smallest coherent local follow-up +scope. Treat an absent reconciliation/foundation receipt as a hard fact, not +something workers may self-certify. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/12-p2-frontier-decomposition.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/12-p2-frontier-decomposition.md new file mode 100644 index 000000000..56d3f8ce5 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/briefs/12-p2-frontier-decomposition.md @@ -0,0 +1,14 @@ +Title: "[analysis 12] P2 leverage and promotion audit" + +Job ID: `analysis-12` +Result ZIP: `analysis-12-p2-frontier-decomposition-r01.zip` + +Survey the full P2 portfolio for leverage: locate clusters that are symptoms of +one missing abstraction, P2s whose prerequisites are now merged, and P2s whose +absence would undermine current P0/P1 programs. Characterize each proposed +promotion as a concrete causal argument, not a title/label preference. + +Use Beads, source, current merged work, and existing program contracts. Deliver +a grouped inventory, a recommended next active tranche, likely worktree/agent +partitions, shared hotspots, and exact Bead edits or new dependencies warranted +by evidence. Do not lower ambition by silently retiring hard work. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/ACTIVE-CENSUS.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/ACTIVE-CENSUS.csv new file mode 100644 index 000000000..5eedbd11b --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/ACTIVE-CENSUS.csv @@ -0,0 +1,556 @@ +id,priority,snapshot_status,effective_status,issue_type,title,assignee,source_areas,parent,authority_class,authority_claim,acceptance_semantics,hard_prerequisites,soft_relations,supersedes,explicit_paths,source_presence,stale_description_signal,write_hotspot_cluster,audit_disposition,updated_at,effective_updated_at,labels +polylogue-866e,0,open,open,bug,Make lineage writes order-independent and branch-point safe,,lineage;storage;test,polylogue-4ts,regression/repair-slice,,bounded-resource;parity/completeness;determinism/idempotency;restart/crash;mutation/falsification;schema/contract;regression-fixture,,,,polylogue/storage/sqlite/archive_tiers/write.py;polylogue/storage/sqlite/queries/session_links.py;polylogue/storage/sqlite/queries/message_query_reads.py;tests/property/test_write_path_state_machine.py,both=4,records-landed-work+records-residual-work,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T13:04:56Z,2026-07-16T13:04:56Z,area:lineage;area:storage;area:test;horizon:frontier +polylogue-hjpx,0,in_progress,in_progress,bug,Make RawAuthorityReconciler execute accepted replay plans to fixed point,Sinity,sources;storage;test,polylogue-lkrc,canonical-authority-owner,2026-07-17 static follow-up from yla8 read-only preflight: current live execution is blocked by one non-stream-safe authority component over the 1 GiB bounded replay envelope.,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-yla8,,polylogue/storage/repair.py;polylogue/sources/revision_backfill.py;polylogue/daemon/cli.py;tests/unit/sources/test_revision_backfill.py,both=4,records-landed-work+records-residual-work,raw-authority,retain fixed-point executor; add prerequisite hjpx.2,2026-07-17T01:54:28Z,2026-07-17T01:54:28Z,area:sources;area:storage;area:test;delivery:A-trust-floor;horizon:frontier +polylogue-lkrc,0,in_progress,in_progress,bug,Converge raw evidence authority through one proof-driven reconciler,Sinity,browser;sources;storage,polylogue-1xc,canonical-authority-owner,"Polylogue has accumulated separate repair actuators and incident Beads for origin-mismatched browser raws, competing canonical heads, duplicate raw identities, replaced snapshots requiring reacquisition, quarantined accepted raws, and superseded revisions.",live-evidence;receipt/provenance;bounded-resource;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,polylogue-yla8,polylogue-1xc.13;polylogue-2qx;polylogue-b5l.1;polylogue-yla8.10,,polylogue/storage/repair.py;tests/unit/sources/test_live_batch_support.py,both=2,records-landed-work+records-residual-work,raw-authority,retain final authority/reconciler closure,2026-07-17T01:48:12Z,2026-07-17T01:48:12Z,area:browser;area:sources;area:storage;delivery:A-trust-floor;horizon:frontier +polylogue-ptx,0,in_progress,in_progress,feature,Expose provider-neutral browser chat action conduit,Sinity,ingest;web,polylogue-bby,proof/verification-slice,"One versioned BrowserActionIntent contract can create a new Chat conversation or reply to an exact existing conversation with text plus multiple hash-pinned attachments; successful receipts bind provider conversation URL/id, submitted user-turn identity, selected surface/model/effort/project, provi…",live-evidence;receipt/provenance;determinism/idempotency;security/authorization;schema/contract;operator-gate;regression-fixture,polylogue-3v1.1;polylogue-83u.3;polylogue-83u.4;polylogue-kwsb.1,,,,no-explicit-paths,records-landed-work+records-residual-work,extension-actions,retain provider-neutral transport/receipt authority,2026-07-16T07:58:36Z,2026-07-16T07:58:36Z,area:ingest;area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-t8t,0,open,open,task,Declare continuity replay scenarios and independent known-answer oracles,,context;legibility;mcp;query,polylogue-z9gh,canonical-authority-owner,"Add polylogue/product/continuity_scenarios.py as the canonical declaration registry, using the existing polylogue.scenarios ScenarioSpec/ScenarioMetadata protocols and referencing, rather than duplicating, product/workflows.py query-action recipes.",live-evidence;receipt/provenance;cancellation/cleanup;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-s7ae,,polylogue/product/continuity_scenarios.py;devtools/continuity_replay.py;tests/data/continuity;tests/unit/product/test_continuity_scenarios.py;tests/integration/test_continuity_replay.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/070_polylogue_t8t.md,origin-only=1;missing/planned=5,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T13:04:58Z,2026-07-16T13:04:58Z,area:context;area:legibility;area:mcp;area:query;delivery:C-read-evidence-contract;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;lane:read-contracts;spine;wave:2 +polylogue-yla8,0,in_progress,in_progress,bug,Run the authority-safe raw replay closure gate,Sinity,daemon;storage;test,polylogue-1xc,canonical-authority-owner,"Chunk 1 rejected two full replays while preserving the accepted index: (1) a 25,898,236-byte ChatGPT browser capture for session 69d5383e-69d0-8327-a899-94a89ff35ea4 hit ""conflicting accepted head""; the existing semantic head comes from a 15,890,659-byte account-export member and the browser captur…",live-evidence;receipt/provenance;bounded-resource;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-1xc.13;polylogue-b5l.2;polylogue-n2wy,,,no-explicit-paths,records-landed-work+records-residual-work,raw-authority-live-gate,retain separate operator-authorized live gate; add prerequisite hjpx,2026-07-17T02:02:05Z,2026-07-17T02:02:05Z,area:daemon;area:storage;area:test;delivery:A-trust-floor;horizon:frontier;lane:operational-resilience;spine +polylogue-yyvg.6,0,in_progress,in_progress,feature,Track and automate external-agent result incorporation,Sinity,capture;coordination;web,polylogue-yyvg,canonical-authority-owner,"The orchestrator owns MissionId, RunId, IterationId, DeliverableId, PackageRevisionId, readable prompt/attachment/result filenames, retry/alternative/supersedes lineage, prompt profiles, queue policy, cadence experiments, and integration receipts.",receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;mutation/falsification;schema/contract;regression-fixture,,,,scripts/local,missing/planned=1,records-landed-work,external-campaign,retain external campaign orchestration; separate branch/ledger,2026-07-16T14:04:17Z,2026-07-16T14:04:17Z,area:capture;area:coordination;area:web;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-yyvg.7,0,in_progress,in_progress,feature,Make extension UX automatic and exception-driven,Sinity,capture;web,polylogue-yyvg,canonical-authority-owner,"F4/ys30/yyvg.4 owns capture dot and Save action in provider-native per-message action rows, resolved by one ProviderAdapter identity contract; no ordinal/text-only durable authority.",live-evidence;restart/crash;security/authorization;schema/contract;operator-gate,,,,docs/design/browser-capture-redesign/mockup.dc.html,both=1,records-landed-work+records-residual-work,extension-ux,retain UX policy; soft-relate to 3v1,2026-07-16T07:58:38Z,2026-07-16T07:58:38Z,area:capture;area:web;delivery:L-external-legibility;horizon:frontier;horizon:mid;lane:docs-demos-launch +polylogue-z9gh,0,open,open,epic,Restore mandate-critical archive queryability under real agent workloads,,mandate;mcp;perf;query,,program/invariant-container,"(D) Agent query declaration: polylogue-z9gh.3 generates executable discovery, structured plans, DSL teaching, compact projections, and errors from one registry.",receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;schema/contract;regression-fixture,,polylogue-1vpm;polylogue-1xc;polylogue-20d;polylogue-2qx;polylogue-rsad;polylogue-t46;polylogue-t46.3;polylogue-t46.8;polylogue-t8t,,,no-explicit-paths,records-residual-work,mandate,retain program container; do not execute as a leaf,2026-07-14T23:13:12Z,2026-07-14T23:13:12Z,area:mandate;area:mcp;area:perf;area:query;horizon:frontier +polylogue-z9gh.1,0,open,in_progress,bug,Make archive queries interruptible and resource-bounded,Sinity,mcp;perf;query,polylogue-z9gh.9,regression/repair-slice,"Cancellation, deadlines, off-event-loop execution, and admission control belong to the shared query transaction rather than MCP-specific wrappers.",live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;mutation/falsification,,polylogue-1xc.14;polylogue-20d.14;polylogue-oxz,,polylogue/archive/query/execution_control.py,both=1,export regressed after later claim; restore in_progress and rewrite to residual work,query-execution,restore in_progress; retain execution core/fairness; move surface residuals to z9gh.9.1; depend on 4s3c,2026-07-16T12:56:52Z,2026-07-17T00:25:42Z,area:mcp;area:perf;area:query;horizon:frontier;incident:memory +polylogue-z9gh.2,0,open,open,bug,Eliminate archive-wide materialization in action and delegation queries,,delegation;perf;query,polylogue-z9gh.9,consolidation-owner,Broad aggregate queries remain exact and bounded by the shared query transaction; per-session materialization does not introduce a hidden row cap or N-plus-one hydration.,live-evidence;bounded-resource;cancellation/cleanup;determinism/idempotency;restart/crash;mutation/falsification;schema/contract,,polylogue-20d.7;polylogue-j2zz,polylogue-20d.10,polylogue/storage/sqlite/archive_tiers/index.py,both=1,records-residual-work,delegation,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:45:30Z,2026-07-15T20:45:30Z,area:delegation;area:perf;area:query;horizon:frontier;incident:memory +polylogue-z9gh.3,0,open,open,feature,Generate agent query discovery from executable query declarations,,mcp;orchestration;query;search,polylogue-z9gh,canonical-authority-owner,"One query declaration registry generates MCP schemas/descriptions, a searchable capability/coverage catalog resource, typed structured-plan input, DSL completions/help, OpenAPI/docs, compact projections, valid-value errors, and curated intent recipes.",live-evidence;bounded-resource;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate,,polylogue-o21;polylogue-o21.1;polylogue-t46.8;polylogue-z9gh.9.1,,polylogue/mcp/server_prompts.py,both=1,records-landed-work,query-declarations,retain query-semantics owner; hard-block on o21.1 kernel,2026-07-16T12:56:53Z,2026-07-16T12:56:53Z,area:mcp;area:orchestration;area:query;area:search;horizon:frontier +polylogue-z9gh.7,0,open,open,task,Prove mandate recovery through real agent continuity replays,,mandate;mcp;verification,polylogue-z9gh,proof/verification-slice,Superseded incident Beads remain fixture/evidence inputs but are not dependencies.,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-1vpm.6.2;polylogue-2qx.2;polylogue-t8t;polylogue-z9gh.3;polylogue-z9gh.9.1,polylogue-1vpm.6;polylogue-2qx,,,no-explicit-paths,records-landed-work+records-residual-work,mandate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:44:19Z,2026-07-15T18:44:19Z,area:mandate;area:mcp;area:verification;horizon:frontier +polylogue-z9gh.9,0,open,open,epic,"Make every archive read a bounded, resumable query transaction",,mcp;perf;protocol;query,polylogue-z9gh,program/invariant-container,"The oversized-response cliff, lost continuation arguments, blocking SQLite event loop, uncancellable statements, global-view materialization, divergent surface totals, and hidden query refs are manifestations of one missing abstraction: Polylogue has query functions but no shared query transaction.",live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;schema/contract;regression-fixture,,polylogue-20d.14;polylogue-4p1;polylogue-rxdo.3,,,no-explicit-paths,records-landed-work,mcp,retain program container; do not execute as a leaf,2026-07-14T22:54:18Z,2026-07-14T22:54:18Z,area:mcp;area:perf;area:protocol;area:query;horizon:frontier +polylogue-z9gh.9.1,0,open,open,task,Land the shared query transaction across every read surface,,mcp;protocol;query,polylogue-z9gh.9,canonical-authority-owner,Land the shared query transaction across every read surface,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;restart/crash;mutation/falsification;schema/contract,polylogue-z9gh.1;polylogue-z9gh.2,polylogue-7q16;polylogue-9l5.6;polylogue-rxdo.3;polylogue-z9gh.3,polylogue-20d.5,polylogue/archive/query/transaction.py,missing/planned=1,records-landed-work+records-residual-work,query-transaction,retain transaction/delivery owner for remaining surface migration/disconnect/resume,2026-07-15T20:45:32Z,2026-07-15T20:45:32Z,area:mcp;area:protocol;area:query;horizon:frontier +polylogue-06zm,1,open,open,epic,Make browser recovery jobs durable across client identity loss,,capture,,program/invariant-container,"These are one missing abstraction: a receiver-authoritative durable job registry with stable job identity, leases, checkpoints, incident history, adoption, and retention independent of any browser profile.",receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,polylogue/browser_capture/{models;tests/unit/browser_capture/test_backfill_checkpoint.py;tests/unit/browser_capture/,both=1;symbolic=2,records-landed-work+records-residual-work,capture,retain program container; do not execute as a leaf,2026-07-15T18:07:40Z,2026-07-15T18:07:40Z,area:capture;delivery:B-storage-rebuild-bytes;horizon:frontier +polylogue-09rn,1,open,open,bug,test_periodic_embedding_backlog_waits_for_catch_up_complete times out (>300s) on master,,daemon,polylogue-avmq,regression/repair-slice,,bounded-resource;determinism/idempotency;mutation/falsification;regression-fixture,,polylogue-88jp;polylogue-b054.1.1,,tests/unit/daemon/test_embedding_convergence_progress.py;docs/retro/2026-05-24-1498-cascade.md,both=2,records-landed-work,daemon-supervision,retain regression slice in avmq branch,2026-07-15T20:09:38Z,2026-07-15T20:09:38Z,area:daemon;horizon:frontier +polylogue-1vpm,1,open,open,epic,"Work-evidence graph: runs, delegations, episodes, claims, artifacts, effects",,substrate,,program/invariant-container,"Define a small typed work graph rather than provider tables or one universal row: node identities for task/call, attempt/run, session segment, actor/context, artifact, commit, PR, Beads issue, and verification receipt; evidence-backed edge families for spawned/resumed/retried, represented_by, produ…",receipt/provenance;schema/contract;regression-fixture,,polylogue-9l5;polylogue-9l5.1;polylogue-9l5.13;polylogue-9l5.2;polylogue-9l5.6;polylogue-rxdo,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/178_polylogue_1vpm.md,origin-only=1,records-residual-work,substrate,retain program container; do not execute as a leaf,2026-07-14T23:07:58Z,2026-07-14T23:07:58Z,area:substrate;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-1vpm.6,1,open,open,epic,Land the provider-neutral work-evidence graph and reconciliation,,evidence;orchestration;substrate,polylogue-1vpm,program/invariant-container,"Workflow is a proving adapter, not a universal hierarchy; claim is not effect; effect is not evaluated satisfaction.",live-evidence;receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-z9gh.7,,,no-explicit-paths,records-landed-work+records-residual-work,evidence,retain program container; do not execute as a leaf,2026-07-15T19:54:00Z,2026-07-15T19:54:00Z,area:evidence;area:orchestration;area:substrate;horizon:frontier +polylogue-1vpm.6.1,1,open,open,feature,Land the provider-neutral work topology and claim graph,,evidence;orchestration;substrate,polylogue-1vpm.6,domain-implementation-slice,"Define typed refs and edge families for invoked, resumed, retried, represented_by, produced/consumed/mentioned, claimed, superseded, and unresolved.",mutation/falsification;schema/contract;regression-fixture,polylogue-h6r,,,,no-explicit-paths,,evidence,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:40Z,2026-07-15T19:19:40Z,area:evidence;area:orchestration;area:substrate;horizon:frontier +polylogue-1vpm.6.2,1,open,open,feature,Reconcile work claims with observed repository effects,,beads;evidence;git;orchestration,polylogue-1vpm.6,domain-implementation-slice,"Complete the work-evidence graph by attaching authority-bearing git, GitHub, Beads, artifact, and verification observations, then evaluating whether claims are supported, partial, contradicted, unresolved, or superseded.",receipt/provenance;mutation/falsification;regression-fixture,polylogue-1vpm.6.1;polylogue-2qx.2,,,,no-explicit-paths,records-landed-work+records-residual-work,beads,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:45:41Z,2026-07-15T17:45:41Z,area:beads;area:evidence;area:git;area:orchestration;horizon:frontier +polylogue-1xc,1,open,open,epic,Scale-hardening: bugs that only bite on real-scale archives,,storage,,program/invariant-container,full insight rebuild = one transaction -> 6GB WAL + minutes-long write lock).,live-evidence;bounded-resource;security/authorization;regression-fixture,,polylogue-1xc.8,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/140_polylogue_1xc.md,origin-only=1,records-landed-work,storage,retain program container; do not execute as a leaf,2026-07-14T23:15:41Z,2026-07-14T23:15:41Z,area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale +polylogue-1xc.12,1,open,open,bug,FTS drift gauges + metamorphic coherence tests; rowid-reuse requires block_id check,,storage,polylogue-1xc,regression/repair-slice,,bounded-resource;determinism/idempotency;security/authorization;mutation/falsification;schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/021_polylogue_1xc_12.md,origin-only=1,,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:19:12Z,2026-07-16T16:19:12Z,area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale;tech-tree +polylogue-1xc.13,1,in_progress,in_progress,feature,Expose named-source freshness and excluded cursor degradation,Sinity,daemon;sources;storage,polylogue-1xc,domain-implementation-slice,"2026-07-16 GPT-Pro corpus adjudication: named-source design package 8fa6ec827281 is superseded by implementation package 17d8a28e9c6, merged as PR #2924 (b6c78adfcd666358307daf64ac97e8d695a8b854).",live-evidence;receipt/provenance;regression-fixture,,polylogue-cuxz;polylogue-lkrc;polylogue-yla8,,,no-explicit-paths,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:57:37Z,2026-07-16T12:57:37Z,area:daemon;area:sources;area:storage;delivery:A-trust-floor;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale +polylogue-1xc.14,1,open,open,feature,Declare workload envelopes and resource receipts once,,ops;perf;verification,polylogue-1xc,domain-implementation-slice,Define one workload-envelope declaration and observation contract.,receipt/provenance;bounded-resource;cancellation/cleanup;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-20d.14;polylogue-o21.1;polylogue-s8gb;polylogue-z9gh.1,,,no-explicit-paths,records-landed-work,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T20:08:55Z,2026-07-16T20:08:55Z,area:ops;area:perf;area:verification;horizon:frontier +polylogue-1xc.14.1,1,in_progress,in_progress,feature,Derive archive-scale workload profiles from provider schemas,Sinity,devtools;ops;perf;sources;test;verification,polylogue-1xc.14,domain-implementation-slice,"The vague performance/throughput scenario family is superseded by this mechanism, and focused schema inference, generator, real-route canary, privacy, determinism, memory-bound, and receipt tests plus devtools verify --quick pass.",receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,records-residual-work,schema-workload-generation,retain root; serialize child slices in one branch,2026-07-16T12:50:38Z,2026-07-16T12:50:38Z,area:devtools;area:ops;area:perf;area:sources;area:test;area:verification;horizon:frontier +polylogue-1xc.14.1.1,1,in_progress,in_progress,feature,Make schema inference replayable and memory-bounded,Sinity,devtools;ops;perf;schema;sources;test;verification,polylogue-1xc.14.1,domain-implementation-slice,"Introduce one replayable bounded-observation substrate so every downstream cluster, package, schema, relationship, privacy, and workload-profile pass can consume the same evidence without retaining the corpus.",bounded-resource;cancellation/cleanup;determinism/idempotency;restart/crash;security/authorization;schema/contract;operator-gate;regression-fixture,,,,.agent/scratch/2026-07-17-codex-live-regeneration.md,missing/planned=1,records-landed-work+records-residual-work,schema-workload-generation,retain replay/memory/cancellation/live-receipt slice first,2026-07-17T03:06:59Z,2026-07-17T03:06:59Z,area:devtools;area:ops;area:perf;area:schema;area:sources;area:test;area:verification;horizon:frontier +polylogue-1xc.14.1.2,1,in_progress,in_progress,bug,Prevent observed content from becoming schema property names,Sinity,devtools;ops;perf;schema;security;sources;test;verification,polylogue-1xc.14.1,regression/repair-slice,,live-evidence;security/authorization;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,schema-workload-generation,retain privacy/promotion slice second,2026-07-16T18:22:42Z,2026-07-16T18:22:42Z,area:devtools;area:ops;area:perf;area:schema;area:security;area:sources;area:test;area:verification;horizon:frontier +polylogue-1xc.14.1.3,1,in_progress,in_progress,bug,Separate schema evidence families from release-version defaults,Sinity,devtools;ops;perf;schema;sources;storage;test;verification,polylogue-1xc.14.1,regression/repair-slice,,live-evidence;determinism/idempotency;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,,schema-workload-generation,retain catalog-role/default slice third,2026-07-16T14:31:31Z,2026-07-16T14:31:31Z,area:devtools;area:ops;area:perf;area:schema;area:sources;area:storage;area:test;area:verification;horizon:frontier +polylogue-20d,1,open,open,epic,Interactive performance: the front door answers in interactive time,,perf,,program/invariant-container,,live-evidence;bounded-resource;operator-gate;regression-fixture,,polylogue-d22s;polylogue-ovme,,docs/plans/slo-catalog.yaml;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/142_polylogue_20d.md,both=1;origin-only=1,,perf,retain program container; do not execute as a leaf,2026-07-15T19:23:14Z,2026-07-15T19:23:14Z,area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-20d.17,1,open,open,bug,Serve every status surface from budgeted component snapshots,,daemon;ops;perf,polylogue-20d,canonical-authority-owner,Define one StatusComponentSpec and StatusSnapshot protocol reused by daemon/archive and agent-coordination status.,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;security/authorization;mutation/falsification,,polylogue-20d.14;polylogue-cuxz;polylogue-s7ae.8,polylogue-703,,no-explicit-paths,records-landed-work+records-residual-work,status-snapshots,retain-as-canonical-owner; absorb 703 acceptance evidence,2026-07-15T20:20:00Z,2026-07-15T20:20:00Z,area:daemon;area:ops;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-2qx,1,open,open,epic,"OriginSpec: declare source admission, fidelity, provenance, and coverage once",,sources,polylogue-l4kf,program/invariant-container,"OriginSpec is the sole executable source-admission contract and drives dispatch, completeness, coverage/readiness, generated docs/schemas, fixture discovery, public filter schemas, CLI/MCP help, completions, and vocabulary errors.",live-evidence;receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-o21;polylogue-z9gh.7,,docs/schemas;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/099_polylogue_2qx.md,both=1;origin-only=1,records-landed-work+records-residual-work,sources,retain program container; do not execute as a leaf,2026-07-15T19:46:20Z,2026-07-15T19:46:20Z,area:sources;delivery:K-interop-origin-export;delivery:ac-patched;horizon:frontier;lane:origin-interop-export;refactor +polylogue-2qx.1,1,open,open,epic,Land the OriginSpec kernel and migrate the current origin vocabulary,,sources;substrate,polylogue-2qx,program/invariant-container,,parity/completeness;determinism/idempotency;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,,sources,retain program container; do not execute as a leaf,2026-07-16T12:56:54Z,2026-07-16T12:56:54Z,area:sources;area:substrate;horizon:frontier +polylogue-2qx.1.1,1,open,open,feature,Land the OriginSpec admission kernel and conformance law,,architecture;sources;verification,polylogue-2qx.1,canonical-authority-owner,Add polylogue/sources/origin_specs.py as the sole typed Origin-domain registry over polylogue/declarations.,receipt/provenance;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-o21.1,,,polylogue/sources/origin_specs.py;polylogue/declarations,missing/planned=2,records-landed-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:49:20Z,2026-07-15T20:49:20Z,area:architecture;area:sources;area:verification;horizon:frontier +polylogue-2qx.1.2,1,open,open,task,Migrate every current origin onto OriginSpec,,sources;verification,polylogue-2qx.1,domain-implementation-slice,,receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-2qx.1.1,,,polylogue/sources/origin_specs.py,missing/planned=1,records-landed-work,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:49:24Z,2026-07-15T20:49:24Z,area:sources;area:verification;horizon:frontier +polylogue-2qx.2,1,open,open,feature,Admit Claude Code orchestration artifacts through OriginSpec,,orchestration;sources,polylogue-2qx,domain-implementation-slice,,live-evidence;receipt/provenance;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-2qx.1.2,,,,no-explicit-paths,records-landed-work,orchestration,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:40Z,2026-07-15T19:19:40Z,area:orchestration;area:sources;horizon:frontier;origin:claude-code +polylogue-303r,1,open,open,epic,Sinex-backed evidence mode: canonical materials and rebuildable projections,,ingest;substrate,,program/invariant-container,"In Sinex-backed deployments, Sinex is the canonical durable substrate for AI-session evidence: exact provider-native artifacts and attachments, immutable Polylogue-normalized transcript materials, admitted observation/revision history, durable assertion and judgment lifecycle, context-delivery reco…",receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;restart/crash;security/authorization;schema/contract;operator-gate;regression-fixture,,,polylogue-6mv,,no-explicit-paths,records-landed-work+records-residual-work,ingest,retain program container; do not execute as a leaf,2026-07-15T19:19:39Z,2026-07-15T19:19:39Z,area:ingest;area:substrate;horizon:mid +polylogue-303r.2,1,open,open,epic,Sinex publication: drain durable obligations into confirmed evidence,,ingest;substrate,polylogue-303r,program/invariant-container,"Reuse acquisition/RawPersistenceStore, ParsedSession/content_hash, changed-session IDs, and the sole writer boundary; do not add another repository abstraction or settlement vocabulary.",receipt/provenance;bounded-resource;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;operator-gate;regression-fixture,polylogue-303r.1,,polylogue-fs1.9,polylogue/sinex/;tests/unit/sinex;tests/unit/storage/test_durable_migrations.py;tests/unit/cli/test_config_command.py;docs/sinex-interop.md;docs/architecture.md;polylogue/sinex/__init__.py;tests/unit/core/test_config_inventory.py,both=7;symbolic=1,records-landed-work+records-residual-work,ingest,retain program container; do not execute as a leaf,2026-07-15T19:24:36Z,2026-07-15T19:24:36Z,area:ingest;area:substrate;horizon:mid +polylogue-303r.2.2,1,open,open,feature,Prove publication against real Sinex receipts and raw settlement,,integration;sinex;substrate,polylogue-303r.2,proof/verification-slice,,receipt/provenance;determinism/idempotency;security/authorization;mutation/falsification;schema/contract,polylogue-303r.2.1,,,,no-explicit-paths,records-residual-work,integration,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:24:04Z,2026-07-15T18:24:04Z,area:integration;area:sinex;area:substrate;horizon:frontier +polylogue-37t.11,1,open,open,epic,Schedule every context source through one disclosed authority boundary,,context;coordination;substrate,polylogue-37t,program/invariant-container,Schedule every context source through one disclosed authority boundary,receipt/provenance;bounded-resource;determinism/idempotency;operator-gate,,polylogue-1hj;polylogue-1jc;polylogue-37t.12;polylogue-37t.15;polylogue-3gd;polylogue-4smp;polylogue-bfv;polylogue-gjg;polylogue-mhx.4;polylogue-rvh;polylogue-s7ae,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/062_polylogue_37t_11.md,origin-only=1,,context,retain program container; do not execute as a leaf,2026-07-15T19:16:41Z,2026-07-15T19:16:41Z,area:context;area:coordination;area:substrate;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;size:L;spine;wave:1 +polylogue-37t.11.1,1,open,open,feature,"Land the ContextSource scheduler, authority firewall, and ledger",,context;security;substrate,polylogue-37t.11,canonical-authority-owner,Execution order remains canonical judgment transaction before the scheduler firewall.,receipt/provenance;bounded-resource;determinism/idempotency;security/authorization;mutation/falsification;operator-gate;regression-fixture,polylogue-37t.12;polylogue-37t.15,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:41Z,2026-07-15T19:19:41Z,area:context;area:security;area:substrate;horizon:frontier +polylogue-37t.12,1,open,open,feature,Finish and consolidate the canonical judgment transaction,,context,polylogue-37t,canonical-authority-owner,Finish and consolidate the canonical judgment transaction,bounded-resource;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract,,polylogue-37t.1;polylogue-37t.11;polylogue-37t.2,,polylogue/cli/query_verbs.py;polylogue/core/assertion_lifecycle.py,both=1;missing/planned=1,records-landed-work+records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:47:50Z,2026-07-15T20:47:50Z,area:context;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory +polylogue-37t.14,1,open,open,task,"Evaluate evidence ancestry once for support, drift, cycles, and grounding",,context;substrate,polylogue-37t,domain-implementation-slice,,receipt/provenance;bounded-resource;cancellation/cleanup;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-cuxz.2,polylogue-svfj,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/172_polylogue_37t_14.md,origin-only=1,records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:23:11Z,2026-07-15T19:23:11Z,area:context;area:substrate;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;spine;tech-tree +polylogue-37t.2,1,open,open,epic,Inline declaration markers: author structure in provider-neutral prose,,context;ingest,polylogue-37t,program/invariant-container,,live-evidence;receipt/provenance;bounded-resource;parity/completeness;security/authorization;schema/contract;operator-gate;regression-fixture,,polylogue-stc,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/166_polylogue_37t_2.md,origin-only=1,records-landed-work,context,retain program container; do not execute as a leaf,2026-07-15T18:20:31Z,2026-07-15T18:20:31Z,area:context;area:ingest;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory;spine +polylogue-37t.2.1,1,open,open,feature,"Land marker syntax, declarations, provenance, and typed lowering",,context;ingest;substrate,polylogue-37t.2,domain-implementation-slice,,live-evidence;receipt/provenance;bounded-resource;parity/completeness;schema/contract;regression-fixture,polylogue-o21.1,polylogue-o21,,,no-explicit-paths,,marker-work-events,retain marker lowering adapter; share event-kind vocabulary with rii.1,2026-07-15T18:20:32Z,2026-07-15T18:20:32Z,area:context;area:ingest;area:substrate;horizon:frontier +polylogue-37t.23,1,open,open,feature,"Derive session resumability from open obligations, not termination text",,context;insights,polylogue-37t,canonical-authority-owner,"Process termination and objective posture are orthogonal: the archive needs one session-level resumability projection over authority-bearing open obligations, not a second completion truth inferred from the final message.",live-evidence;receipt/provenance;bounded-resource;schema/contract;operator-gate,,polylogue-1vpm.6;polylogue-7yk5,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:55Z,2026-07-16T12:56:55Z,area:context;area:insights;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-3gd,1,open,open,epic,Make Polylogue self-teaching and correctly installable for agents,,context;devloop;legibility,polylogue-37t,program/invariant-container,,receipt/provenance;bounded-resource;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-37t.11.1;polylogue-d1y;polylogue-pj8,polylogue-t46.8;polylogue-z9gh.3,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/069_polylogue_3gd.md,origin-only=1,records-landed-work,context,retain program container; do not execute as a leaf,2026-07-15T20:51:25Z,2026-07-15T20:51:25Z,area:context;area:devloop;area:legibility;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;size:L;spine +polylogue-3gd.2,1,open,open,task,Inject a comprehensive executable Polylogue manual into agent context,,context;devloop;legibility;mcp,polylogue-3gd,consolidation-owner,This supersedes every same-day tiny-bootstrap or pointer-only formulation.,live-evidence;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-pj8;polylogue-t46.8;polylogue-z9gh.3,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:51:28Z,2026-07-15T20:51:28Z,area:context;area:devloop;area:legibility;area:mcp;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;size:L;spine +polylogue-3gd.3,1,open,open,task,"Install the agent integration kit through packages, clients, and Nix",,context;devloop;legibility;mcp;ops,polylogue-3gd,consolidation-owner,This supersedes every same-day small discovery-hook or downstream hard-coded tool-list formulation.,live-evidence;receipt/provenance;parity/completeness;determinism/idempotency;restart/crash;security/authorization;schema/contract;operator-gate;regression-fixture,,polylogue-3tl.7;polylogue-pj8;polylogue-t46.8;polylogue-z9gh.3,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:51:32Z,2026-07-15T20:51:32Z,area:context;area:devloop;area:legibility;area:mcp;area:ops;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;size:L;spine +polylogue-3uw,1,open,open,task,Capture-completeness: the instrument's coverage error as a standing measure,,analytics;ingest;ops,polylogue-2qx,domain-implementation-slice,,live-evidence;parity/completeness;schema/contract;regression-fixture,polylogue-d1y,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/184_polylogue_3uw.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:09:41Z,2026-07-15T20:09:41Z,area:analytics;area:ingest;area:ops;delivery:I-analytics-experiments;horizon:frontier;lane:analytics-experiments;spine;tech-tree;wave:2 +polylogue-3v1,1,in_progress,in_progress,task,"Capture extension reliability + status UX: spool health, completeness, gap visibility",Sinity,ingest;web,polylogue-jlme,proof/verification-slice,,live-evidence;bounded-resource;parity/completeness;determinism/idempotency;restart/crash;schema/contract;regression-fixture,,,,docs/design/browser-capture-redesign/,symbolic=1,records-landed-work+records-residual-work,extension-capture,retain capture truth/status authority,2026-07-16T12:57:38Z,2026-07-16T12:57:38Z,area:ingest;area:web;delivery:G-live-performance;horizon:frontier;lane:capture-reliability +polylogue-41ow,1,open,open,bug,user_write.py: upsert_assertion has a reproduced TOCTOU race that silently reverts operator judgments,,correctness;storage,,canonical-authority-owner,Multi-row invariants use one immediate transaction; commutative counters elsewhere should remain atomic SQL expressions rather than sharing a coarse process lock.,determinism/idempotency;mutation/falsification;operator-gate,,polylogue-303r.5,,polylogue/storage/sqlite/archive_tiers/user_write.py,both=1,,correctness,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:17:37Z,2026-07-16T16:17:37Z,area:correctness;area:storage;discovered-from:dogfood-2 +polylogue-4p1,1,open,open,epic,Make Query × Projection × Render the sole executable read algebra,,query;surface,polylogue-t46,program/invariant-container,"Polylogue has the pieces of one read algebra but not one authority: SessionQuerySpec, ContentProjectionSpec, ProjectionSpec, RenderSpec, CLI QueryOutputSpec/read-view handlers, MCP request types, and daemon fast paths each own overlapping semantics.",bounded-resource;parity/completeness;security/authorization;mutation/falsification;schema/contract,,polylogue-7le;polylogue-ap7;polylogue-t46.5;polylogue-z9gh.9;polylogue-z9gh.9.1,polylogue-vv2b,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/039_polylogue_4p1.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07-design-reports/one-read-contract-cut.report.md;docs/plans/one-read-contract-cut.*;.agent/reports/decision-brief-2026-07-08.md,origin-only=3;symbolic=1,records-landed-work,query,retain program container; do not execute as a leaf,2026-07-16T12:57:37Z,2026-07-16T12:57:37Z,area:query;area:surface;decision;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-4p1.1,1,open,open,task,Route daemon split-archive fast path through SessionQuerySpec.from_params,,query;surface,polylogue-4p1,domain-implementation-slice,,parity/completeness;schema/contract,,,,polylogue/daemon/http.py;polylogue/archive/query/spec.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/040_polylogue_4p1_1.md,both=2;origin-only=1,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:09:44Z,2026-07-15T20:09:44Z,area:query;area:surface;decision;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-4ts,1,open,open,epic,"Session lineage truth: shared content stored once, counted once, composed correctly",,lineage,,program/invariant-container,,determinism/idempotency;restart/crash;schema/contract;regression-fixture,,polylogue-38x,,docs/design/session-lineage-model.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/176_polylogue_4ts.md,both=1;origin-only=1,records-landed-work+records-residual-work,lineage,retain program container; do not execute as a leaf,2026-07-15T16:56:42Z,2026-07-15T16:56:42Z,area:lineage;delivery:F-lineage-compaction;horizon:frontier;lane:lineage-compaction +polylogue-4ts.3,1,open,open,bug,Distinguish subagent auto-compaction from main-session acompact,,lineage,polylogue-4ts,regression/repair-slice,,live-evidence;security/authorization;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/080_polylogue_4ts_3.md;polylogue/sources/parsers/claude/code_parser.py;polylogue/sources/dispatch.py,both=2;origin-only=1,records-landed-work,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:28:04Z,2026-07-15T19:28:04Z,area:lineage;delivery:F-lineage-compaction;horizon:frontier;lane:lineage-compaction +polylogue-60i5,1,open,open,epic,"Durable-tier change train: declare, reserve, migrate, prove, release",,substrate,polylogue-1xc,program/invariant-container,"(6) APPLY under stopped-daemon/single-writer authority; record pre/post versions, integrity, row parity, and per-rider behavior.",live-evidence;receipt/provenance;parity/completeness;restart/crash;security/authorization;schema/contract;operator-gate,polylogue-8jg9.5,polylogue-stc,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/174_polylogue_60i5.md,origin-only=1,records-landed-work,substrate,retain program container; do not execute as a leaf,2026-07-14T23:24:27Z,2026-07-14T23:24:27Z,area:substrate;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:blob-integrity;schema:source-v8;schema:user-v6;tech-tree +polylogue-60i5.1,1,open,open,feature,Land the durable change-train manifest and lifecycle gate,,storage;substrate,polylogue-60i5,canonical-authority-owner,"Land one machine-readable DurableChangeTrain contract for source.db and user.db so a migration window is declared, admitted, reserved, backup-authorized, applied, proven, and released through one lifecycle rather than coordinated in prose.",receipt/provenance;cancellation/cleanup;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate,,polylogue-p155,,,no-explicit-paths,records-landed-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:56Z,2026-07-16T12:56:56Z,area:storage;area:substrate;delivery:B-storage-rebuild-bytes;horizon:frontier +polylogue-6mvg,1,open,open,task,Accelerate index rebuild planning and replay throughput,,daemon;performance;storage,,domain-implementation-slice,,live-evidence;bounded-resource;determinism/idempotency,,polylogue-3v1,,,no-explicit-paths,records-landed-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T09:55:27Z,2026-07-16T09:55:27Z,area:daemon;area:performance;area:storage +polylogue-71ey,1,open,open,bug,Make the maintenance catalog own replay execution semantics,,cli;ops;storage,polylogue-o21,regression/repair-slice,,security/authorization;schema/contract,,polylogue-9e5.31;polylogue-o21.1;polylogue-sl1,,,no-explicit-paths,,maintenance-declarations,retain maintenance pilot; hard-block on o21.1,2026-07-15T16:44:13Z,2026-07-15T16:44:13Z,area:cli;area:ops;area:storage;delivery:B-storage-rebuild;horizon:frontier;lane:storage-rebuild +polylogue-8jg9.1,1,open,open,task,"Enforce portfolio, active-set, and execution-focus invariants",,beads;devtools;ops;planning,polylogue-b054,domain-implementation-slice,Standalone .agent/tools/bead-lint.py removed (superseded); allowlist at .agent/tools/bead-lint-allow.txt unchanged.,receipt/provenance;bounded-resource;security/authorization;schema/contract;regression-fixture,polylogue-gxjh.1,,,devtools/verify_backlog_hygiene.py;devtools/workspace/frontier_report.py;.agent/tools/bead-lint.py;.beads/issues.jsonl;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/139_polylogue_8jg9_1.md;docs/devtools.md;.agent/tools/bead-lint-allow.txt;docs/comments;tests/unit/devtools/test_verify_backlog_hygiene.py;.github/workflows;.agent/scripts/bd-graph-lint,both=4;origin-only=4;missing/planned=3,records-landed-work+records-residual-work,beads-sync-policy,retain policy consumer after gxjh.1,2026-07-15T19:37:33Z,2026-07-15T19:37:33Z,area:beads;area:devtools;area:ops;area:planning;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale;spine +polylogue-9e5.31,1,in_progress,in_progress,epic,Enforce definition-to-production closure as an executable graph,Sinity,audit,polylogue-9e5,program/invariant-container,"The mechanism covers at least one storage/lifecycle, event, registry/declaration, query, and cross-surface operation family and detects seeded missing, tests-only, bypass, and divergent-twin mutations.",live-evidence;receipt/provenance;bounded-resource;parity/completeness;restart/crash;mutation/falsification;schema/contract;regression-fixture,,polylogue-o21.3,,.agent/scratch/2026-07-15-wiring-closure-census.md,missing/planned=1,records-residual-work,audit,retain program container; do not execute as a leaf,2026-07-15T19:31:08Z,2026-07-15T19:31:08Z,area:audit;delivery:A-trust-floor;horizon:frontier;lane:usage-cost-honesty +polylogue-9e5.31.1,1,open,open,feature,Land the DefinitionClosureGraph kernel and representative policies,,architecture;audit;devtools;verification,polylogue-9e5.31,canonical-authority-owner,"Seed representative policies: one durable data family, one event/write-effect family, one registry/declaration family, one query parse-to-render path, and one CLI/MCP/HTTP/Python semantic operation.",live-evidence;receipt/provenance;bounded-resource;parity/completeness;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,,definition-closure,retain closure-graph consumer; hard-block on o21.1,2026-07-16T13:05:00Z,2026-07-16T13:05:00Z,area:architecture;area:audit;area:devtools;area:verification;horizon:frontier +polylogue-9gh1,1,open,open,epic,[EPIC] config.py: close the gap between documented 5-layer precedence and actual runtime behavior,,config,,program/invariant-container,,live-evidence;regression-fixture,,,,,no-explicit-paths,records-residual-work,config,retain program container; do not execute as a leaf,2026-07-16T11:25:18Z,2026-07-16T11:25:18Z,area:config;discovered-from:dogfood-2 +polylogue-9rw0,1,open,open,feature,Complete derived fast-forwards with source-replay equivalence proof,,substrate,polylogue-b5l,canonical-authority-owner,"The one remaining implementation is the semantic proof formerly split into p5r4: for each eligible generated plan, create a deterministic source-backed sample manifest; replay sampled retained raw sessions through the production parse/materialize route into an owned inactive schema-current generati…",receipt/provenance;determinism/idempotency;schema/contract;regression-fixture,,,,devtools/index_fast_forward.py;tests/unit/devtools/test_index_fast_forward.py,both=2,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:09:51Z,2026-07-15T20:09:51Z,area:substrate;delivery:B-storage-rebuild-bytes;horizon:frontier +polylogue-avmq,1,open,open,epic,Supervise every daemon background service through one lifecycle contract,,architecture;daemon;verification,,program/invariant-container,Supervise every daemon background service through one lifecycle contract,bounded-resource;cancellation/cleanup;parity/completeness;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-yp0,,,,no-explicit-paths,records-landed-work+records-residual-work,daemon-supervision,retain; remove false hard blocker on yp0,2026-07-15T20:09:35Z,2026-07-15T20:09:35Z,area:architecture;area:daemon;area:verification;horizon:frontier;refactor +polylogue-b054,1,open,open,epic,"Portfolio convergence: preserve ambition, expose active work, focus execution",,beads;planning,,program/invariant-container,The canonical frontier implementation must reproduce these counts from complete input and must not treat all 39 P1 items as scheduled.,bounded-resource;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work,beads,retain program container; do not execute as a leaf,2026-07-15T19:37:32Z,2026-07-15T19:37:32Z,area:beads;area:planning;horizon:frontier;spine +polylogue-b054.1.1,1,in_progress,in_progress,bug,Make fresh-checkout affected verification bounded and baseline-aware,Sinity,architecture;beads,polylogue-b054.1,consolidation-owner,"Fresh-worktree and warm-cache runs emit machine-readable receipts consumed by devtools verify and agent guidance, with one tested configuration contract across focused, xdist, seed, affected, and CI modes.",receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-09rn;polylogue-hjpx;polylogue-vyxq;polylogue-wple;polylogue-y6tb,,tests/unit/daemon/test_daemon_cli.py;devtools/test,both=1;missing/planned=1,records-landed-work+records-residual-work,verification-harness,retain harness owner; reparent from closed b054.1 to active 88jp,2026-07-16T11:53:05Z,2026-07-16T11:53:05Z,agent-readiness;area:architecture;area:beads;horizon:frontier;invariant;verification +polylogue-b054.1.1.1,1,open,open,bug,Eliminate nondeterministic demo construct loss under xdist,,architecture;beads;demo;test-harness,polylogue-b054.1.1,regression/repair-slice,,receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;regression-fixture,,,,tests/unit/demo/test_demo_seed_verify.py,both=1,records-landed-work+records-residual-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:50:36Z,2026-07-16T12:50:36Z,agent-readiness;area:architecture;area:beads;area:demo;area:test-harness;horizon:frontier;invariant;verification +polylogue-b054.1.1.3,1,open,open,task,Complete seed receipt identity and resource accounting,,architecture;beads;devtools;test-harness,polylogue-b054.1.1,domain-implementation-slice,,receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;restart/crash;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T20:08:56Z,2026-07-16T20:08:56Z,agent-readiness;area:architecture;area:beads;area:devtools;area:test-harness;horizon:frontier;invariant;verification +polylogue-b054.1.1.4,1,open,open,task,Prove affected selection with a real production mutation,,architecture;beads;devtools;test-harness,polylogue-b054.1.1,proof/verification-slice,,bounded-resource;cancellation/cleanup;determinism/idempotency;mutation/falsification,,,,,no-explicit-paths,,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:54:16Z,2026-07-16T11:54:16Z,agent-readiness;area:architecture;area:beads;area:devtools;area:test-harness;horizon:frontier;invariant;verification +polylogue-b054.1.1.5,1,open,open,task,Prove all seed hang witnesses under isolated and xdist repetition,,architecture;beads;daemon;pipeline;test-harness,polylogue-b054.1.1,proof/verification-slice,,receipt/provenance;bounded-resource;cancellation/cleanup;regression-fixture,,polylogue-09rn;polylogue-b054.1.1.1,,,no-explicit-paths,records-residual-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:54:16Z,2026-07-16T11:54:16Z,agent-readiness;area:architecture;area:beads;area:daemon;area:pipeline;area:test-harness;horizon:frontier;invariant;verification +polylogue-b5l,1,open,open,epic,"Derived-tier transition protocol: plan, build, prove, activate, recover",,daemon;ops;storage,polylogue-1xc,program/invariant-container,"One planner, executor, receipt schema, authority vocabulary, and status projection serve every derived transition; embeddings may use the same protocol with a money-bounded rebuild policy rather than being hard-coded out.",live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;restart/crash;security/authorization;schema/contract;regression-fixture,polylogue-1xc.8,polylogue-20d.15,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/088_polylogue_b5l.md,origin-only=1,records-landed-work+records-residual-work,daemon,retain program container; do not execute as a leaf,2026-07-15T17:19:24Z,2026-07-15T17:19:24Z,area:daemon;area:ops;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale;size:L;spine +polylogue-b5l.1,1,open,open,bug,Make raw-replay rebuild exactly resumable and visibly exclusive,,daemon;ops;storage,polylogue-b5l,regression/repair-slice,"The remaining P1 defect is the raw-replay rebuild path: its attempt row has no committed per-batch cursor or captured delta boundary, so --only-missing can revisit 6,946 superseded historical revisions instead of resuming after the 10,362/17,788 committed boundary.",parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-lkrc;polylogue-n2wy,,devtools/archive_schema_fast_forward.py,both=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:42:24Z,2026-07-15T18:42:24Z,area:daemon;area:ops;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale;size:M +polylogue-bj5h,1,open,open,feature,Selection -> assertion write flow with exact-message evidence ref,,capture,polylogue-yyvg,domain-implementation-slice,,receipt/provenance;cancellation/cleanup;determinism/idempotency;security/authorization;schema/contract;regression-fixture,polylogue-yyvg.4,,,browser-extension/src/content/message_layer.js,both=1,records-residual-work,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T16:51:14Z,2026-07-15T16:51:14Z,area:capture;delivery:L-external-legibility;horizon:frontier +polylogue-cuxz,1,open,open,epic,"EvidenceValue: preserve authority, coverage, freshness, and unknowns",,storage,polylogue-rxdo,program/invariant-container,Define a shared wire/domain protocol for factual values and projections without creating a universal stored object or one confidence score.,live-evidence;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-srjq,,polylogue/storage/sqlite/archive_tiers/,symbolic=1,records-landed-work+records-residual-work,storage,retain program container; do not execute as a leaf,2026-07-15T18:17:28Z,2026-07-15T18:17:28Z,area:storage;horizon:frontier +polylogue-cuxz.2,1,open,open,feature,Land the EvidenceValue declaration core and dogfood canaries,,query;substrate;surface,polylogue-cuxz,canonical-authority-owner,"One typed EvidenceValue protocol represents known zero, unknown, unavailable, skipped, not-applicable, and redacted without sentinels.",parity/completeness;security/authorization;schema/contract;regression-fixture,polylogue-o21.1,polylogue-9l5.7,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:41Z,2026-07-15T19:19:41Z,area:query;area:substrate;area:surface;horizon:frontier +polylogue-duti,1,open,open,task,"Default projections declare readiness, cost, and degraded outcomes",,cli,polylogue-4p1,surface/adaptation-slice,Two production smokes exposed one missing ProjectionSpec contract.,live-evidence;bounded-resource;parity/completeness;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:34:34Z,2026-07-15T19:34:34Z,area:cli;discovered-from:prod-smoke-test-2026-07-09;horizon:frontier +polylogue-f2qv.6,1,open,open,bug,Reconcile profiles and costs to exact provider usage,,analytics;insights,polylogue-f2qv,canonical-authority-owner,Create one canonical per-session usage snapshot with disjoint token-lane authority separate from monetary price authority.,live-evidence;security/authorization,polylogue-cuxz.2,polylogue-cuxz;polylogue-f2qv.5,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:50:04Z,2026-07-15T18:50:04Z,area:analytics;area:insights;delivery:A-trust-floor;horizon:frontier;lane:security-privacy;spine +polylogue-fd2s,1,open,open,bug,config.py: archive_root TOML setting is dead for the runtime Config every real consumer uses,,config,polylogue-9gh1,regression/repair-slice,,receipt/provenance;security/authorization;mutation/falsification;schema/contract,,polylogue-uu8r,,,no-explicit-paths,records-landed-work,config,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:19:40Z,2026-07-16T16:19:40Z,area:config;discovered-from:dogfood-2 +polylogue-gnie,1,open,open,bug,Automate secure browser-to-receiver pairing,,browser-capture;security,,canonical-authority-owner,Reopened with installer-managed native messaging as the recommended automatic design and a user-presence one-time protocol as the compatibility fallback.,restart/crash;security/authorization;mutation/falsification;operator-gate;regression-fixture,polylogue-jlme.5,polylogue-kwsb.1,,polylogue/browser_capture/receiver.py;polylogue/paths/browser_capture_receiver_token_path;browser-extension/src/background.js;tests/unit/browser_capture/test_receiver_token.py;browser-extension/tests/popup.test.js;docs/CLI-help;browser-extension/README.md;docs/design/mk2/**.jsx,both=5;missing/planned=2;symbolic=1,records-landed-work+records-residual-work,browser-capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:18:21Z,2026-07-16T17:18:21Z,area:browser-capture;area:security;horizon:frontier;horizon:mid +polylogue-gxjh.1,1,open,open,task,Make Beads JSONL synchronization monotonic and receipted,,ops,polylogue-8jg9,canonical-authority-owner,"Import compares project/database/branch identity and per-row revision/updated_at in one transaction, creates missing rows, accepts demonstrably newer rows, preserves equal rows, and refuses/report downgrades or incomparable conflicts.",receipt/provenance;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;operator-gate;regression-fixture,,,,,no-explicit-paths,records-landed-work,beads-sync,retain monotonic synchronization owner; add z9gh.1/xnws regressions,2026-07-15T19:16:14Z,2026-07-15T19:16:14Z,area:ops;horizon:frontier +polylogue-h6r,1,open,open,task,Declare stable actor identity and content-addressed execution context,,analytics;insights,polylogue-1vpm,domain-implementation-slice,,receipt/provenance;mutation/falsification;regression-fixture,,polylogue-7aw,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/087_polylogue_h6r.md,origin-only=1,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:16:13Z,2026-07-15T19:16:13Z,area:analytics;area:insights;delivery:F-lineage-compaction;horizon:frontier;lane:lineage-compaction;spine;wave:2 +polylogue-hg8n,1,open,open,epic,Outside adoption v1: first external user of Polylogue,,legibility,,program/invariant-container,,receipt/provenance;restart/crash;schema/contract;operator-gate,,polylogue-3tl.16;polylogue-67ac;polylogue-bby.15;polylogue-rxdo.10;polylogue-rxdo.10.2;polylogue-y8s5,,,no-explicit-paths,records-residual-work,legibility,retain program container; do not execute as a leaf,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:legibility;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-hg8n.1,1,open,open,task,Run the first unaided external adoption receipt,,adoption;legibility,polylogue-hg8n,canonical-authority-owner,Recruit one explicit participant with consent and privacy boundary; provide only the public instructions.,receipt/provenance;restart/crash;security/authorization;operator-gate,,polylogue-yeq.4,,,no-explicit-paths,records-residual-work,adoption,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:32:43Z,2026-07-15T18:32:43Z,area:adoption;area:legibility;delivery:L-external-legibility;horizon:frontier +polylogue-hjpx.2,1,in_progress,in_progress,task,Prove raw replay convergence at the July-15 archive shape,Sinity,sources;storage;test,polylogue-hjpx,proof/verification-slice,,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate,polylogue-hjpx.1,,,,no-explicit-paths,records-landed-work+records-residual-work,raw-authority,retain scale-proof prerequisite to hjpx,2026-07-17T03:54:47Z,2026-07-17T03:54:47Z,area:sources;area:storage;area:test;delivery:A-trust-floor;horizon:frontier;performance;raw-authority;scale-proof +polylogue-ih67,1,open,open,bug,Enrich Codex titles from authored history in canonical ingest,,insights;sources;surface,polylogue-2qx,canonical-authority-owner,"Portfolio placement 2026-07-15: execution slice and live canary of OriginSpec artifact inventory, canonical assembly, authoredness authority, title provenance, and semantic reparse.",live-evidence;receipt/provenance;parity/completeness;determinism/idempotency;security/authorization;schema/contract;operator-gate,polylogue-2qx.1.2,polylogue-30h,,polylogue/sources/assembly_codex.py;polylogue/pipeline/services/ingest_worker.py,both=2,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:49:27Z,2026-07-15T20:49:27Z,area:insights;area:sources;area:surface;delivery:C-read-evidence-contract;horizon:frontier +polylogue-j2zz,1,open,open,bug,Lower Codex orchestration child calls into typed actions,,query;sources,polylogue-2qx,regression/repair-slice,,live-evidence;receipt/provenance;bounded-resource;determinism/idempotency;restart/crash;schema/contract;regression-fixture,polylogue-2qx.1.2,polylogue-9l5.6;polylogue-z9gh.2,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T16:38:55Z,2026-07-15T16:38:55Z,area:query;area:sources;delivery:C-read-evidence-contract;horizon:frontier +polylogue-jlme.5,1,open,open,bug,Bind extension receiver pairing to a stable runtime identity,,ingest;web,polylogue-jlme,proof/verification-slice,,receipt/provenance;bounded-resource;determinism/idempotency;restart/crash;security/authorization;schema/contract;regression-fixture,,polylogue-06zm;polylogue-3v1,,,no-explicit-paths,records-landed-work+records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:18:24Z,2026-07-16T17:18:24Z,area:ingest;area:web;delivery:G-live-performance;horizon:frontier;lane:capture-reliability;spine +polylogue-kwsb.2,1,open,open,feature,MutationTransaction: authorize and receipt every destructive operation,,security;substrate,polylogue-kwsb,canonical-authority-owner,"The missing abstraction is a shared transaction protocol, not a universal mutation executor.",receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,destructive-mutations,retain-as-canonical mutation transaction owner,2026-07-16T12:56:53Z,2026-07-16T12:56:53Z,area:security;area:substrate;delivery:A-trust-floor;horizon:frontier;lane:security-privacy;spine +polylogue-ng9m,1,open,open,bug,Measure and bound daemon catch-up memory envelope,,daemon;perf,polylogue-1xc,regression/repair-slice,,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;restart/crash;operator-gate;regression-fixture,polylogue-1xc.14,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:45:48Z,2026-07-15T18:45:48Z,area:daemon;area:perf;delivery:A-trust-floor;horizon:frontier +polylogue-o21,1,open,open,epic,DeclarationSpec: derive extension surfaces and completeness once,,devtools;substrate,polylogue-a7xr,program/invariant-container,"Three legs, applied per extension point (MCP tool, CLI verb/command, DSL unit/stage, insight, origin, assertion kind, devtools command, workflow): (1) DECLARE-ONCE: each point gets a single declaration object carrying ALL metadata the parallel sites currently hold (name, contract, role gating, sche…",live-evidence;bounded-resource;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-t46;polylogue-utf,,docs/schemas;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/100_polylogue_o21.md,both=1;origin-only=1,,devtools,retain program container; do not execute as a leaf,2026-07-15T18:22:30Z,2026-07-15T18:22:30Z,area:devtools;area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;refactor;wave:2 +polylogue-o21.1,1,open,open,feature,Land the DeclarationSpec kernel and derivation contract,,architecture;devtools;substrate,polylogue-o21,canonical-authority-owner,"Missing producer, handler, role gate, schema, example, generated output, or consumer edge produces one source-locatable Diagnostic containing declaration id, owner path, and exact repair command.",receipt/provenance;parity/completeness;determinism/idempotency;mutation/falsification;schema/contract,,polylogue-1xc.14,,polylogue/declarations;polylogue/declarations/models.py;polylogue/mcp,both=1;missing/planned=2,,declaration-kernel,retain shared storage-free declaration kernel,2026-07-16T12:56:54Z,2026-07-16T12:56:54Z,area:architecture;area:devtools;area:substrate;horizon:frontier +polylogue-oucx,1,open,open,bug,Restore source-derived run and OTel query-unit parity after cache removal,,query;substrate;test,polylogue-a7xr,canonical-authority-owner,"Repair the single source-derived owner and every sync/async or surface adapter that consumes it; do not resurrect dropped cache tables, add suite-order state, or special-case tests.",receipt/provenance;mutation/falsification;regression-fixture,,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:23:09Z,2026-07-15T19:23:09Z,area:query;area:substrate;area:test;horizon:frontier +polylogue-ovme,1,open,open,epic,ArchiveLocation: one typed tier map and generation identity,,devtools;perf;storage,polylogue-1xc,program/invariant-container,"The defect is ambiguous path typing, so the repair is a single archive-plan authority rather than another filename special case.",bounded-resource;parity/completeness;security/authorization;schema/contract;regression-fixture,,polylogue-20d,,,no-explicit-paths,,devtools,retain program container; do not execute as a leaf,2026-07-15T18:48:42Z,2026-07-15T18:48:42Z,area:devtools;area:perf;area:storage;horizon:frontier +polylogue-ovme.1,1,open,open,feature,"Land ArchiveLocation identity, resolution, and split-tier canaries",,config;storage,polylogue-ovme,domain-implementation-slice,,security/authorization;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,,config,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:55Z,2026-07-16T12:56:55Z,area:config;area:storage;horizon:frontier +polylogue-qs0a,1,open,open,bug,Reconcile orphaned blob publication reservations without TTL authority,,blob;storage,polylogue-a7xr,regression/repair-slice,"A reconciler may release only when the owner attempt is terminal or proven superseded, source/index reference checks are empty, no live lease owns the blob, and writer exclusion spans the final recheck and delete.",live-evidence;receipt/provenance;cancellation/cleanup;determinism/idempotency;restart/crash,,polylogue-0puw,,,no-explicit-paths,,blob,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:17:39Z,2026-07-16T16:17:39Z,area:blob;area:storage;discovered-from:dogfood-2 +polylogue-rxdo.2,1,open,open,task,Make promoted query evidence privacy-classified and excisable,,substrate,polylogue-rxdo,domain-implementation-slice,"PR #2813 merged: canonical query-identity module (polylogue/core/query_identity.py) + durable query-provenance substrate (user-tier migration 007_query_objects.sql, USER_SCHEMA_VERSION 6->7, polylogue/storage/sqlite/query_objects.py).",receipt/provenance;parity/completeness;security/authorization;schema/contract;operator-gate;regression-fixture,,polylogue-27m;polylogue-60i5,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/055_polylogue_rxdo_2.md;polylogue/core/query_identity.py;polylogue/storage/sqlite/query_objects.py;polylogue/archive/query/production_evaluator.py;polylogue/archive/query/predicate.py;tests/unit/daemon/test_standing_queries_default_evaluator.py,both=5;origin-only=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:14:51Z,2026-07-15T18:14:51Z,area:substrate;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;schema:user-v5;tech-tree +polylogue-s8gb,1,open,open,task,Recover oversized browser backfill captures through bounded MAIN-world projection,,browser-capture;capture,polylogue-jlme,surface/adaptation-slice,,live-evidence;receipt/provenance;bounded-resource,,polylogue-1xc.14,,,no-explicit-paths,records-landed-work,browser-capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:06:56Z,2026-07-15T18:06:56Z,area:browser-capture;area:capture;delivery:K-interop-origin-export;horizon:frontier;lane:capture-reliability +polylogue-t46,1,open,open,epic,Contracts own surfaces: delete parallel dispatch and the QA middle layer,,unlabeled,,program/invariant-container,"- For each listed first slice — config aliases (daemon_host/daemon_port, top-level observability); hidden/help + historical CLI aliases (find_help, -n, --full, demo-shelf --bundle); status JSON compatibility aliases; origin->provider projection bridges in outputs; browser-capture old synthetic-ID r…",restart/crash;security/authorization;schema/contract,,,,docs/schemas;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/151_polylogue_t46.md,both=1;origin-only=1,records-landed-work+records-residual-work,unlabeled,retain program container; do not execute as a leaf,2026-07-14T22:45:05Z,2026-07-14T22:45:05Z,delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-t46.6,1,open,open,task,Fix referenced_path OR-vs-AND filter divergence and delete dead CLI stats aggregators,,surface,polylogue-t46,surface/adaptation-slice,,live-evidence;bounded-resource;security/authorization;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/044_polylogue_t46_6.md,origin-only=1,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:09:48Z,2026-07-15T20:09:48Z,area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-t46.8,1,open,open,epic,Replace MCP tool sprawl with a protocol-native verb algebra,,mcp,polylogue-t46,program/invariant-container,"MCP cancellation and disconnect propagate to the shared query transaction; repeated concurrent incident-scale calls keep health responsive, bound RSS/PSS/swap/temp usage, return to a measured steady-state envelope, and leave no anonymous payload, reader, cursor, lease, or task.",live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/045_polylogue_t46_8.md,origin-only=1,records-landed-work+records-residual-work,mcp,retain program container; do not execute as a leaf,2026-07-16T16:18:12Z,2026-07-16T16:18:12Z,area:mcp;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor;tech-tree +polylogue-t46.8.1,1,open,open,feature,Declare the MCP verb/resource/prompt algebra and equivalence map,,mcp;protocol;surface,polylogue-t46.8,surface/adaptation-slice,,live-evidence;bounded-resource;parity/completeness;restart/crash;security/authorization;mutation/falsification;schema/contract,polylogue-o21.1,polylogue-z9gh.3,,polylogue/mcp/declarations/models.py;polylogue/declarations;tests/infra/mcp.py,both=1;missing/planned=2,,declaration-kernel,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:49:17Z,2026-07-15T20:49:17Z,area:mcp;area:protocol;area:surface;horizon:frontier +polylogue-t46.8.2,1,open,open,task,Migrate MCP reads to query/read/get/explain and URI resources,,mcp;query;surface,polylogue-t46.8,consolidation-owner,"Move mandate-critical MCP read and discovery families from competing list/search/insight tools onto declared query/read/get/explain transactions and stable URI resources, backed by the shared bounded query transaction.",bounded-resource;cancellation/cleanup;security/authorization;mutation/falsification;schema/contract,polylogue-t46.8.1;polylogue-z9gh.9.1,polylogue-z9gh.7,polylogue-moyt,,no-explicit-paths,,mcp,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:30:28Z,2026-07-15T20:30:28Z,area:mcp;area:query;area:surface;horizon:frontier +polylogue-t46.9,1,open,open,feature,Make OperationSpec the executable mutation authority,,security;storage;surface,polylogue-t46,domain-implementation-slice,,live-evidence;receipt/provenance;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate,,polylogue-a7xr.18;polylogue-jn40;polylogue-jnj.5;polylogue-t46.8,,,no-explicit-paths,,destructive-mutations,supersede/close into kwsb.2; preserve declaration inventory and surface derivation,2026-07-16T16:18:10Z,2026-07-16T16:18:10Z,area:security;area:storage;area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor;spine +polylogue-wmsc,1,open,open,bug,Make embedding freshness one monotonic content-and-recipe invariant,,audit;storage,polylogue-mhx,consolidation-owner,Invariant consolidation 2026-07-15: absorbs polylogue-iqd3 and incorporates the already-fixed y337 success-race as a regression.,live-evidence;bounded-resource;security/authorization;mutation/falsification;schema/contract,,polylogue-303r.7;polylogue-9e5.6,polylogue-iqd3,polylogue/storage/derivation_identity.py,missing/planned=1,records-landed-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:19:12Z,2026-07-16T16:19:12Z,area:audit;area:storage;horizon:frontier +polylogue-wvji,1,open,open,feature,In-page Layer 2: corner chip + slide-over deep-dive,,capture,polylogue-yyvg,canonical-authority-owner,,receipt/provenance;security/authorization;schema/contract;regression-fixture,polylogue-yyvg.4,,,,no-explicit-paths,records-residual-work,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T23:30:18Z,2026-07-14T23:30:18Z,area:capture;delivery:L-external-legibility;horizon:frontier +polylogue-yeq,1,open,open,epic,"Independent falsification program: safety, semantics, query laws, and value",,daemon;query;test;verification,polylogue-88jp,program/invariant-container,It absorbs the former yeq metamorphic/chaos/ref-walk task as one slice; it does not create another product registry or ceremonial dashboard.,live-evidence;receipt/provenance;cancellation/cleanup;parity/completeness;restart/crash;mutation/falsification;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/149_polylogue_yeq.md,origin-only=1,records-residual-work,daemon,retain program container; do not execute as a leaf,2026-07-15T18:01:30Z,2026-07-15T18:01:30Z,area:daemon;area:query;area:test;area:verification;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-yeq.1,1,open,open,task,Prove archive safety through hazard cases and lifecycle fault sequences,,daemon;storage;verification,polylogue-yeq,proof/verification-slice,,receipt/provenance;cancellation/cleanup;restart/crash;security/authorization;mutation/falsification;operator-gate,,polylogue-hjwr;polylogue-lkrc;polylogue-yla8,,,no-explicit-paths,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:43Z,2026-07-15T19:19:43Z,area:daemon;area:storage;area:verification;horizon:frontier +polylogue-yeq.2,1,open,open,task,Mine semantic contradictions and provider construct negative space,,audit;sources;verification,polylogue-yeq,domain-implementation-slice,,receipt/provenance;bounded-resource;security/authorization;mutation/falsification;regression-fixture,,polylogue-2qx.1;polylogue-9e5.31;polylogue-cuxz,,,no-explicit-paths,,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:43Z,2026-07-15T19:19:43Z,area:audit;area:sources;area:verification;horizon:frontier +polylogue-yeq.3,1,open,open,task,"Prove query laws, cross-surface parity, and adversarial scale bounds",,mcp;query;test;verification,polylogue-yeq,consolidation-owner,"Generalize the original yeq metamorphic DSL, daemon chaos, and reference walks into one query-contract differential.",live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;schema/contract;regression-fixture,polylogue-1xc.14,polylogue-t67b;polylogue-t8t;polylogue-z9gh.1;polylogue-z9gh.7;polylogue-z9gh.9.1,polylogue-20d.7,,no-explicit-paths,,mcp,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:19:43Z,2026-07-15T19:19:43Z,area:mcp;area:query;area:test;area:verification;horizon:frontier +polylogue-ys30,1,open,open,feature,Finish Layer 1 against receiver-resolved message identity,,capture,polylogue-yyvg,consolidation-owner,Invariant collapse 2026-07-15: absorbs mpig’s stuck-pending and ordinal-correlation findings.,live-evidence;bounded-resource;determinism/idempotency;restart/crash;schema/contract,polylogue-yyvg.4,,,browser-extension/src/content/message_layer.js;tests/content/message_layer.test.js,both=1;missing/planned=1,records-landed-work+records-residual-work,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:14:56Z,2026-07-15T18:14:56Z,area:capture;delivery:L-external-legibility;horizon:frontier +polylogue-yyvg,1,open,open,epic,Extension redesign: ambient two-way surface,,capture,,program/invariant-container,Supersedes yajm/x5k3 incremental framing.,live-evidence;receipt/provenance;cancellation/cleanup;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;regression-fixture,,,,docs/design/browser-capture-redesign/,symbolic=1,records-residual-work,capture,retain program container; do not execute as a leaf,2026-07-15T19:19:39Z,2026-07-15T19:19:39Z,area:capture;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-yyvg.4,1,open,open,feature,Resolve provider-native conversation and message identity once,,capture;identity;surface,polylogue-yyvg,domain-implementation-slice,One ProviderAdapter identity contract and conformance harness must own this mapping; consumer surfaces may not infer identity independently.,receipt/provenance;bounded-resource;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T13:04:57Z,2026-07-16T13:04:57Z,area:capture;area:identity;area:surface;horizon:frontier +polylogue-06zm.2,2,open,open,feature,Project CaptureJob events into recovery and conversation timelines,,browser;capture;daemon;surface,polylogue-06zm,domain-implementation-slice,,receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;restart/crash;mutation/falsification;schema/contract,polylogue-06zm.1,,,,no-explicit-paths,,browser,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:07:37Z,2026-07-15T18:07:37Z,area:browser;area:capture;area:daemon;area:surface;horizon:frontier +polylogue-06zm.3,2,open,open,task,"Close CaptureJob retention, quota, migration, and profile-loss proof",,browser;capture;ops;storage,polylogue-06zm,domain-implementation-slice,,live-evidence;receipt/provenance;cancellation/cleanup;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,polylogue-06zm.1;polylogue-06zm.2,,,,no-explicit-paths,records-landed-work+records-residual-work,browser,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:07:38Z,2026-07-15T18:07:38Z,area:browser;area:capture;area:ops;area:storage;horizon:frontier +polylogue-0aj,2,open,open,feature,Enforce phased write effects and deferred derived-view scheduling,,substrate,polylogue-a7xr,domain-implementation-slice,,receipt/provenance;determinism/idempotency,,,,tests/unit/archive/test_write_effects.py;tests/unit/archive/test_write_gateway.py,both=2,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:48:01Z,2026-07-15T19:48:01Z,area:substrate;delivery:M-substrate-consolidation;delivery:ac-patched;horizon:frontier;lane:substrate-consolidation;refactor +polylogue-0ns,2,open,open,task,Bound archive embedding work within large sessions,,daemon,polylogue-mhx,domain-implementation-slice,,live-evidence;bounded-resource;determinism/idempotency;restart/crash;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/111_polylogue_0ns.md,origin-only=1,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:daemon;delivery:J-embeddings-retrieval;horizon:frontier;lane:embeddings-retrieval +polylogue-0puw,2,open,open,bug,Revalidate ingest-batch blob publication finalization under crash schedules,,blobs;ingest;storage,polylogue-8jg9,regression/repair-slice,,live-evidence;cancellation/cleanup;determinism/idempotency;restart/crash;mutation/falsification;schema/contract,,polylogue-qs0a,,,no-explicit-paths,,blobs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:26:33Z,2026-07-16T16:26:33Z,area:blobs;area:ingest;area:storage;horizon:frontier +polylogue-0v5b,2,open,open,chore,Cap browser-extension test worker concurrency,,verification,polylogue-88jp,domain-implementation-slice,,receipt/provenance;bounded-resource;schema/contract,,,,,no-explicit-paths,,verification,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:verification;horizon:frontier +polylogue-1lm,2,open,open,task,Composable transcript views: selector x transform x budget algebra,,context;query;surface,polylogue-4p1,domain-implementation-slice,It remains sequenced behind the shared projection normalizer; priority does not imply a parallel executor.,live-evidence;schema/contract,polylogue-jnj.1,polylogue-ap7,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/157_polylogue_1lm.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:54:01Z,2026-07-15T19:54:01Z,area:context;area:query;area:surface;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts +polylogue-1xc.8,2,open,open,task,Schema rebuild-safety scenario,,audit;storage,polylogue-b5l,domain-implementation-slice,,cancellation/cleanup;parity/completeness;determinism/idempotency;schema/contract;operator-gate;regression-fixture,,polylogue-1xc,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/089_polylogue_1xc_8.md,origin-only=1,,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:48:46Z,2026-07-15T18:48:46Z,area:audit;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale +polylogue-20d.1,2,open,open,feature,CLI->daemon fast path over UDS (persistent hot process),,daemon;perf,polylogue-20d,surface/adaptation-slice,"PROTOCOL PRIOR-ART (2026-07-06 DR corpus): JSON-RPC 2.0 as the frame (transport-agnostic, id correlation, notifications, batch); gopls -remote=auto pattern (auto-start daemon on connect, Unix socket, idle listen timeout); watchman/emacs deterministic per-user socket discovery + autostart-on-connect…",live-evidence;cancellation/cleanup;parity/completeness;schema/contract;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/097_polylogue_20d_1.md;tests/unit/cli/test_daemon_golden_parity.py,both=1;origin-only=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:57:38Z,2026-07-16T12:57:38Z,area:daemon;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance;spine;wave:2 +polylogue-20d.13,2,open,open,feature,Complete identity-scoped SSE producer semantics,,daemon;perf;web,polylogue-20d,domain-implementation-slice,,bounded-resource;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;regression-fixture,,polylogue-bby.11,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/093_polylogue_20d_13.md,origin-only=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:50:13Z,2026-07-15T19:50:13Z,area:daemon;area:perf;area:web;delivery:G-live-performance;horizon:frontier;lane:interactive-performance;spine +polylogue-20d.14,2,open,open,epic,Enforce interactive latency as a measured product contract,,audit;perf,polylogue-20d,program/invariant-container,This epic owns one latency contract from declared budgets through benchmark enforcement and production observation; individual performance mechanisms consume it rather than inventing targets.,live-evidence;receipt/provenance;bounded-resource;schema/contract;operator-gate;regression-fixture,,polylogue-1xc.14;polylogue-20d.17,,docs/plans/slo-catalog.yaml;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/091_polylogue_20d_14.md;tests/benchmarks/test_daemon_uds.py,both=2;origin-only=1,records-residual-work,audit,retain program container; do not execute as a leaf,2026-07-15T19:27:25Z,2026-07-15T19:27:25Z,area:audit;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance;spine +polylogue-20d.6,2,open,open,task,Live full-ingest catch-up latency + WAL shape,,daemon;perf,polylogue-20d,canonical-authority-owner,Related invariant to keep verified: full-replace re-ingest rewrites all messages in one transaction (correct for idempotency) — for live-tailed long sessions the append path (sources/live/append_ingest.py) must stay the hot route; run devtools bench ingest-amplification on real tails as a scheduled…,live-evidence;bounded-resource;determinism/idempotency;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/094_polylogue_20d_6.md,origin-only=1,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:daemon;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-2kvn,2,open,open,bug,Align raw-artifact parsed_at contract with parsed writes,,api;durability;test,polylogue-2qx,regression/repair-slice,,determinism/idempotency;schema/contract;regression-fixture,,,,tests/unit/api/test_facade_contracts.py,both=1,,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:api;area:durability;area:test;discovered-from:polylogue-g8km;horizon:frontier +polylogue-37t,2,open,open,epic,Agent context/memory loop: declared claims -> judgment -> preamble -> reboot,,context,,program/invariant-container,"[2026-07-11 recovered implementation] Durable exact context-delivery receipts merged via PR #2703 (37bdfa04c): user v5 additive ledger, canonical image hash, recipient/actor/run/boundary/refs/omissions/caveats, exact retry idempotency and drift refusal.",live-evidence;bounded-resource;security/authorization;operator-gate;regression-fixture,,polylogue-cfk,,tests/unit/context/test_judged_memory_loop.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/162_polylogue_37t.md;.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md,origin-only=2;missing/planned=1,records-landed-work+records-residual-work,context,retain program container; do not execute as a leaf,2026-07-16T12:57:38Z,2026-07-16T12:57:38Z,area:context;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination +polylogue-37t.1,2,open,open,feature,Assertions: consumer wiring + lifecycle tightening for unified overlays,,context,polylogue-37t,domain-implementation-slice,,bounded-resource;operator-gate,,,,tests/unit/insights/test_feedback.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/165_polylogue_37t_1.md,both=1;origin-only=1,records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:05:20Z,2026-07-07T13:05:20Z,area:context;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory +polylogue-37t.16,2,open,open,task,Claim-kind -> allowed grounding-class compatibility registry,,context;substrate,polylogue-37t,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:02:02Z,2026-07-13T04:02:02Z,area:context;area:substrate;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;tech-tree +polylogue-37t.22,2,open,open,feature,Expose durable context-delivery receipts through authenticated surfaces,,api;cli;context;mcp,polylogue-37t,canonical-authority-owner,Keep a single transaction per receipt write and preserve exact-drift refusal across every adapter.,receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract,,,,polylogue/storage/sqlite/archive_tiers/context_delivery_write.py;polylogue/api/archive.py;polylogue/surfaces/payloads.py;polylogue/cli/query_verbs.py;tests/infra/mcp.py;tests/unit/mcp/test_envelope_contracts.py;polylogue/cli/commands/status.py;polylogue/mcp/{server_tools.py;tests/unit/{api;tests/unit/storage/test_context_delivery_write.py;tests/unit/api/test_facade_contracts.py;tests/unit/cli/test_query_verbs_runtime.py;tests/unit/mcp/test_tool_contracts.py;tests/unit/mcp/test_tool_discovery.py,both=12;symbolic=2,records-landed-work,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-11T15:51:53Z,2026-07-11T15:51:53Z,area:api;area:cli;area:context;area:mcp;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination;lane:context-memory +polylogue-37t.3,2,open,open,feature,Reboot-with-refs: session self-compaction protocol,,context,polylogue-37t,domain-implementation-slice,,unspecified/qualitative,,polylogue-37t.12,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/167_polylogue_37t_3.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:05:22Z,2026-07-07T13:05:22Z,area:context;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory +polylogue-37t.7,2,open,open,feature,Close the failure loop: verify postmortem -> next session's context seed,,context;devloop,polylogue-37t,domain-implementation-slice,,restart/crash;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/169_polylogue_37t_7.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:05:23Z,2026-07-07T13:05:23Z,area:context;area:devloop;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-37t.8,2,open,open,feature,Resume routing: map a session to the harness invocation that reopens it,,cli;context,polylogue-37t,domain-implementation-slice,,regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/170_polylogue_37t_8.md,origin-only=1,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T06:28:12Z,2026-07-13T06:28:12Z,area:cli;area:context;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory +polylogue-3tl.16,2,open,open,task,"Public claims view: evidence status over findings, judgments, and ancestry",,legibility,polylogue-3tl,domain-implementation-slice,"""blob cleanup cannot delete leased blobs"", ""one read contract across CLI/daemon/MCP/API/web"", ""agents write candidates, never trusted memory"") maps to bead IDs, schema versions, commands, fixtures, proof artifacts, and required validation lanes (see prework-v2/verification_lanes.md).",security/authorization;regression-fixture,polylogue-37t.14;polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-rxdo.4;polylogue-svfj,,,docs/public-claims.yaml;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/060_polylogue_3tl_16.md;docs/findings/claim-vs-evidence.md;.agent/demos/uplift-two-arm;.agent/handoffs/polylogue-legibility-kit-2026-07-10/09-public-claims-ledger.yaml;devtools/public_claims.py;tests/unit/devtools/test_public_claims.py;docs/claims.yml,both=4;origin-only=3;missing/planned=1,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:34:52Z,2026-07-15T18:34:52Z,area:legibility;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch;tech-tree +polylogue-4be,2,open,open,task,"Restore drill: prove the backups restore, quarterly",,ops,polylogue-8jg9,domain-implementation-slice,,live-evidence;operator-gate,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/033_polylogue_4be.md,origin-only=1,,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:15:27Z,2026-07-13T04:15:27Z,area:ops;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:operational-resilience;spine +polylogue-4s3c,2,open,open,task,Verify execution-control steady-state resource envelope at live scale,,unlabeled,,proof/verification-slice,,live-evidence;receipt/provenance;bounded-resource;regression-fixture,,polylogue-z9gh,,tests/benchmarks,both=1,records-landed-work,query-scale-proof,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T00:41:16Z,2026-07-17T00:41:16Z, +polylogue-4ts.5,2,open,open,feature,Compaction boundary-range columns + effective-context derivation,,lineage,polylogue-4ts,domain-implementation-slice,,live-evidence;schema/contract;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/082_polylogue_4ts_5.md,origin-only=1,,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:54:53Z,2026-07-15T19:54:53Z,area:lineage;delivery:F-lineage-compaction;horizon:frontier;lane:lineage-compaction +polylogue-4ts.9,2,open,open,feature,Expose a seed-relative compact lineage graph,,lineage;query,polylogue-4ts,domain-implementation-slice,Always include the seed and page nodes and edges independently through the shared query transaction.,live-evidence;regression-fixture,polylogue-z9gh.9.1,polylogue-vv2b,,,no-explicit-paths,,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T04:24:06Z,2026-07-15T04:24:06Z,area:lineage;area:query;delivery:F-lineage-compaction;horizon:frontier;lane:lineage-compaction +polylogue-5k5l,2,in_progress,in_progress,feature,Browser-capture asset acquisition: fetch sandbox + file-service bytes at capture time,Sinity,browser;sources,polylogue-83u,domain-implementation-slice,,live-evidence;determinism/idempotency,,,,,no-explicit-paths,records-landed-work+records-residual-work,browser,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T03:44:53Z,2026-07-16T03:44:53Z,area:browser;area:sources;horizon:frontier +polylogue-5q2u,2,open,open,bug,Order rebuild replay by lineage to avoid deferred-tail amplification,,storage,polylogue-b5l,regression/repair-slice,,cancellation/cleanup;parity/completeness;security/authorization;mutation/falsification;regression-fixture,,polylogue-3wb,,tests/benchmarks/test_graph_resolve_deferred_tail.py;polylogue/sources/revision_backfill.py,both=2,records-residual-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-12T02:43:59Z,2026-07-12T02:43:59Z,area:storage;horizon:frontier +polylogue-5wp,2,open,open,task,Declare materialization and freshness policy for every derived view,,insights;storage,polylogue-b5l,consolidation-owner,Class-mechanism promotion 2026-07-15: promoted P3 to P2 and absorbs polylogue-20d.8.,live-evidence;receipt/provenance;bounded-resource;parity/completeness;security/authorization;mutation/falsification;schema/contract,polylogue-0aj,polylogue-1xc.14;polylogue-20d.14;polylogue-38x,,,no-explicit-paths,records-residual-work,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:48:01Z,2026-07-15T19:48:01Z,area:insights;area:storage;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;refactor +polylogue-6o9b,2,open,open,bug,daemon: DB-backed and archive-backed session-detail routes compute different flattened message.text for identical content,,daemon;rendering,polylogue-a7xr,regression/repair-slice,,regression-fixture,,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:18:07Z,2026-07-16T11:18:07Z,area:daemon;area:rendering;discovered-from:dogfood-2 +polylogue-703,2,open,open,task,"One status assembly: daemon/status.py, cli/commands/status.py, and workload diagnostics converge",,daemon;surface,polylogue-t46,surface/adaptation-slice,"The convergence-snapshot bead (4bu) defines the shared payload for converging-state; this bead is the structural follow-through: ONE status assembly module in the substrate computes every shared fact once; daemon HTTP, CLI, and diagnostics become renderers.",bounded-resource;parity/completeness;schema/contract;regression-fixture,,,,,no-explicit-paths,records-residual-work,status-snapshots,supersede/close into 20d.17,2026-07-16T11:44:52Z,2026-07-16T11:44:52Z,area:daemon;area:surface;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-7aw,2,open,open,feature,Bind every session to versioned agent configuration evidence,,analytics;ingest,polylogue-2qx,domain-implementation-slice,Classification and migration of static identity/reference/state/lesson content remains an evidence consumer; judged setup changes belong to polylogue-37t.10 rather than an auto-edit path here.,live-evidence;receipt/provenance;parity/completeness;regression-fixture,,polylogue-37t.10;polylogue-h6r,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/102_polylogue_7aw.md,origin-only=1,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:52:05Z,2026-07-15T19:52:05Z,area:analytics;area:ingest;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-7ey6,2,open,open,task,"Schema-conditional test skips must fail, not skip, in a full checkout",,test,polylogue-88jp,domain-implementation-slice,"Skips should express environmental impossibility, not absorb repo damage.",schema/contract;regression-fixture,,,,tests/unit/core/test_synthetic_semantics.py;tests/unit/core/test_schema_annotation_contracts.py;tests/infra;tests/integration/test_installed_package_smoke.py,both=4,records-residual-work,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:test;horizon:frontier +polylogue-7uqr,2,open,open,bug,Converger process-pool machinery is dead and diverges between file and batch paths,,unlabeled,,regression/repair-slice,,parity/completeness,,,,docs/CLAUDE.md,missing/planned=1,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T00:45:04Z,2026-07-17T00:45:04Z, +polylogue-83u.2,2,open,open,feature,Attachment byte acquisition for non-inline sources (Drive/zip/local),,attachments;storage,polylogue-83u,consolidation-owner,Do not trust that packets anchors for 83u.2; this note supersedes it.,live-evidence;determinism/idempotency;regression-fixture,,,,tests/unit/sources/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/036_polylogue_83u_2.md;browser_capture/models.py,origin-only=1;missing/planned=1;symbolic=1,records-landed-work+records-residual-work,attachments,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:58:33Z,2026-07-15T17:58:33Z,area:attachments;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:blob-integrity +polylogue-83u.3,2,open,open,feature,Preserve uploaded attachment bytes in live browser capture,,attachments;storage,polylogue-83u,domain-implementation-slice,,live-evidence;determinism/idempotency;schema/contract;regression-fixture,,,,polylogue/daemon/http.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/035_polylogue_83u_3.md,both=1;origin-only=1,,attachments,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:28Z,2026-07-08T20:14:28Z,area:attachments;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:blob-integrity +polylogue-83u.6,2,open,open,task,Complete the attachment acquisition before/after census,,attachments;audit;storage,polylogue-83u,domain-implementation-slice,,mutation/falsification;schema/contract,polylogue-5k5l;polylogue-83u.2;polylogue-83u.3,,,.agent/demos/attachment-acquisition-census/regenerate.sh;polylogue/storage/blob_store.py;.agent/handoffs/polylogue-deep-research-2026-07-09/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/037_polylogue_83u_6.md,both=1;origin-only=2;symbolic=1,records-landed-work+records-residual-work,attachments,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:16:12Z,2026-07-16T17:16:12Z,area:attachments;area:audit;area:storage;delivery:B-storage-rebuild-bytes;lane:blob-integrity +polylogue-88jp,2,open,open,epic,Verification risk model: spend test effort where failures can escape,,verification,,program/invariant-container,"Absorbs remaining metric-only scope of c52g, znwj, and n4hb; their landed PR #2787 behavior tests become evidence inputs.",security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-09rn,,,no-explicit-paths,records-landed-work+records-residual-work,verification-risk,retain program container; do not execute as a leaf,2026-07-15T19:19:39Z,2026-07-15T19:19:39Z,area:verification;delivery:M-substrate-consolidation;horizon:frontier;lane:verification-readiness +polylogue-8jg9.3,2,open,open,task,SLO samples + idle-vs-stalled verdict: steady-state observability over convergence,,daemon;ops,polylogue-8jg9,domain-implementation-slice,,regression-fixture,,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:30Z,2026-07-08T20:14:30Z,area:daemon;area:ops;delivery:B-storage-rebuild-bytes;horizon:mid;lane:storage-rebuild-scale;spine;tech-tree +polylogue-8jg9.6,2,open,open,feature,Persist logical archive lineage across tier generations and restores,,ops;storage,polylogue-8jg9,domain-implementation-slice,,receipt/provenance;cancellation/cleanup;determinism/idempotency;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate,,polylogue-fd2s;polylogue-nkmy;polylogue-s8q,,,no-explicit-paths,records-landed-work+records-residual-work,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:19:39Z,2026-07-16T16:19:39Z,area:ops;area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;horizon:mid;lane:operational-resilience;spine +polylogue-9e5.10,2,open,open,task,Measure resume-context efficacy from exact delivery evidence,,audit;context,polylogue-9e5,domain-implementation-slice,,live-evidence;mutation/falsification;schema/contract,polylogue-nas1,,,,no-explicit-paths,,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:16:19Z,2026-07-16T17:16:19Z,area:audit;area:context;delivery:A-trust-floor;horizon:mid;lane:agent-write-safety +polylogue-9e5.31.2,2,open,open,task,Adopt closure policies across every authoritative definition family,,audit;devtools;verification,polylogue-9e5.31,canonical-authority-owner,"Seeded missing-edge mutations in at least one registry, storage/lifecycle family, and cross-surface operation remain detected.",live-evidence;bounded-resource;mutation/falsification;schema/contract;regression-fixture,polylogue-9e5.31.1,,,,no-explicit-paths,records-residual-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:40:53Z,2026-07-15T18:40:53Z,area:audit;area:devtools;area:verification;horizon:mid +polylogue-9l5,2,open,open,epic,Outcome-grounded analytics: the archive answers 'so what' questions,,analytics,,program/invariant-container,,parity/completeness,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/179_polylogue_9l5.md,origin-only=1,,analytics,retain program container; do not execute as a leaf,2026-07-15T19:37:34Z,2026-07-15T19:37:34Z,area:analytics;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments +polylogue-9l5.6,2,open,open,feature,tool-episodes projection: call + result + outcome + context + next action,,analytics;query,polylogue-9l5,surface/adaptation-slice,,bounded-resource;cancellation/cleanup;parity/completeness;restart/crash;schema/contract;regression-fixture,polylogue-z9gh.2,polylogue-1vpm;polylogue-9l5.7.2;polylogue-j2zz;polylogue-z9gh.9.1,,polylogue/storage/insights/session/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/182_polylogue_9l5_6.md,origin-only=1;symbolic=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:23:15Z,2026-07-15T19:23:15Z,area:analytics;area:query;delivery:I-analytics-experiments;horizon:frontier;lane:analytics-experiments +polylogue-9l5.7,2,open,open,epic,Compose honest analytics over canonical metric definitions,,analytics;query,polylogue-9l5,program/invariant-container,"Polylogue needs one analytics validity pipeline, but the former feature combined canonical metric registration, coverage/authority enforcement, DSL composition, basic aggregates, comparative statistics, intervals, effect sizes, and rendering.",parity/completeness;mutation/falsification;schema/contract,,polylogue-3uw;polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-9l5.18;polylogue-9l5.19;polylogue-cpf.5;polylogue-cpf.6;polylogue-cuxz.2;polylogue-f2qv.1;polylogue-f2qv.2;polylogue-f2qv.3;polylogue-f2qv.4;polylogue-f2qv.5;polylogue-rxdo.9.1,,tests/distances;.agent/handoffs/polylogue-gpt-pro-2026-07-06/bundles/rnd-bundle-2-of-6.md;.agent/handoffs/polylogue-gpt-pro-2026-07-06/B-A16-measure-registry.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/116_polylogue_9l5_7.md;polylogue/analytics/stats.py,origin-only=3;missing/planned=2,records-landed-work,analytics,retain program container; do not execute as a leaf,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:analytics;area:query;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;spine;wave:2 +polylogue-9l5.7.2,2,open,open,feature,"Land canonical metric validity, composition, and honest rendering",,analytics;query;substrate,polylogue-9l5.7,canonical-authority-owner,"Implement the minimum high-leverage analytics substrate: consume canonical MetricDefinition identities, enforce construct/frame/authority/denominator/null/confound contracts, compose aggregates through the query algebra, and render EvidenceValue-compatible results.",receipt/provenance;parity/completeness;mutation/falsification;schema/contract,polylogue-cuxz.2;polylogue-o21.1;polylogue-rxdo.9.1,polylogue-9l5.13;polylogue-9l5.5;polylogue-9l5.6;polylogue-bby.3,,,no-explicit-paths,records-landed-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:53:03Z,2026-07-15T18:53:03Z,area:analytics;area:query;area:substrate;horizon:frontier +polylogue-a7xr.18,2,open,open,task,Apply the archive write-effects gateway to every declared write family,,storage;substrate,polylogue-a7xr,domain-implementation-slice,The canonical write-effects contract is only true for ingest.,parity/completeness;determinism/idempotency;mutation/falsification;schema/contract;regression-fixture,,polylogue-0aj;polylogue-9e5.31,,polylogue/archive/write_effects.py,both=1,,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T01:50:40Z,2026-07-15T01:50:40Z,area:storage;area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine +polylogue-a7xr.19,2,open,open,task,Close runtime artifact graph and strict scenario coverage,,substrate;test,polylogue-a7xr,canonical-authority-owner,"devtools lab graph --strict exits 0 and is run by a publish-boundary verification command; deleting one registry edge, lifecycle target, or cross-surface operation proof makes it fail.",mutation/falsification;schema/contract,,polylogue-9e5.31;polylogue-t46,,docs/tests,missing/planned=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T01:51:15Z,2026-07-15T01:51:15Z,area:substrate;area:test;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine +polylogue-a7xr.20,2,open,open,chore,Remove the orphaned legacy pipeline stage subsystem,,pipeline;substrate,polylogue-a7xr,canonical-authority-owner,"Confirm the current production ingest/convergence routes own each behavior, move the one reusable schema-generation helper to its actual service/demo owner, point OperationSpec code_refs at the real acquire actuator, then delete run_stages.py, stage_specs.py, and tests that only memorialize the dea…",live-evidence;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-9e5.31,,,no-explicit-paths,records-landed-work+records-residual-work,pipeline,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T01:51:30Z,2026-07-15T01:51:30Z,area:pipeline;area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine +polylogue-ap7,2,open,open,epic,Semantic transcript renderer registry across normalized tool families,,legibility;surface;web,polylogue-fnm,program/invariant-container,"Polylogue must render typed work semantics—shell, edit/write, file reads/search, task/delegation, web, MCP, attachments, lineage, and unknown tools—through one provider-neutral semantic-card registry shared by terminal and web readers.",bounded-resource;schema/contract;regression-fixture,,polylogue-1ilk;polylogue-1lm;polylogue-37km;polylogue-4p1,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/158_polylogue_ap7.md;polylogue/sources/parsers/chatgpt.py;.agent/handoffs/polylogue-legibility-kit-2026-07-10/fork-prompts/04-polylogue-semantic-renderer.md;.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md;tests/visual;docs/schemas/semantic-card-v1.schema.json;tests/unit/rendering/test_semantic_cards.py;tests/visual/test_reader_semantic_cards.py;tests/unit/daemon/test_daemon_http_contracts.py,both=6;origin-only=3,records-landed-work+records-residual-work,legibility,retain program container; do not execute as a leaf,2026-07-15T18:32:09Z,2026-07-15T18:32:09Z,area:legibility;area:surface;area:web;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-ap7.1,2,open,open,task,Complete semantic-card family coverage and bounded lineage parity,,legibility;rendering;surface,polylogue-ap7,domain-implementation-slice,,receipt/provenance;bounded-resource;parity/completeness;mutation/falsification;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:10:14Z,2026-07-15T18:10:14Z,area:legibility;area:rendering;area:surface;horizon:frontier +polylogue-b054.1.1.2,2,open,open,task,Reduce xdist worker memory amplification without sacrificing throughput,,architecture;beads;test-harness,polylogue-b054.1.1,domain-implementation-slice,,receipt/provenance;bounded-resource;cancellation/cleanup;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:59:05Z,2026-07-16T10:59:05Z,agent-readiness;area:architecture;area:beads;area:test-harness;horizon:frontier;invariant;performance;verification +polylogue-buns,2,open,open,task,Model multi-acquisition provenance for content-identical raw captures,,substrate,polylogue-2qx,domain-implementation-slice,,receipt/provenance;regression-fixture,,polylogue-2ilz,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:41:17Z,2026-07-15T19:41:17Z,area:substrate;horizon:frontier +polylogue-cnu3,2,open,open,task,Make every ingest attempt outcome structurally queryable,,sources,polylogue-2qx,domain-implementation-slice,"Invariant reformulation 2026-07-15: expands the validation-rejection tag into the shared ingest-attempt outcome contract needed by OriginSpec coverage, retry policy, and operator remediation.",receipt/provenance;bounded-resource;cancellation/cleanup;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-fs1.15;polylogue-iwmt,,,no-explicit-paths,,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:41:18Z,2026-07-15T19:41:18Z,area:sources;discovered-from:polylogue-9e5.12;horizon:frontier +polylogue-cuxz.1,2,open,open,task,Retrofit time_confidence contract into predecessor consumers + ArchiveStore reads,,storage,polylogue-cuxz,domain-implementation-slice,,receipt/provenance;parity/completeness;regression-fixture,polylogue-cuxz.2,,,,no-explicit-paths,records-landed-work+records-residual-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:storage;horizon:frontier +polylogue-cuxz.3,2,open,open,task,Migrate fact families and renderers to declared EvidenceValue axes,,substrate;surface,polylogue-cuxz,surface/adaptation-slice,,parity/completeness;mutation/falsification;schema/contract;regression-fixture,polylogue-cuxz.1;polylogue-cuxz.2,,,,no-explicit-paths,records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:17:30Z,2026-07-15T18:17:30Z,area:substrate;area:surface;horizon:mid +polylogue-cxlk,2,open,open,bug,config.py: nested-table TOML merge is full-replace not deep-merge (health.convergence_debt / health.cursor_lag),,config,polylogue-9gh1,regression/repair-slice,,regression-fixture,,,,,no-explicit-paths,records-landed-work,config,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:03:09Z,2026-07-16T11:03:09Z,area:config;discovered-from:dogfood-2 +polylogue-d22s,2,open,open,task,Route ops embed resolve-failure mutation through the daemon writer,,daemon,polylogue-kwsb.2,canonical-authority-owner,"Follow-up from #2796 review (CodeRabbit Major, accepted as debt): polylogue ops embed resolve-failure writes embeddings.db directly from the CLI process, bypassing the daemon single-writer boundary.",receipt/provenance;security/authorization;mutation/falsification;schema/contract,,polylogue-20d,,,no-explicit-paths,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:daemon;horizon:frontier +polylogue-d45p,2,open,open,feature,Maintain an evidence-backed verification failure ledger,,devloop;test,polylogue-88jp,domain-implementation-slice,,determinism/idempotency;security/authorization;mutation/falsification;regression-fixture,,polylogue-9e5.11;polylogue-wple,,,no-explicit-paths,,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:35:14Z,2026-07-15T19:35:14Z,area:devloop;area:test;horizon:frontier +polylogue-da1,2,open,open,feature,Provider format-drift sentinel: detect upstream export-shape changes from live ingest,,daemon;sources,polylogue-2qx,domain-implementation-slice,,bounded-resource;schema/contract;regression-fixture,,polylogue-38x,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/101_polylogue_da1.md,origin-only=1,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:daemon;area:sources;delivery:K-interop-origin-export;horizon:frontier;lane:origin-interop-export +polylogue-e6ja,2,open,open,task,Close the zero-tests-pre-merge hole: bounded per-PR test lane or local-verify attestation (operator decision),,devloop;test,polylogue-88jp,domain-implementation-slice,,operator-gate;regression-fixture,,polylogue-27rb,,tests/unit/;.agent/reports/decision-brief-2026-07-08.md,origin-only=1;symbolic=1,records-landed-work,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:05Z,2026-07-15T19:53:05Z,area:devloop;area:test;decision;horizon:frontier +polylogue-enj7,2,open,open,bug,Provide production-valid daemon service profiles and lifecycle harnesses,,daemon;test,polylogue-avmq,consolidation-owner,Invariant consolidation 2026-07-15: absorbs polylogue-nu2h.,bounded-resource;parity/completeness;mutation/falsification;schema/contract;regression-fixture,,,polylogue-nu2h,,no-explicit-paths,records-landed-work+records-residual-work,daemon-supervision,retain profile/harness slice in avmq branch,2026-07-15T19:32:15Z,2026-07-15T19:32:15Z,area:daemon;area:test;horizon:frontier +polylogue-f2qv,2,open,open,epic,"Provider usage & cost honesty: disjoint token lanes, one pricing source, dual cost view",,analytics,,program/invariant-container,,live-evidence;security/authorization;schema/contract;regression-fixture,,,,docs/internals.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/133_polylogue_f2qv.md,both=1;origin-only=1,records-residual-work,analytics,retain program container; do not execute as a leaf,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:analytics;delivery:A-trust-floor;horizon:frontier;lane:security-privacy;spine +polylogue-f2qv.7,2,open,open,feature,Add a bounded exact-session usage audit,,analytics;cli;query,polylogue-f2qv,canonical-authority-owner,Route selection through the shared query transaction.,live-evidence;bounded-resource;security/authorization,polylogue-f2qv.6;polylogue-z9gh.9.1,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T04:24:02Z,2026-07-15T04:24:02Z,area:analytics;area:cli;area:query;delivery:A-trust-floor;delivery:C-read-evidence-contract;horizon:frontier;lane:security-privacy;spine +polylogue-f57q,2,open,open,bug,Make maintenance preview and apply outcomes phase-honest,,contracts;ops,polylogue-8jg9,regression/repair-slice,,receipt/provenance;cancellation/cleanup;mutation/falsification;schema/contract;regression-fixture,,,,devtools/scale_regression_probe.py;tests/unit/devtools/test_scale_regression_probe.py;polylogue/storage/repair.py;tests/unit/storage/test_repair.py,both=4,records-residual-work,contracts,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T23:27:41Z,2026-07-14T23:27:41Z,area:contracts;area:ops;horizon:frontier +polylogue-f7zw,2,open,open,task,Material protocol: cross-language canonical-bytes golden fixtures (Python/Rust parity),,interop;protocol;test,polylogue-303r,canonical-authority-owner,"Removing NFC normalization, changing float formatting/key ordering/sentinel encoding, or swapping head/transcript boundaries makes at least one test fail in each implementation.",receipt/provenance;bounded-resource;mutation/falsification;regression-fixture,,,,tests/fixtures/material_protocol/v1/small-session,both=1,,interop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T23:27:43Z,2026-07-14T23:27:43Z,area:interop;area:protocol;area:test;horizon:frontier +polylogue-fko9,2,open,open,task,Daemon fast path: read/messages/context views + full golden parity,,cli;perf,polylogue-20d,domain-implementation-slice,,receipt/provenance;parity/completeness;security/authorization;regression-fixture,20d.1,,,tests/unit/cli/test_daemon_golden_parity.py,both=1,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:cli;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-fnm.10,2,open,open,feature,fields/select stage with parent-field projection (first real Transform),,query,polylogue-fnm,canonical-authority-owner,,schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/051_polylogue_fnm_10.md,origin-only=1,records-landed-work+records-residual-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:04:04Z,2026-07-07T13:04:04Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.11,2,open,open,task,Pipeline/clause parity across units + generated support matrix,,query,polylogue-fnm,domain-implementation-slice,,bounded-resource;security/authorization,,,,docs/memory;docs/query-support-matrix.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/049_polylogue_fnm_11.md,origin-only=1;missing/planned=2,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:04:02Z,2026-07-07T13:04:02Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;wave:2 +polylogue-fnm.14,2,open,open,feature,find | compact: token-budgeted corpus-compaction projection with drop manifest,,query;query-dsl,polylogue-fnm,surface/adaptation-slice,,receipt/provenance;determinism/idempotency;security/authorization;regression-fixture,,polylogue-3tl;polylogue-4ts;polylogue-rxdo.3,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/153_polylogue_fnm_14.md,origin-only=1,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T16:55:54Z,2026-07-15T16:55:54Z,area:query;area:query-dsl;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-fnm.5,2,open,open,feature,topic-pack: staged multi-channel topic-lineage retrieval,,query,polylogue-fnm,domain-implementation-slice,,parity/completeness;restart/crash;regression-fixture,,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:39Z,2026-07-08T20:14:39Z,area:query;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts +polylogue-fnm.8,2,open,open,feature,Lineage scope operator: logical: prefix expands predicates across the session family,,lineage;query,polylogue-fnm,domain-implementation-slice,,live-evidence,,,,,no-explicit-paths,records-landed-work,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:00:28Z,2026-07-13T04:00:28Z,area:lineage;area:query;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts +polylogue-fs1.14,2,open,open,feature,Unify Hermes trace evidence with canonical session topology,,ingest;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;determinism/idempotency;regression-fixture,polylogue-fs1.2,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T20:25:47Z,2026-07-14T20:25:47Z,area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:frontier;lane:origin-interop-export +polylogue-fs1.15,2,open,open,task,Expose Hermes-to-Polylogue integration liveness and coverage,,ingest;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;bounded-resource;security/authorization;regression-fixture,polylogue-fs1.11;polylogue-fs1.2;polylogue-fs1.7,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T20:25:47Z,2026-07-14T20:25:47Z,area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:frontier;lane:origin-interop-export +polylogue-fs1.2.1,2,in_progress,in_progress,task,Prove Hermes ATIF/ATOF admission against real producer bytes,Sinity,ingest;substrate,polylogue-fs1.2,proof/verification-slice,,determinism/idempotency;security/authorization;mutation/falsification;regression-fixture,,,,,no-explicit-paths,records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:26:07Z,2026-07-15T19:26:07Z,area:ingest;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;horizon:frontier;lane:origin-interop-export +polylogue-h1wt,2,open,open,task,archive_tiers package __init__.py eager DDL imports cost ~950ms-1.2s on any import,,perf,polylogue-20d,domain-implementation-slice,,bounded-resource;schema/contract;regression-fixture,,polylogue-20d.14,,polylogue/storage/sqlite/archive_tiers/__init__.py;polylogue/storage/sqlite/__init__.py;polylogue/cli/commands/maintenance/_migrate_tier.py;devtools/help_latency_probe.py,both=4,records-residual-work,perf,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:45:10Z,2026-07-15T19:45:10Z,area:perf;horizon:frontier +polylogue-hiu,2,open,open,task,Collapse storage twins onto the sync core behind an async adapter boundary,,substrate,polylogue-a7xr,surface/adaptation-slice,,bounded-resource;regression-fixture,polylogue-b5l;polylogue-exb;polylogue-pf1,,,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:substrate;decision;delivery:M-substrate-consolidation;horizon:mid;lane:substrate-consolidation;refactor;size:L +polylogue-hjwr,2,open,open,feature,Deterministic-rebuild differential lane: full rebuild vs incremental convergence must agree,,daemon;storage;test,polylogue-b5l,domain-implementation-slice,,determinism/idempotency;mutation/falsification;regression-fixture,,polylogue-1xc.8;polylogue-a7xr.2;polylogue-f2qv.5,,devtools/index_fast_forward.py,both=1,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:15:16Z,2026-07-13T04:15:16Z,area:daemon;area:storage;area:test;horizon:frontier +polylogue-ihp0,2,open,open,bug,Derive durable-tier schema assertions from the canonical inventory,,storage;test,polylogue-60i5,canonical-authority-owner,Replace hand-maintained exact sets with the canonical tier inventory/fresh-DDL authority.,parity/completeness;schema/contract;regression-fixture,,,polylogue-gxly,tests/unit/storage/test_archive_tiers_assertions.py;tests/unit/storage/test_archive_tiers_write.py,both=2,records-landed-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:32:17Z,2026-07-15T19:32:17Z,area:storage;area:test;horizon:frontier +polylogue-iwmt,2,open,open,task,Classify transient SQLite locks in append_ingest.py's single-file write path,,daemon;reliability,polylogue-avmq,domain-implementation-slice,,live-evidence;bounded-resource;schema/contract;regression-fixture,,polylogue-9e5.7,,docs/audits/2026-07-09-daemon-loop-lock-starvation-map.md,both=1,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T16:54:35Z,2026-07-15T16:54:35Z,area:daemon;area:reliability;horizon:frontier +polylogue-jn40,2,open,open,bug,MCP: add confirm parameters to unprotected destructive tools (interim mitigation ahead of t46.8),,mcp,,regression/repair-slice,,security/authorization,,polylogue-t46.8,,,no-explicit-paths,,destructive-mutations,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:18:12Z,2026-07-16T16:18:12Z,area:mcp;discovered-from:dogfood-2 +polylogue-jnj.1,2,open,open,task,Collapse read per-view flags into ProjectionSpec/RenderSpec algebra,,cli,polylogue-jnj,canonical-authority-owner,Semantic inclusion belongs to ProjectionSpec or ContentProjectionSpec.,schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/046_polylogue_jnj_1.md,origin-only=1,records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:04:00Z,2026-07-07T13:04:00Z,area:cli;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.10,2,open,open,task,Make the completion system and DSL discoverable at point of use,,cli,polylogue-jnj,domain-implementation-slice,,parity/completeness;regression-fixture,,,,,no-explicit-paths,records-landed-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:36Z,2026-07-08T20:14:36Z,area:cli;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.12,2,open,open,task,Empty-result guidance: 0 hits explains itself,,cli;query,polylogue-jnj,domain-implementation-slice,,parity/completeness;schema/contract;regression-fixture,,,,,no-explicit-paths,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:36Z,2026-07-08T20:14:36Z,area:cli;area:query;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.14,2,open,open,task,Bare single-token query-first dispatch: prefer subcommand-typo error over silent search,,cli;surface,polylogue-jnj,domain-implementation-slice,,security/authorization;schema/contract;operator-gate,,,,,no-explicit-paths,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:31Z,2026-07-08T20:14:31Z,area:cli;area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.2,2,open,open,task,analyze boolean modes -> named projections; facets becomes a real verb,,cli,polylogue-jnj,surface/adaptation-slice,,parity/completeness;security/authorization;schema/contract;regression-fixture,polylogue-z9gh.9.1,,,,no-explicit-paths,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:39Z,2026-07-08T20:14:39Z,area:cli;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.4,2,open,open,task,Direct `read session:REF` uses read-view semantics,,cli,polylogue-jnj,domain-implementation-slice,,unspecified/qualitative,,,,polylogue/cli/read_view_handlers.py,both=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:38Z,2026-07-08T20:14:38Z,area:cli;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.8,2,open,open,task,Make first-run human onboarding teach one verified path,,cli,polylogue-jnj,domain-implementation-slice,,bounded-resource;restart/crash;security/authorization;mutation/falsification;regression-fixture,,polylogue-3gd.2,,,no-explicit-paths,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:19:51Z,2026-07-15T20:19:51Z,area:cli;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.9,2,open,open,feature,Intentional runtime/deployment configuration surface,,cli;config,polylogue-jnj,surface/adaptation-slice,,parity/completeness,,,,polylogue/config.py,both=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:37Z,2026-07-08T20:14:37Z,area:cli;area:config;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;refactor +polylogue-jtwu,2,open,open,task,Emit bounded latency evidence from one route-observation contract,,daemon;perf,polylogue-20d.14,consolidation-owner,Emit bounded latency evidence from one route-observation contract,receipt/provenance;bounded-resource;security/authorization;mutation/falsification;regression-fixture,20d.14,polylogue-1xc.14,,,no-explicit-paths,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:49:03Z,2026-07-15T19:49:03Z,area:daemon;area:perf;delivery:G-live-performance;horizon:frontier;lane:interactive-performance +polylogue-kwsb,2,open,open,epic,Security & privacy: the archive can forget on purpose and never leaks secrets,,security,,program/invariant-container,Excision (right-to-forget + secret redaction + blob excision) is execution-grade and shares one mutation-audit/dry-run/--yes contract with reset (jnj.5); the security-privacy-coverage.yaml gaps each have an owning bead or test; the MCP write/admin destructive path shares the same audit-row contract.,receipt/provenance;cancellation/cleanup;parity/completeness;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/130_polylogue_kwsb.md,origin-only=1,records-landed-work+records-residual-work,security,retain program container; do not execute as a leaf,2026-07-15T19:27:26Z,2026-07-15T19:27:26Z,area:security;delivery:A-trust-floor;horizon:frontier;lane:security-privacy;spine +polylogue-l40k,2,open,open,feature,Multi-tab aggregate + calm offline spool model,,capture,polylogue-yyvg,domain-implementation-slice,,bounded-resource;determinism/idempotency;restart/crash;regression-fixture,,,,,no-explicit-paths,records-residual-work,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:27:27Z,2026-07-15T19:27:27Z,area:capture;delivery:L-external-legibility;horizon:mid +polylogue-layg.1,2,open,open,task,Bulk/archive-wide secret-candidate scan (beyond per-session CLI),,security;storage,polylogue-kwsb,surface/adaptation-slice,,receipt/provenance;bounded-resource;security/authorization;operator-gate;regression-fixture,,,,,no-explicit-paths,records-residual-work,security,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:16:39Z,2026-07-15T19:16:39Z,area:security;area:storage;horizon:frontier +polylogue-mhx.2,2,open,open,feature,"Embedding target policy: what gets a vector, at what granularity, at what cost",,embeddings;insights;substrate,polylogue-mhx,domain-implementation-slice,,security/authorization,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/107_polylogue_mhx_2.md,origin-only=1,records-landed-work+records-residual-work,embeddings,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T08:48:08Z,2026-07-13T08:48:08Z,area:embeddings;area:insights;area:substrate;delivery:J-embeddings-retrieval;horizon:frontier;lane:embeddings-retrieval;spine;wave:2 +polylogue-mhx.3,2,open,open,task,Retrieval quality eval lane: measure FTS vs vector vs hybrid before believing any of them,,audit;embeddings;substrate,polylogue-mhx,domain-implementation-slice,,live-evidence;bounded-resource;operator-gate;regression-fixture,,,,docs/search.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/108_polylogue_mhx_3.md,both=1;origin-only=1,records-landed-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:55:43Z,2026-07-15T19:55:43Z,area:audit;area:embeddings;area:substrate;delivery:J-embeddings-retrieval;horizon:mid;lane:embeddings-retrieval +polylogue-mhx.7,2,open,open,bug,Two live vec0 DDL definitions: unify to one canonical embeddings table-creation path,,embeddings;substrate,polylogue-mhx,canonical-authority-owner,Canonical site: storage/sqlite/archive_tiers/embeddings.py (the tier owner per architecture; search_providers/sqlite_vec_runtime.py becomes a consumer importing the DDL constant).,schema/contract,,,,,no-explicit-paths,,embeddings,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:embeddings;area:substrate;delivery:J-embeddings-retrieval;horizon:frontier;lane:embeddings-retrieval;tech-tree +polylogue-mrxt,2,open,open,task,Exercise the first real assertion and judgment transaction,,context,polylogue-37t,canonical-authority-owner,The canonical judgment transaction records one explicit operator verdict and produces the declared visible state without a parallel promotion path.,receipt/provenance;parity/completeness;determinism/idempotency;operator-gate;regression-fixture,polylogue-37t.12,,,,no-explicit-paths,records-landed-work+records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:22:36Z,2026-07-15T19:22:36Z,area:context;discovered-from:polylogue-9e5.1;horizon:frontier +polylogue-nas1,2,open,open,task,Separate provider-native resume topology from context-assisted continuation,,insights,polylogue-4ts,domain-implementation-slice,"One provider-native resume without Polylogue context, one context-assisted continuation, one bare continuation, and one prepared-but-never-used context are distinguishable by the same query contract.",receipt/provenance;mutation/falsification;schema/contract,polylogue-7s57,polylogue-1vpm.6;polylogue-37t.22,,,no-explicit-paths,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:40:19Z,2026-07-15T19:40:19Z,area:insights;discovered-from:polylogue-9e5.10;horizon:frontier +polylogue-of39,2,open,open,chore,Post-billing-unlock CI re-verification sweep,,verification,polylogue-88jp,domain-implementation-slice,,receipt/provenance;parity/completeness;regression-fixture,,,,,no-explicit-paths,records-landed-work,verification,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T09:43:08Z,2026-07-13T09:43:08Z,area:verification;horizon:frontier +polylogue-ovme.2,2,open,open,task,"Migrate storage, status, maintenance, and transitions to ArchiveLocation",,daemon;ops;storage,polylogue-ovme,domain-implementation-slice,,bounded-resource;parity/completeness;security/authorization;schema/contract;regression-fixture,polylogue-ovme.1,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:08:42Z,2026-07-15T18:08:42Z,area:daemon;area:ops;area:storage;horizon:frontier +polylogue-ovme.3,2,open,open,task,Migrate devtools campaigns and enforce ArchiveLocation completeness,,devtools;perf;storage,polylogue-ovme,domain-implementation-slice,,receipt/provenance;parity/completeness;security/authorization;mutation/falsification;regression-fixture,polylogue-ovme.1,,,,no-explicit-paths,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:08:43Z,2026-07-15T18:08:43Z,area:devtools;area:perf;area:storage;horizon:frontier +polylogue-ox0,2,open,open,epic,Codex authoritative-state and AppServer integration,,context;ingest;sources,polylogue-l4kf,program/invariant-container,,live-evidence;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-2qx,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/103_polylogue_ox0.md,origin-only=1,records-landed-work+records-residual-work,context,retain program container; do not execute as a leaf,2026-07-15T19:51:03Z,2026-07-15T19:51:03Z,area:context;area:ingest;area:sources;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-ox0.1,2,open,open,feature,Ingest and reconcile Codex state and goal databases,,ingest;sources;substrate,polylogue-ox0,domain-implementation-slice,,live-evidence;parity/completeness;determinism/idempotency;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:51:32Z,2026-07-15T19:51:32Z,area:ingest;area:sources;area:substrate;horizon:frontier +polylogue-p5li,2,open,open,bug,Triage six clean-baseline failures into four owned invariants,,test,polylogue-88jp,canonical-authority-owner,"Treat this as one baseline-failure triage transaction under the verification risk model, not one implementation patch.",determinism/idempotency;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-d45p,,,tests/unit/cli/test_archive_maintenance_cli.py;polylogue/storage/sqlite/archive_tiers/bootstrap.py;polylogue/storage/embeddings/reconcile.py;tests/unit/daemon/test_daemon_cli.py,both=4,records-residual-work,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:35:26Z,2026-07-15T19:35:26Z,area:test;horizon:frontier +polylogue-peo,2,open,open,bug,Correlate unexplained daemon exits with host and workload evidence,,daemon;ops,polylogue-8jg9,regression/repair-slice,,receipt/provenance;bounded-resource;determinism/idempotency;restart/crash;mutation/falsification;regression-fixture,,polylogue-1xc.14;polylogue-20d.17,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/034_polylogue_peo.md,origin-only=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:38:50Z,2026-07-15T19:38:50Z,area:daemon;area:ops;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:operational-resilience +polylogue-prfe,2,open,open,task,Audit devtools/ for duplicated per-command scaffolding vs legitimate breadth,,unlabeled,,canonical-authority-owner,"Unconfirmed whether this reflects legitimate verification breadth or duplicated load-check-diff-report scaffolding across commands that could collapse into one registry-driven harness (precedent: insights/registry.py, 803 lines covering comparable complexity via one descriptor pattern).",unspecified/qualitative,,,,.agent/scratch/test-suite-composition-and-scale-2026-07-16.md;devtools/dev_loop.py;docs/devtools.md;.agent/conductor-devloop/{OPERATING-LOG.md,both=2;missing/planned=1;symbolic=1,records-residual-work,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:31:43Z,2026-07-16T10:31:43Z, +polylogue-psz6,2,open,open,task,Codex multi-meta CONTINUATION-as-proxy heuristic still infers relationship type from a count,,sources,polylogue-4ts,domain-implementation-slice,,regression-fixture,,,,,no-explicit-paths,,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:40:20Z,2026-07-15T19:40:20Z,area:sources;discovered-from:polylogue-38x;horizon:frontier +polylogue-qz86,2,open,open,task,Fold .agent/tools/ Beads-delivery-workflow scripts into devtools workspace beads-* subcommands,,unlabeled,,domain-implementation-slice,,unspecified/qualitative,,,,.agent/tools/;devtools/command_catalog.py,both=1;symbolic=1,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:51:26Z,2026-07-16T10:51:26Z, +polylogue-r7p6,2,open,open,bug,Codex token columns on session_profiles undercount 1000x vs session_model_usage,,unlabeled,,canonical-authority-owner,"are computed during materialization; make them an aggregation over the same canonical usage substrate session_model_usage rows are built from (or aggregate model_usage directly), provider-neutrally.",security/authorization;schema/contract;regression-fixture,,,,docs/cost-model.md,both=1,records-landed-work+records-residual-work,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T01:45:30Z,2026-07-17T01:45:30Z, +polylogue-rii.1,2,open,open,feature,Agent work-event write-leg -> session_events -> materialized read-models,,substrate,polylogue-rii,domain-implementation-slice,,determinism/idempotency;security/authorization;mutation/falsification;schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/071_polylogue_rii_1.md,origin-only=1,,marker-work-events,retain work-event adapter; hard-block on o21.1 and relate to 37t.2.1,2026-07-13T04:00:08Z,2026-07-13T04:00:08Z,area:substrate;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-rii.2,2,open,open,feature,Materialize hook events + OTLP spans into queryable evidence,,ingest;substrate,polylogue-rii,domain-implementation-slice,,determinism/idempotency;security/authorization;schema/contract;regression-fixture,polylogue-rii.1,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/072_polylogue_rii_2.md,origin-only=1,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:54:53Z,2026-07-15T19:54:53Z,area:ingest;area:substrate;delivery:D-agent-context-coordination;delivery:ac-patched;horizon:frontier;lane:agent-coordination +polylogue-rii.3,2,open,open,task,"Ingest fidelity: parser fingerprints, byte-fidelity bands, unparsed-key census, round-trip bar",,ingest;substrate,polylogue-rii,domain-implementation-slice,,regression-fixture,,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:30Z,2026-07-08T20:14:30Z,area:ingest;area:substrate;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;schema:source-v3;tech-tree +polylogue-rii.4,2,open,open,feature,Ingest first-class Codex Cloud and Claude Code Web run evidence,,coordination;ingest;substrate,polylogue-rii,proof/verification-slice,,live-evidence;parity/completeness;determinism/idempotency;security/authorization;regression-fixture,,,,,no-explicit-paths,records-landed-work,coordination,retain-distinct; no duplicate/contradiction found in this pass,2026-07-10T12:48:39Z,2026-07-10T12:48:39Z,area:coordination;area:ingest;area:substrate;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-substrate;size:L +polylogue-rxdo,2,open,open,epic,"Evidence and analysis contracts: definitions, runs, relations, findings, judgments",,substrate,,program/invariant-container,The two loop pilots share one scheduler/state contract without per-loop forks before any other,receipt/provenance;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-37t.12;polylogue-4p1;polylogue-fnm;polylogue-t46,,polylogue/analysis/;docs/design/analysis-substrate.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/159_polylogue_rxdo.md;.agent/handoffs/polylogue-session-snapshot-2026-07-08/6a4ac7f7-f0b4-83eb-941d-7428e03f4834.md,origin-only=2;missing/planned=1;symbolic=1,records-landed-work+records-residual-work,substrate,retain program container; do not execute as a leaf,2026-07-15T19:37:34Z,2026-07-15T19:37:34Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.3,2,open,open,task,"Query-run telemetry with frame, evaluation-world, and privacy contracts",,daemon;substrate,polylogue-rxdo,canonical-authority-owner,"WRITE PATH: single recording hook at the shared execution chokepoint (post-4p1 preset executor; interim: archive_query + MCP safe_call + daemon list/search handlers), fire-and-forget queue to the ops writer (never blocks a read; drop-on-backpressure with a counter).",receipt/provenance;bounded-resource;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,polylogue-cuxz.2,polylogue-27m;polylogue-3uw;polylogue-rxdo.2;polylogue-z9gh.9.1,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/056_polylogue_rxdo_3.md;tests/unit/archive/query/test_production_evaluator.py,both=1;origin-only=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:42:53Z,2026-07-15T18:42:53Z,area:daemon;area:substrate;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;tech-tree +polylogue-rxdo.4,2,open,open,task,Gate finding support on the shared evidence-integrity verdict,,substrate,polylogue-rxdo,domain-implementation-slice,,receipt/provenance;determinism/idempotency,polylogue-37t.14,polylogue-svfj,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/057_polylogue_rxdo_4.md;polylogue/api/archive.py;tests/unit/api/test_facade_contracts.py;polylogue/storage/sqlite/finding_provenance.py;polylogue/surfaces/payloads.py,both=4;origin-only=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:34:51Z,2026-07-15T18:34:51Z,area:substrate;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;tech-tree +polylogue-rzve,2,open,open,bug,Daemon API --api-auth-token help claims auto-generation that is not implemented,,daemon,polylogue-kwsb,regression/repair-slice,,security/authorization;schema/contract,,polylogue-gnie,,polylogue/daemon/cli.py;docs/daemon.md;polylogue/daemon/http.py;tests/unit/daemon,both=4,records-landed-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:28:05Z,2026-07-15T19:28:05Z,area:daemon;horizon:frontier +polylogue-s1kr,2,open,open,task,Govern Python API parity matrix and docs,,audit;surface,polylogue-o21,domain-implementation-slice,,live-evidence;mutation/falsification;schema/contract,,polylogue-9e5.16,,polylogue/api/;tests/unit/api/;docs/library-api.md,both=1;symbolic=2,records-residual-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-12T23:09:00Z,2026-07-12T23:09:00Z,area:audit;area:surface;delivery:A-trust-floor;horizon:frontier +polylogue-s7ae.3,2,open,open,feature,"Deliver scoped coordination messages with unread, ack, expiry, and wakeup semantics",,context;coordination;hooks;mcp,polylogue-s7ae,canonical-authority-owner,"Use one CoordinationMessage plus Delivery/Ack receipt contract in user.db, scoped to direct actor/session, session tree, repo/project, work object/query ref, path/resource, or explicit broadcast.",live-evidence;receipt/provenance;bounded-resource;security/authorization;mutation/falsification;schema/contract,polylogue-37t.11.1;polylogue-37t.15;polylogue-kwsb.1,polylogue-1hj;polylogue-bfv,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/066_polylogue_s7ae_3.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:53:43Z,2026-07-15T19:53:43Z,area:context;area:coordination;area:hooks;area:mcp;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;size:M;spine;wave:2 +polylogue-s8q,2,open,open,bug,Attest deployed archive state and capture freshness,,daemon,polylogue-8jg9,regression/repair-slice,Expose it through the shared StatusComponentSpec/StatusSnapshot protocol in polylogue-20d.17 so expensive checks are off-request and freshness/degradation are explicit.,receipt/provenance;bounded-resource;parity/completeness;schema/contract;regression-fixture,,polylogue-20d.17,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:19:40Z,2026-07-16T16:19:40Z,area:daemon;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale +polylogue-t0p,2,open,open,task,Admit ephemeral Claude Code sidecars before evidence is pruned,,ingest;sources,polylogue-l4kf,domain-implementation-slice,,live-evidence;receipt/provenance;security/authorization;schema/contract;operator-gate,,polylogue-1vpm.6;polylogue-2qx,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/104_polylogue_t0p.md,origin-only=1,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:52:06Z,2026-07-15T19:52:06Z,area:ingest;area:sources;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-t0ta,2,open,open,task,"Tighten chatgpt-export detection (currently single dict-key, externally-versioned, ungated)",,sources,polylogue-2qx,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:sources;discovered-from:polylogue-9e5.12;horizon:frontier +polylogue-t46.4,2,open,open,task,Delegate daemon session-similarity KNN to SqliteVecProvider.query_by_session,,surface,polylogue-t46,domain-implementation-slice,,parity/completeness;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/042_polylogue_t46_4.md,origin-only=1,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:03:57Z,2026-07-07T13:03:57Z,area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-t46.8.2.1,2,open,open,task,MCP: remove archive_list_sessions/archive_search_sessions -- zero-usage duplicates of list_sessions/search with no filter gap,,mcp;query;surface,polylogue-t46.8.2,surface/adaptation-slice,"dogfood-2 round-4 verification of closed polylogue-moyt (investigations/moyt-mcp-collapse-verify.md): archive_list_sessions (server_tools.py:435) and archive_search_sessions (server_tools.py:560) are still live, unchanged, still in EXPECTED_TOOL_NAMES, one day after moyt was closed as ""superseded b…",parity/completeness;schema/contract;regression-fixture,,,,docs/mcp-reference.md,both=1,records-landed-work,mcp,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:44:54Z,2026-07-16T11:44:54Z,area:mcp;area:query;area:surface;discovered-from:dogfood-2;horizon:frontier +polylogue-t46.8.3,2,open,open,task,"Migrate context, assertion, judgment, and maintenance MCP families",,context;mcp;ops;surface,polylogue-t46.8,surface/adaptation-slice,,receipt/provenance;parity/completeness;determinism/idempotency;restart/crash;security/authorization;schema/contract;regression-fixture,polylogue-t46.8.2,,,,no-explicit-paths,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:10:35Z,2026-07-15T20:10:35Z,area:context;area:mcp;area:ops;area:surface;horizon:mid +polylogue-t67b,2,open,open,feature,Compile browser proof obligations from workflow claims,,test;web,polylogue-bby,domain-implementation-slice,,bounded-resource;determinism/idempotency;security/authorization;mutation/falsification;operator-gate;regression-fixture,,polylogue-1ilk,,,no-explicit-paths,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-10T17:06:07Z,2026-07-10T17:06:07Z,area:test;area:web;delivery:H-web-cockpit;horizon:frontier;lane:web-evidence-cockpit +polylogue-tilk,2,open,open,bug,user_write.py: upsert_* identity semantics inconsistent -- content-hash append vs stable-identity update,,annotations;storage,polylogue-a7xr,regression/repair-slice,,bounded-resource;security/authorization,,polylogue-37t.13,,,no-explicit-paths,,annotations,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:21:28Z,2026-07-16T10:21:28Z,area:annotations;area:storage;discovered-from:dogfood-2 +polylogue-u0dm,2,open,open,bug,Query DSL: add pre-parse nesting-depth guard to prevent transformer RecursionError,,query-dsl,,regression/repair-slice,,bounded-resource;security/authorization;schema/contract;operator-gate,,polylogue-ekes;polylogue-fnm;polylogue-h8fr,,polylogue/archive/query/expression.py,both=1,,query-dsl,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T16:18:46Z,2026-07-16T16:18:46Z,area:query-dsl;discovered-from:dogfood-2 +polylogue-uu8r,2,open,open,task,Route the 31 files reading os.environ directly through config.py resolution,,unlabeled,polylogue-9gh1,domain-implementation-slice,,security/authorization,,,,polylogue/config.py;browser_capture/receiver.py,both=1;missing/planned=1,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:43:07Z,2026-07-16T10:43:07Z, +polylogue-v1vo,2,open,open,bug,Resume file-overlap ranking anti-selects refactor-ancestor sessions via dead-path Jaccard,,unlabeled,,regression/repair-slice,,live-evidence;bounded-resource;restart/crash;regression-fixture,,,,,no-explicit-paths,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T01:36:34Z,2026-07-17T01:36:34Z, +polylogue-vhjs,2,open,open,bug,"terminal_state_method/evidence provenance is never populated (NULL for all 8,507 labels)",,unlabeled,,regression/repair-slice,,security/authorization;schema/contract,,,,,no-explicit-paths,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T01:45:18Z,2026-07-17T01:45:18Z, +polylogue-wofr,2,open,open,bug,Marathon sessions are terminal-state blind: bounded profiles skip an O(tail) construct,,unlabeled,,regression/repair-slice,,bounded-resource;parity/completeness;schema/contract,,,,,no-explicit-paths,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T01:45:09Z,2026-07-17T01:45:09Z, +polylogue-wple,2,open,open,chore,Worktree hygiene: detect environment artifacts poisoning lane test runs,,devtools;test,polylogue-88jp,domain-implementation-slice,"Flag stray .venv, node_modules, or inherited cache directories in linked worktrees; flag interpreters whose resolved environment belongs to another checkout.",regression-fixture,,polylogue-b054.1.1;polylogue-d45p,,,no-explicit-paths,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:devtools;area:test;delivery:M-substrate-consolidation;horizon:frontier;lane:verification-readiness +polylogue-x1uh,2,open,open,bug,Isolate and receipt every convergence work unit,,daemon,polylogue-avmq,canonical-authority-owner,"The standing-query stage proves a class-level convergence defect: one unsupported watched query is evaluated inside a stage-wide transaction/exception boundary, discards already-computed siblings, and turns false_means_pending into permanent archive-wide retry debt.",receipt/provenance;bounded-resource;cancellation/cleanup;mutation/falsification;schema/contract,,,,,no-explicit-paths,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:34:36Z,2026-07-15T19:34:36Z,area:daemon;horizon:frontier +polylogue-x35k,2,open,open,task,Compile devloop handoffs from verified state facts and open obligations,,context;substrate,polylogue-37t,domain-implementation-slice,,live-evidence;receipt/provenance;determinism/idempotency;mutation/falsification,polylogue-37t.11.1;polylogue-37t.14,polylogue-1vpm.6;polylogue-cfk;polylogue-fs1.11;polylogue-gxjh.1,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:42:07Z,2026-07-15T19:42:07Z,area:context;area:substrate;horizon:frontier +polylogue-x7d,2,open,open,task,Unify root query row rendering contracts,,query,polylogue-jnj,domain-implementation-slice,"Collapse list/search/select row rendering onto one projection contract for title normalization, snippet bounds, machine payload shape, and plain text rendering, then keep archive_query/query_output/select as thin adapters.",receipt/provenance;bounded-resource;parity/completeness;schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/048_polylogue_x7d.md,origin-only=1,records-residual-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:04:01Z,2026-07-07T13:04:01Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-xl25,2,open,open,task,Implement relocated_lineage + quarantined states for block anchor resolver,,lineage;substrate,polylogue-4ts,domain-implementation-slice,,regression-fixture,,polylogue-svfj,,polylogue/storage/block_anchor.py;docs/architecture.md;tests/unit/storage/test_block_anchor.py,both=3,,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T23:56:41Z,2026-07-08T23:56:41Z,area:lineage;area:substrate;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-y6tb,2,open,open,task,"Configure a default per-test timeout so a single hung test fails with a stack, not a burned step budget",,test,polylogue-88jp,domain-implementation-slice,,security/authorization,,polylogue-b054.1.1,,devtools/verify.py,both=1,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:47:09Z,2026-07-15T19:47:09Z,area:test;horizon:frontier +polylogue-yla8.8,2,open,open,task,Bound complete-prefix verification cost,,daemon;performance;sources;storage;test,polylogue-yla8,domain-implementation-slice,,bounded-resource;security/authorization;mutation/falsification,,polylogue-yla8.6,,,no-explicit-paths,records-landed-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-11T20:33:03Z,2026-07-11T20:33:03Z,area:daemon;area:performance;area:sources;area:storage;area:test;delivery:A-trust-floor;horizon:frontier;lane:operational-resilience;spine +polylogue-0v9p,3,open,open,feature,Language detection and preference facts for variant selection,,context;query;surface,polylogue-4smp,domain-implementation-slice,,live-evidence;receipt/provenance;schema/contract,,,,polylogue/storage/sqlite/archive_tiers/index.py;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/075_polylogue_0v9p.md,both=1;origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:42Z,2026-07-08T20:14:42Z,area:context;area:query;area:surface;delivery:E-variants-preferences;lane:variants-preferences;size:M +polylogue-14t7,3,open,open,task,Wire daemon event bus into a real producer/consumer pair (convert one polling loop),,daemon;events,polylogue-yp0,domain-implementation-slice,,bounded-resource;determinism/idempotency;mutation/falsification;schema/contract;regression-fixture,,,,polylogue/daemon/event_bus.py;docs/retro,both=2,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:daemon;area:events;horizon:frontier +polylogue-1ebm,3,open,open,task,devtools: register bead-cluster.py as a workspace subcommand,,devtools,,domain-implementation-slice,,unspecified/qualitative,,polylogue-2yax;polylogue-utf,,.agent/tools/bead-cluster.py;.agent/tools/,origin-only=1;symbolic=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:20:47Z,2026-07-16T10:20:47Z,area:devtools;discovered-from:dogfood-2 +polylogue-1fp,3,open,open,task,Facade decomposition: split api/archive.py into per-capability protocols,,substrate,polylogue-t46,canonical-authority-owner,"Execution order: (1) land the layering bead first (substrate must stop calling up); (2) cut protocols along the 9e5.14 usage-map clusters, biggest consumer first (MCP tools likely map cleanly to read protocols); (3) each protocol extraction is one PR: define protocol, move/alias methods, re-point o…",bounded-resource;parity/completeness;security/authorization;regression-fixture,polylogue-9e5.14;polylogue-exb,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:59:19Z,2026-07-07T12:59:19Z,area:substrate;delivery:C-read-evidence-contract;delivery:ac-patched;lane:read-contracts;refactor +polylogue-1ilk,3,open,open,task,Webui v2 test stack: vitest component lane + playwright e2e/visual-regression riding the stack decision,,test;web,polylogue-bby,domain-implementation-slice,,security/authorization;schema/contract;regression-fixture,,polylogue-ap7,,tests/visual/test_reader_*.py;tests/visual;.agent/scratch/2026-07-10-webui-verifiability-audit.md,both=1;missing/planned=1;symbolic=1,records-landed-work+records-residual-work,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-11T15:52:31Z,2026-07-11T15:52:31Z,area:test;area:web;horizon:mid +polylogue-1r9c,3,open,open,epic,Decompose Polylogue execution control centers,,architecture;daemon;mcp;storage,,program/invariant-container,"Favor descriptor/registry extraction, typed command specs, and narrow orchestration functions while preserving one canonical contract and avoiding parallel abstractions.",receipt/provenance;mutation/falsification;schema/contract,,,,docs/architecture-hotspots.md;tests/unit/storage/test_archive_tiers_write.py,both=2,records-landed-work+records-residual-work,architecture,retain program container; do not execute as a leaf,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:architecture;area:daemon;area:mcp;area:storage;horizon:frontier;refactor +polylogue-1rfj,3,open,open,task,"Stale ""polylogue browser-capture serve"" doc references (should be polylogued)",,docs,polylogue-3tl,domain-implementation-slice,,schema/contract;operator-gate,,polylogue-gnie,,browser-extension/README.md;docs/design/mk2/design-canvas/{artboard-boundary.jsx;polylogue/cli/*.py;docs/design/mk2/**,both=1;symbolic=3,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:41Z,2026-07-15T17:09:41Z,area:docs +polylogue-1wtm,3,open,open,bug,"engaged_duration_ms is degenerate: 72% equals wall clock, 11% null — carries no signal",,unlabeled,,regression/repair-slice,,unspecified/qualitative,,,,,no-explicit-paths,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T01:45:37Z,2026-07-17T01:45:37Z, +polylogue-20d.11,3,open,open,task,"Read-profile mmap tuning: raise READ_MMAP, lower double-buffering cache",,perf;storage,polylogue-20d,domain-implementation-slice,,bounded-resource;regression-fixture,,,,,no-explicit-paths,records-residual-work,perf,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:59:35Z,2026-07-07T12:59:35Z,area:perf;area:storage;delivery:G-live-performance;delivery:ac-patched;lane:interactive-performance +polylogue-212.10,3,open,open,task,Validate analytical demo packets against their evidence graph,,demos;verification,polylogue-212,domain-implementation-slice,,receipt/provenance;determinism/idempotency;mutation/falsification;schema/contract;regression-fixture,polylogue-37t.14,polylogue-212.7,,,no-explicit-paths,records-landed-work+records-residual-work,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:demos;area:verification;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-212.11,3,open,open,task,Incident 14:32 — one shared deterministic proof world for all flagship demos,,demos,polylogue-212,domain-implementation-slice,Scenario/oracle/mutation separation accepted; most scope superseded by #2674.,receipt/provenance;parity/completeness;determinism/idempotency;regression-fixture,,,,polylogue/scenarios/corpus.py;.agent/handoffs/polylogue-legibility-kit-2026-07-10/;polylogue/demo/verify.py;docs/plans/demo-corpus-construct-audit.md;.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md;devtools/proof_world_real_slice.py;polylogue/scenarios/;.agent/scratch/real-slice-vetting-2026-07-14/,both=4;origin-only=1;symbolic=3,records-landed-work+records-residual-work,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:demos;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-212.6,3,open,open,task,PF-D8 'Pick up where I left off': abandoned-session triage to live continuation,,context;demos,polylogue-212,domain-implementation-slice,,live-evidence;receipt/provenance;parity/completeness;schema/contract;operator-gate;regression-fixture,polylogue-37t.23;polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj;polylogue-tsk,polylogue-stc,,,no-explicit-paths,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:demos;delivery:L-external-legibility;delivery:ac-patched;horizon:mid;lane:docs-demos-launch +polylogue-212.9,3,open,open,epic,Fable-as-Foreman campaign: prove delegation discourse before comparing it,,demos,polylogue-212,program/invariant-container,2026-07-10 stop-the-line audit supersedes the prior interim-Task-block readiness note: the shipped delegations view reverses canonical child-to-parent session_links and aliases branch points as dispatches; direct-SQL tests encode the inverse direction.,live-evidence;receipt/provenance,polylogue-212.7;polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj,polylogue-1vpm.1;polylogue-4c27;polylogue-kmts;polylogue-lph4;polylogue-rxdo.7;polylogue-xiyv;polylogue-y964,,,no-explicit-paths,records-landed-work+records-residual-work,demos,retain program container; do not execute as a leaf,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:demos;campaign;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch;tech-tree +polylogue-212.9.2,3,open,open,task,Compare Fable discourse against matched orchestrator controls,,analytics;demos,polylogue-212.9,domain-implementation-slice,,determinism/idempotency;mutation/falsification;schema/contract,polylogue-212.9.1;polylogue-4c27;polylogue-xiyv,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:analytics;area:demos;campaign;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch;tech-tree +polylogue-212.9.3,3,open,open,task,Derive the sanitized public Fable finding and thread,,demos;legibility;privacy,polylogue-212.9,domain-implementation-slice,,live-evidence;receipt/provenance;security/authorization;operator-gate;regression-fixture,polylogue-212.10;polylogue-212.9.1;polylogue-3tl.4.1,,,,no-explicit-paths,,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:demos;area:legibility;area:privacy;campaign;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch;tech-tree +polylogue-2hwl,3,open,open,bug,merge_parsed_session_chunks drops later-chunk titles and can multi-flag active leaf,,unlabeled,,regression/repair-slice,,regression-fixture,,,,,no-explicit-paths,records-landed-work,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T00:45:17Z,2026-07-17T00:45:17Z, +polylogue-303r.3,3,open,open,feature,Rebuild Polylogue projections from Sinex evidence with parity proof,,ingest;substrate,polylogue-303r,surface/adaptation-slice,,receipt/provenance;parity/completeness;mutation/falsification;schema/contract,polylogue-303r.2.2;polylogue-303r.4;polylogue-303r.5;polylogue-303r.6,,,,no-explicit-paths,records-landed-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:ingest;area:substrate;horizon:mid +polylogue-303r.4,3,open,open,feature,Bridge stable Polylogue refs across Sinex replay and re-export,,lineage;substrate,polylogue-303r,domain-implementation-slice,,mutation/falsification;schema/contract;regression-fixture,polylogue-303r.1,,,,no-explicit-paths,,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:lineage;area:substrate;horizon:mid +polylogue-303r.5,3,open,open,feature,Persist durable assertions and context delivery through Sinex,,context;substrate,polylogue-303r,consolidation-owner,Separate proposal from authority: agent/model output remains candidate with inject=false until an operator or declared policy judgment accepts/rejects/defers/supersedes it.,receipt/provenance;determinism/idempotency;restart/crash;operator-gate,polylogue-303r.2.2;polylogue-303r.4,,,,no-explicit-paths,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:context;area:substrate;horizon:mid +polylogue-303r.6,3,open,open,feature,"Enforce backed-mode privacy, retention, and deletion lifecycle",,privacy;security,polylogue-303r,domain-implementation-slice,,receipt/provenance;cancellation/cleanup;parity/completeness;restart/crash;security/authorization;schema/contract;operator-gate;regression-fixture,polylogue-27m;polylogue-303r.2.2;polylogue-303r.5;polylogue-4be,polylogue-83u;polylogue-kwsb,,,no-explicit-paths,records-landed-work+records-residual-work,privacy,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:privacy;area:security;horizon:mid +polylogue-303r.7,3,open,open,feature,Reuse embeddings through a complete model-effect recipe key,,cost;embeddings,polylogue-303r,domain-implementation-slice,,receipt/provenance;security/authorization;schema/contract;regression-fixture,,polylogue-303r.1,,,no-explicit-paths,records-landed-work,cost,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:58:34Z,2026-07-15T17:58:34Z,area:cost;area:embeddings;horizon:vision +polylogue-303r.8,3,open,open,feature,Render Sinex ambient evidence in Polylogue session workflows,,context;interop,polylogue-303r,domain-implementation-slice,,receipt/provenance;bounded-resource;schema/contract;regression-fixture,polylogue-303r.2.2;polylogue-303r.4,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:context;area:interop;horizon:mid +polylogue-30h,3,open,open,feature,Display titles: synthesize when the stored title is a first-prompt echo,,insights;surface,polylogue-4p1,domain-implementation-slice,,receipt/provenance;regression-fixture,,polylogue-ih67,,,no-explicit-paths,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:27:39Z,2026-07-07T13:27:39Z,area:insights;area:surface;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit;wave:2 +polylogue-37km,3,open,open,feature,"Transcript reading surface: measure, speaker rail, outcome-first collapsed tool blocks in the canonical renderer",,surface;web,polylogue-9xuk,surface/adaptation-slice,,receipt/provenance;security/authorization,,polylogue-ap7;polylogue-bby.11,,.agent/reports/aesthetics-direction-2026-07-08.md;tests/visual;tests/unit/rendering,both=2;origin-only=1,records-landed-work,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:17:10Z,2026-07-15T19:17:10Z,area:surface;area:web;horizon:frontier +polylogue-37t.10,3,open,open,feature,Setup evolution via judged candidates: hooks/context-specs/cookbook changes proposed as evidence-linked assertions,,context;devloop,polylogue-37t,domain-implementation-slice,,receipt/provenance;bounded-resource;regression-fixture,polylogue-37t.12,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:00:03Z,2026-07-13T04:00:03Z,area:context;area:devloop;delivery:D-agent-context-coordination;delivery:ac-patched;lane:agent-coordination +polylogue-37t.11.2,3,open,open,feature,"Add adaptive context allocation, deduplication, and explain surfaces",,context;surface,polylogue-37t.11,proof/verification-slice,,live-evidence;receipt/provenance;bounded-resource;determinism/idempotency;security/authorization;mutation/falsification;schema/contract,polylogue-37t.11.1,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:surface;horizon:mid +polylogue-37t.13,3,open,open,task,Revisit beads<->assertions boundary once beads-history ingestion (7fj) lands,,context;coordination,polylogue-37t,domain-implementation-slice,,security/authorization,,,,polylogue/insights/feedback.py;polylogue/storage/insights/feedback,both=2,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:context;area:coordination;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-37t.17,3,open,open,task,Read-access log + memory-utility analytics: which injected memories earn their tokens,,context;daemon,polylogue-37t,domain-implementation-slice,"Enables memory-utility analytics: injected-but-never-used memories, warnings that preceded avoided mistakes, saved queries returning nothing, recall packs never opened (dead-memory detection) -> delete/supersede recommendations surfaced as candidate assertions.",receipt/provenance;bounded-resource;security/authorization,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-06/bundles/rnd-bundle-2-of-6.md,origin-only=1,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:daemon;delivery:D-agent-context-coordination;horizon:mid;lane:context-memory;tech-tree +polylogue-37t.18,3,open,open,feature,"Second-brain entity graph: structural-vs-candidate mention split, backlinks, topic co-occurrence",,context;substrate,polylogue-37t,domain-implementation-slice,,receipt/provenance;determinism/idempotency;restart/crash;regression-fixture,,polylogue-9l5.18,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:substrate;delivery:D-agent-context-coordination;horizon:mid;lane:context-memory;tech-tree +polylogue-37t.19,3,open,open,feature,"Semantic notification policy: route CONTENT signals through the existing fan-out, fatigue-controlled",,context;daemon,polylogue-37t,domain-implementation-slice,,bounded-resource;determinism/idempotency;security/authorization;regression-fixture,,polylogue-rxdo.5,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:daemon;delivery:D-agent-context-coordination;horizon:mid;lane:context-memory;tech-tree +polylogue-37t.2.2,3,open,open,task,"Measure optional marker adoption, recall, calibration, and friction",,context;experiment;verification,polylogue-37t.2,domain-implementation-slice,,receipt/provenance;bounded-resource;security/authorization;schema/contract;regression-fixture,polylogue-37t.2.1,polylogue-stc,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:experiment;area:verification;horizon:mid +polylogue-37t.20,3,open,open,feature,Cross-project recall(task_hint) MCP tool: most-similar prior sessions + their lessons across all repos,,context;mcp,polylogue-37t,surface/adaptation-slice,,live-evidence;bounded-resource;schema/contract;regression-fixture,polylogue-37t.15,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:mcp;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;tech-tree +polylogue-37t.21,3,open,open,feature,Prompt/meta-workflow distillery: induce parametrized meta-prompts from high-value past sessions,,context;substrate,polylogue-37t,domain-implementation-slice,,receipt/provenance;regression-fixture,polylogue-37t.15,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;area:substrate;delivery:D-agent-context-coordination;horizon:mid;lane:context-memory;tech-tree +polylogue-37t.4,3,open,open,task,SessionStart preamble opt-in rollout (polylogue + sinnix repos),,context,polylogue-37t,domain-implementation-slice,,bounded-resource;security/authorization,polylogue-37t.12,polylogue-3gd.3,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/173_polylogue_37t_4.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:19:54Z,2026-07-15T20:19:54Z,area:context;delivery:D-agent-context-coordination;lane:agent-coordination;spine;wave:2 +polylogue-37t.6,3,open,open,feature,Session-aware devshell entry: surface what the last agent session left behind,,context;devloop,polylogue-37t,surface/adaptation-slice,,bounded-resource;security/authorization,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/168_polylogue_37t_6.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:37Z,2026-07-15T20:07:37Z,area:context;area:devloop;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-37t.9,3,open,open,feature,Agent self-experimentation rail: PROMPT_EVAL writer + context-spec variation + background candidate passes,,context,polylogue-37t,domain-implementation-slice,,receipt/provenance;bounded-resource;regression-fixture,,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:context;delivery:D-agent-context-coordination;delivery:ac-patched;horizon:frontier;lane:context-memory +polylogue-395j,3,open,open,task,Web/CLI transcript renderer gives zero-content ChatGPT thinking blocks full message-card weight,,legibility;surface;web,polylogue-ap7,surface/adaptation-slice,Make empty-content treatment a semantic renderer rule shared by CLI and web through the ap7 renderer registry.,receipt/provenance;schema/contract;operator-gate;regression-fixture,,,,polylogue/sources/parsers/chatgpt.py,both=1,records-landed-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:legibility;area:surface;area:web;horizon:frontier +polylogue-3gd.1,3,open,open,feature,polylogue doctor + adoption telemetry: why-zero-usage diagnosis with a relevance control,,context;daemon;devloop;legibility,polylogue-3gd,domain-implementation-slice,,receipt/provenance;determinism/idempotency;security/authorization;regression-fixture,polylogue-d1y,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:48Z,2026-07-15T17:09:48Z,area:context;area:daemon;area:devloop;area:legibility;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;size:L;spine;tech-tree +polylogue-3tl.18,3,open,open,task,"Adjudicate legibility-v2 mission control plane (swarmctl, 23-mission graph) vs s7ae substrate",,legibility,polylogue-3tl,canonical-authority-owner,This bead remains the sole classification authority.,parity/completeness;schema/contract;operator-gate,,polylogue-s7ae,,scripts/swarmctl.py;.agent/handoffs/polylogue-legibility-kit-v2-2026-07-10/{scripts,missing/planned=1;symbolic=1,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:52Z,2026-07-16T12:56:52Z,area:legibility;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-3tl.19,3,open,open,feature,Reader-comprehension test harness for README/positioning claims,,legibility,polylogue-3tl,domain-implementation-slice,,live-evidence;receipt/provenance;regression-fixture,,,,.agent/handoffs/polylogue-readme-positioning-2026-07-14/;docs/demos.md;.agent/handoffs/polylogue-readme-positioning-2026-07-14/reader-test-runner.html;docs/examples/reader-comprehension-test/reader-test-runner.html;docs/examples/reader-comprehension-test/README.md;docs/examples/,both=3;origin-only=1;symbolic=2,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:42:05Z,2026-07-14T14:42:05Z,area:legibility;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-3tl.4.1,3,open,open,task,Publish sanitized live-derived findings with transformation manifests,,legibility;privacy,polylogue-3tl.4,domain-implementation-slice,,live-evidence;receipt/provenance;security/authorization;operator-gate;regression-fixture,,,,,no-explicit-paths,records-landed-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:legibility;area:privacy;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch;spine;wave:2 +polylogue-3utv,3,open,open,feature,"Typed route registry: declare-once RouteSpec table generates Starlette router, OpenAPI, and the TS client",,daemon;web,polylogue-o21,surface/adaptation-slice,"RouteSpec registry, one entry per route: RouteSpec(name, method, path template with typed params, request model | None, response model, auth tier CHECK(open|read|write|admin), streaming: none|sse, preset_ref /* the (Q,P,R) preset this route serves, 4p1 — read routes MUST name one */, operation_ref…",receipt/provenance;parity/completeness;schema/contract,,polylogue-4p1;polylogue-bby.11;polylogue-dx1,,tests/unit/daemon,both=1,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:daemon;area:web;horizon:frontier +polylogue-57bg,3,open,open,task,Extend cfk uplift re-run to n=12-20 using the production pack-generation pipeline,,analytics;experiments,polylogue-rxdo,domain-implementation-slice,,unspecified/qualitative,polylogue-e5b5;polylogue-x35k,polylogue-cfk,,.agent/demos/uplift-two-arm/,symbolic=1,records-landed-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T04:50:45Z,2026-07-09T04:50:45Z,area:analytics;area:experiments +polylogue-5en,3,open,open,task,Branch-local daemon/web/extension dev loops: verify remaining AC and close out,,devloop,polylogue-88jp,domain-implementation-slice,,bounded-resource;restart/crash,,,,,no-explicit-paths,records-residual-work,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:02Z,2026-07-07T13:00:02Z,area:devloop;delivery:G-live-performance;horizon:frontier;lane:capture-reliability +polylogue-5vbs,3,open,open,bug,FTS convergence-debt session-scoped retry has no live feeder for the fts stage,,daemon;search,polylogue-a7xr,regression/repair-slice,,unspecified/qualitative,,,,,no-explicit-paths,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:18:05Z,2026-07-16T11:18:05Z,area:daemon;area:search;discovered-from:dogfood-2 +polylogue-60v8,3,open,open,task,Archive .agent/scratch/ directories once their open-bead references clear,,unlabeled,,domain-implementation-slice,,unspecified/qualitative,,,,.agent/scratch/;.beads/issues.jsonl;.agent/archive/scratch-2026-07/;.agent/archive/scratch-2026-07/README.md;.agent/handoffs/polylogue-session-snapshot-2026-07-08/,origin-only=2;symbolic=3,records-landed-work,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:01:56Z,2026-07-16T11:01:56Z, +polylogue-64g7,3,open,open,feature,Track LLM/provider quotas and API usage,,cost;usage,polylogue-f2qv,domain-implementation-slice,,receipt/provenance;security/authorization;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,records-residual-work,cost,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:cost;area:usage;horizon:mid +polylogue-67ac,3,open,open,feature,Measured-result receipts experiment: X% of completion claims lack structural evidence,,demos,polylogue-1vpm,domain-implementation-slice,,live-evidence;receipt/provenance;determinism/idempotency;security/authorization;schema/contract,,,,,no-explicit-paths,records-landed-work+records-residual-work,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:demos;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-7nu9,3,open,open,task,A2A source importer: task streams + Agent Cards -> canonical evidence,,ingest;interop,polylogue-pb0j,domain-implementation-slice,,live-evidence;receipt/provenance;parity/completeness;determinism/idempotency;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,records-landed-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:56:04Z,2026-07-15T19:56:04Z,area:ingest;area:interop;horizon:mid +polylogue-7ome,3,open,open,task,Judgment experience presets over one canonical queue,,surface,polylogue-37t.12,canonical-authority-owner,Implement all human judgment affordances as typed projection/render/input presets over 37t.12 canonical queue and transaction plus the rxdo.9.11-.15 judgment mechanisms.,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;schema/contract;regression-fixture,,polylogue-p5g;polylogue-rxdo.9.16,,polylogue/insights/judgment/,symbolic=1,records-landed-work+records-residual-work,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:surface;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-7yk5,3,open,open,feature,"Goal/question graph: declared open/close events, cross-session linking, the future cone",,substrate,polylogue-1vpm,domain-implementation-slice,,receipt/provenance;schema/contract;operator-gate;regression-fixture,polylogue-37t.2;polylogue-fnm.8;polylogue-mhx.2,,,,no-explicit-paths,records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:24Z,2026-07-15T20:07:24Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-83u,3,open,open,epic,"Attachment & blob evidence integrity: bytes exist, are honest, and stay affordable",,attachments;storage,,program/invariant-container,Define one AttachmentAcquisition contract over an origin-declared handle and a content-addressed blob outcome.,live-evidence;receipt/provenance;bounded-resource;determinism/idempotency;security/authorization;schema/contract;operator-gate,,polylogue-38x,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/137_polylogue_83u.md,origin-only=1,records-residual-work,attachments,retain program container; do not execute as a leaf,2026-07-15T19:38:12Z,2026-07-15T19:38:12Z,area:attachments;area:storage;delivery:B-storage-rebuild-bytes;horizon:mid;lane:blob-integrity +polylogue-83u.5,3,open,open,feature,Blob store zstd compression (36GB -> est 5-8GB),,attachments;perf;storage,polylogue-83u,domain-implementation-slice,,cancellation/cleanup;operator-gate;regression-fixture,polylogue-83u.2;polylogue-83u.3;polylogue-83u.4;polylogue-83u.6,,,,no-explicit-paths,,attachments,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:13:27Z,2026-07-13T04:13:27Z,area:attachments;area:perf;area:storage;delivery:B-storage-rebuild-bytes;delivery:ac-patched;lane:blob-integrity +polylogue-896y,3,open,open,task,Audit production time authority: naive datetimes and wall-clock reads outside a clock seam,,audit;substrate,,proof/verification-slice,,unspecified/qualitative,,,,tests/infra/frozen_clock.py;docs/plans/test-clock-allowlist.yaml,both=2,,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T18:51:55Z,2026-07-16T18:51:55Z,area:audit;area:substrate;horizon:frontier +polylogue-8jg9,3,open,open,epic,"Operational resilience: recoverable, restorable, survives daemon death and deploy",,ops,,program/invariant-container,,live-evidence;restart/crash;security/authorization;operator-gate,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/138_polylogue_8jg9.md,origin-only=1,,ops,retain program container; do not execute as a leaf,2026-07-15T19:38:13Z,2026-07-15T19:38:13Z,area:ops;delivery:B-storage-rebuild-bytes;horizon:mid;lane:operational-resilience;spine +polylogue-90y,3,open,open,feature,"In-page overlay: Polylogue presence on chat sites — archive state, context, assertion capture",,context;ingest;web,polylogue-jlme,canonical-authority-owner,"F2/F3-vs-F4 RESOLVED (2026-07-09, follow-up Claude Design pass grounded in real ChatGPT/Claude.ai screenshots): they are NOT alternatives, they are a two-layer split with one boundary rule -- 'Per-message state blends in.",receipt/provenance;security/authorization;operator-gate,polylogue-1nb2,polylogue-bby.11,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/171_polylogue_90y.md;docs/design/browser-capture-redesign/;docs/design/browser-capture-redesign/README.md,both=1;origin-only=1;symbolic=1,records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T12:41:16Z,2026-07-09T12:41:16Z,area:context;area:ingest;area:web;delivery:G-live-performance;lane:capture-reliability +polylogue-9e5,3,open,open,epic,Audit lane: read-only analyses producing evidence artifacts,,audit,,program/invariant-container,,unspecified/qualitative,,,,,no-explicit-paths,records-residual-work,audit,retain program container; do not execute as a leaf,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:audit;delivery:A-trust-floor;horizon:mid;lane:usage-cost-honesty +polylogue-9l5.18,3,open,open,task,Infer cross-origin threads without confusing similarity with lineage,,analytics;substrate,polylogue-9l5,consolidation-owner,"The former six-unit epic mixed this with entity mentions, topic clustering, world effects, verification runs, and project identity; those now belong to stronger graph and verification contracts.",receipt/provenance;mutation/falsification;schema/contract;regression-fixture,,polylogue-1vpm;polylogue-1vpm.6;polylogue-37t.18;polylogue-9l5.7;polylogue-d45p,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:49:55Z,2026-07-15T19:49:55Z,area:analytics;area:substrate;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-9l5.7.3,3,open,open,feature,Add sampling-aware comparative statistics and uncertainty rendering,,analytics;query,polylogue-9l5.7,domain-implementation-slice,,mutation/falsification;regression-fixture,polylogue-9l5.7.2,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T18:53:06Z,2026-07-15T18:53:06Z,area:analytics;area:query;horizon:mid +polylogue-9xuk,3,open,open,epic,Generate evidence-honest visual semantics across every reader,,surface;web,polylogue-3tl,program/invariant-container,Invariant consolidation 2026-07-15: promoted the former token-only feature into the shared visual-semantics authority.,receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract,polylogue-dbiv,,,,no-explicit-paths,,surface,retain program container; do not execute as a leaf,2026-07-15T19:17:09Z,2026-07-15T19:17:09Z,area:surface;area:web;horizon:frontier +polylogue-9yz,3,open,open,task,Named bounded-dialogue layout for operator-readable windows,,query,polylogue-fnm,domain-implementation-slice,,bounded-resource;parity/completeness;security/authorization;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:query;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts +polylogue-a7xr,3,open,open,epic,Substrate consolidation: kill the storage twins and split the god-modules,,substrate,,program/invariant-container,,security/authorization;schema/contract,,,,,no-explicit-paths,,substrate,retain program container; do not execute as a leaf,2026-07-15T19:37:35Z,2026-07-15T19:37:35Z,area:substrate;delivery:M-substrate-consolidation;horizon:mid;lane:substrate-consolidation;spine +polylogue-ahqd,3,open,open,task,Observe MCP write adoption after role rollout,,context;coordination;mcp,polylogue-s7ae,surface/adaptation-slice,,receipt/provenance;security/authorization;mutation/falsification;schema/contract,polylogue-kwsb.1;polylogue-s7ae.2,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/068_polylogue_ahqd.md,origin-only=1,records-landed-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:51Z,2026-07-15T17:09:51Z,area:context;area:coordination;area:mcp;delivery:D-agent-context-coordination;lane:agent-coordination;spine;wave:2 +polylogue-arso,3,open,open,feature,"Content variant substrate: refs, nodes, alignment, storage",,query;storage;surface,polylogue-4smp,domain-implementation-slice,Use closed vocabularies: kind translation/transliteration/simplification/summary; status candidate/active/rejected/superseded/stale; coverage complete/partial/sparse; relation translates/transliterates/simplifies/summarizes/omits/expands/reorders.,schema/contract,,polylogue-37t.1;polylogue-4p1,,polylogue/storage/sqlite/migrations/user/;polylogue/storage/sqlite/archive_tiers/user.py;polylogue/core/;polylogue/core/refs.py;polylogue/storage/repository/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/076_polylogue_arso.md,both=2;origin-only=1;symbolic=3,records-landed-work+records-residual-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:14:11Z,2026-07-13T04:14:11Z,area:query;area:storage;area:surface;delivery:E-variants-preferences;lane:variants-preferences;size:L;spine +polylogue-at44,3,open,open,bug,"user_settings table is dead: DDL + migration 004 exist, zero runtime read/write helpers",,substrate,polylogue-w8db,regression/repair-slice,,unspecified/qualitative,,polylogue-f2qv;polylogue-y4c,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:57:59Z,2026-07-13T04:57:59Z,area:substrate;delivery:E-variants-preferences;horizon:frontier;lane:variants-preferences;tech-tree +polylogue-avna,3,open,open,spike,Order-explicit row patterns with typed match sets,,analytics,polylogue-4p1,domain-implementation-slice,,receipt/provenance;bounded-resource;parity/completeness;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,.agent/scratch/dsl-pattern-matching-design-2026-07-13.md;docs/search.md,both=1;missing/planned=1,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:analytics;horizon:frontier +polylogue-avna.1,3,open,open,task,"Pattern-analysis grounding catalog: 16 analyses, tokens, tiers, soundness verdict",,analytics,polylogue-avna,domain-implementation-slice,,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;parity/completeness;determinism/idempotency;restart/crash;security/authorization;schema/contract;operator-gate;regression-fixture,,,,.agent/scratch/pattern-analysis-grounding-2026-07-13.md,missing/planned=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:analytics;horizon:frontier +polylogue-avna.2,3,open,open,task,Alphabet pack v1: action-semantics + failure-kind rule classifiers (PACK-A/B),,analytics,polylogue-avna,domain-implementation-slice,,live-evidence;receipt/provenance;bounded-resource;parity/completeness;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:analytics;horizon:frontier +polylogue-avna.3,3,open,open,task,"Alphabet packs C-E: interaction tokens, speech-acts bootstrap, outcome proxies",,analytics,polylogue-avna,domain-implementation-slice,"DEP: PACK-A/B (shared classifier registry), dve1.",receipt/provenance;parity/completeness;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T05:58:38Z,2026-07-13T05:58:38Z,area:analytics +polylogue-bby.13,3,open,open,feature,The day page: a daily narrative the operator actually reads,,surface;web,polylogue-bby,domain-implementation-slice,,live-evidence,,,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:58:57Z,2026-07-07T12:58:57Z,area:surface;area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-bby.14,3,open,open,feature,Pinboard: workspaces as a spatial surface,,web,polylogue-bby,surface/adaptation-slice,,live-evidence;restart/crash,,,,,no-explicit-paths,,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:57Z,2026-07-08T20:14:57Z,area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-bby.15,3,open,open,task,Verified cold-reader evidence export over findings and selected relations,,web,polylogue-bby,domain-implementation-slice,"The profile is an export shape, not a universal portable-",receipt/provenance;security/authorization,polylogue-4p1;polylogue-fnm.11;polylogue-rxdo.1;polylogue-rxdo.2;polylogue-rxdo.3;polylogue-rxdo.4;polylogue-rxdo.9.9;polylogue-svfj,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/059_polylogue_bby_15.md,origin-only=1,records-residual-work,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:web;delivery:H-web-cockpit;horizon:frontier;lane:web-evidence-cockpit;tech-tree +polylogue-bby.2,3,open,open,feature,Query completions + expression explain in the web search box,,query;web,polylogue-bby,domain-implementation-slice,"The web behavior for `polylogue-bby.2` is backed by the shared API contract, handles loading/stale/error states explicitly, and has a seeded visual or interaction smoke test.",live-evidence;schema/contract;regression-fixture,,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:57:38Z,2026-07-16T12:57:38Z,area:query;area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-bby.5,3,open,open,feature,Long-session navigation: phases/windows/minimap,,web,polylogue-bby,domain-implementation-slice,"The web behavior for `polylogue-bby.5` is backed by the shared API contract, handles loading/stale/error states explicitly, and has a seeded visual or interaction smoke test.",schema/contract;regression-fixture,,,,,no-explicit-paths,,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:59:54Z,2026-07-07T12:59:54Z,area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-bby.6,3,open,open,chore,Interaction debt: replace window.prompt(); de-drift the JS renderer,,web,polylogue-bby,surface/adaptation-slice,Renderer drift: the reader renders messages in hand-rolled JS (web_shell_reader.py:208-437) independent of polylogue/rendering/ — exported HTML and web view diverge forever unless one side becomes canonical: either server-rendered fragments or a shared template contract with a parity snapshot test…,parity/completeness;schema/contract;regression-fixture,,,,polylogue/rendering/,symbolic=1,,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:59:53Z,2026-07-07T12:59:53Z,area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-bby.8,3,open,open,feature,"Web reader perceived performance: virtualized list, streamed search, optimistic navigation",,perf;web,polylogue-bby,consolidation-owner,Invariant collapse 2026-07-15: absorbs the client half of nhjs.,bounded-resource;cancellation/cleanup;mutation/falsification;operator-gate,,polylogue-bby.11,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/143_polylogue_bby_8.md,origin-only=1,,perf,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T23:43:24Z,2026-07-14T23:43:24Z,area:perf;area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-bfv,3,open,open,feature,Advisory hooks: archive-informed PreToolUse/UserPromptSubmit responses,,context;ingest,polylogue-37t,canonical-authority-owner,,live-evidence;receipt/provenance;bounded-resource;operator-gate;regression-fixture,polylogue-20d.1;polylogue-20d.12;polylogue-37t.11.1;polylogue-37t.15;polylogue-d1y;polylogue-kwsb.1,polylogue-s7ae.3,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:02:12Z,2026-07-13T04:02:12Z,area:context;area:ingest;delivery:D-agent-context-coordination;delivery:ac-patched;lane:agent-coordination +polylogue-bkb2,3,open,open,task,Collapse ArchiveStore.open_existing boilerplate for ~35 facade thin-wrapper methods,,api,polylogue-1r9c,domain-implementation-slice,,unspecified/qualitative,,,,.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-codebase-structure-audit.md,origin-only=1,,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:api;discovered-from:polylogue-9e5.14;horizon:frontier +polylogue-bkzv,3,open,open,feature,Land the visual-semantics vocabulary and generated token kernel,,surface;web,polylogue-9xuk,domain-implementation-slice,It does not absorb transcript or CLI/TUI adoption.,receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract;regression-fixture,,polylogue-9e5.29;polylogue-cuxz;polylogue-rxdo.3,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:22:25Z,2026-07-15T19:22:25Z,area:surface;area:web;horizon:frontier +polylogue-bv1w,3,open,open,task,Scoped watched-query predicate fingerprints,,query,polylogue-20d,domain-implementation-slice,,receipt/provenance;bounded-resource;schema/contract,,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:06:46Z,2026-07-15T17:06:46Z,area:query +polylogue-ccma,3,open,open,task,"Full claim-by-claim docs-commands/version sweep, sequenced after polylogue-ttu",,docs,polylogue-hg8n,domain-implementation-slice,,live-evidence,polylogue-ttu,,,docs/*.md;devtools/verify_doc_commands.py,both=1;symbolic=1,records-landed-work,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:09Z,2026-07-09T19:47:09Z,area:docs;discovered-from:polylogue-6l6 +polylogue-cijx,3,open,open,spike,File/repo evidence: observed tree trajectories and graded reproduction,,insights;interop,polylogue-1vpm,domain-implementation-slice,Own the session-to-repository evidence program that superseded 7xv and 7xv.1.,live-evidence;receipt/provenance;bounded-resource;security/authorization;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:insights;area:interop;horizon:mid;tech-tree +polylogue-cinh,3,open,open,task,Terminal-grid rendering laws: width-degradation snapshots over the PTY harness,,cli;test,,domain-implementation-slice,,live-evidence,,,,tests/infra/pty_cli.py,both=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T18:51:53Z,2026-07-16T18:51:53Z,area:cli;area:test;horizon:frontier +polylogue-cpf,3,open,open,task,Reconcile doctrine text with shipped enforcement and owning contracts,,legibility;substrate,polylogue-b054,domain-implementation-slice,,receipt/provenance;schema/contract;regression-fixture,,,,docs/generated-reference,missing/planned=1,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:legibility;area:substrate;delivery:A-trust-floor;horizon:frontier;lane:agent-write-safety;spine;wave:2 +polylogue-dbiv,3,open,open,task,"CLI/TUI alignment with the shared aesthetic vocabulary (glyphs, provenance treatment, generated Textual theme)",,cli;surface,polylogue-9xuk,surface/adaptation-slice,,receipt/provenance;security/authorization;schema/contract,polylogue-bkzv,polylogue-jnj.3,,tests/unit/cli;tests/unit/ui,both=2,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:17:11Z,2026-07-15T19:17:11Z,area:cli;area:surface;horizon:frontier +polylogue-dve1,3,open,open,feature,Seed annotation ontologies + autonomous per-archive ontology bootstrap,,annotations,polylogue-37t,domain-implementation-slice,"- goal events: opened, blocked, resumed, declared-resolved, superseded, and explicitly-abandoned only when an actor actually declares it;",receipt/provenance;security/authorization;schema/contract;operator-gate;regression-fixture,,,,polylogue/annotations,both=1,records-residual-work,annotations,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:annotations;horizon:frontier +polylogue-dx1,3,open,open,task,Decision: daemon HTTP substrate — hand-rolled BaseHTTPRequestHandler vs ASGI,,daemon,polylogue-1r9c,surface/adaptation-slice,,live-evidence;bounded-resource;schema/contract;regression-fixture,,,,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T18:39:43Z,2026-07-08T18:39:43Z,area:daemon;decision;delivery:G-live-performance;delivery:ac-patched;lane:capture-reliability +polylogue-e5b5,3,open,open,task,Cheap pre-registered micro-evals: state-fact QA + status-claim staleness verification,,analytics;experiments,polylogue-rxdo,domain-implementation-slice,,live-evidence;bounded-resource;determinism/idempotency;security/authorization,,polylogue-cfk,,.agent/scratch/gpt-pro-demo-review-analysis.md,missing/planned=1,records-landed-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:38:30Z,2026-07-13T07:38:30Z,area:analytics;area:experiments +polylogue-ei94,3,open,open,task,Merge conductor with contention-class admission and completion receipts,,coordination;devloop,polylogue-s7ae,domain-implementation-slice,,live-evidence;receipt/provenance;bounded-resource;cancellation/cleanup;security/authorization,,polylogue-2yax;polylogue-e6ja;polylogue-p155;polylogue-s7ae.5;polylogue-wple,,.agent/scratch/merge-conductor-prompt.md;.agent/tools/fanout-launch.sh;docs/topology-status.md;docs/plans/topology-target.yaml;docs/cli-reference.md;tests/unit;.agent/tools/fanout_gen_prompts.py;.beads/*.jsonl;.beads/issues.jsonl;.beads/metadata.json,both=4;origin-only=4;missing/planned=1;symbolic=1,records-landed-work+records-residual-work,coordination,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:coordination;area:devloop;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-ekes,3,open,open,task,Fuzz lane: campaign freshness gate + dispatch and query-DSL targets + README drift fix,,devtools;test,polylogue-88jp,domain-implementation-slice,,parity/completeness;mutation/falsification;schema/contract,,,,tests/fuzz/;tests/fuzz/README.md;devtools/verify_mutation_freshness.py;docs/plans/campaign-coverage.yaml;tests/unit/sources/test_fuzz_targets_executable.py;tests/fuzz,both=5;symbolic=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T17:31:10Z,2026-07-08T17:31:10Z,area:devtools;area:test;horizon:frontier +polylogue-f3kd,3,open,open,task,"Model delegation chains, retries, and evidence-backed parent follow-up",,analytics;delegations;lineage,polylogue-1vpm,domain-implementation-slice,,schema/contract;regression-fixture,polylogue-lph4;polylogue-y964,polylogue-1vpm.1,,,no-explicit-paths,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:analytics;area:delegations;area:lineage;delivery:I-analytics-experiments;horizon:frontier +polylogue-fcyf,3,open,open,feature,Fleet observatory: archive-backed live view of multi-agent fanouts,,coordination,polylogue-s7ae,domain-implementation-slice,"CLI, MCP, and cockpit consume one read contract, and the feature performs no orchestration mutations.",live-evidence;receipt/provenance;parity/completeness;security/authorization;mutation/falsification;schema/contract;operator-gate,,,,,no-explicit-paths,,coordination,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:37:56Z,2026-07-13T07:37:56Z,area:coordination;horizon:frontier +polylogue-fgmk,3,open,open,task,Re-score mock-depth ranking excluding polylogue.paths.* test-isolation idiom,,test,polylogue-88jp,domain-implementation-slice,,unspecified/qualitative,,polylogue-9e5.21,,.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-test-suite-meta-health.md,origin-only=1,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:37:57Z,2026-07-13T07:37:57Z,area:test;discovered-from:polylogue-9e5.21 +polylogue-fie,3,open,open,task,"Decision: archive scaling doctrine — keep everything, optimize the ceilings",,storage,polylogue-1xc,consolidation-owner,2026-07-15 portfolio convergence: absorbs the storage-footprint/dbstat half of polylogue-20d.7.,live-evidence;bounded-resource;restart/crash;security/authorization;schema/contract;operator-gate,,,polylogue-20d.7,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,records-residual-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:storage;decision;delivery:B-storage-rebuild-bytes;delivery:ac-patched;horizon:frontier;lane:blob-integrity +polylogue-fkn5,3,open,open,bug,DaemonConverger.summary() double-counts converged-after-failure files,,unlabeled,,regression/repair-slice,,unspecified/qualitative,,,,,no-explicit-paths,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-17T00:45:09Z,2026-07-17T00:45:09Z, +polylogue-fnm,3,open,open,epic,Query DSL: one grammar owns query semantics; compose instead of multiplying verbs,,query,,program/invariant-container,Coordinates with t46 (the DSL becomes the sole owner of query semantics).,schema/contract,,polylogue-38x,,polylogue/archive/query/expression.py;docs/schemas;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/150_polylogue_fnm.md;.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md,both=2;origin-only=2,records-landed-work,query,retain program container; do not execute as a leaf,2026-07-15T19:38:13Z,2026-07-15T19:38:13Z,area:query;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts +polylogue-fnm.1,3,open,open,feature,Aggregates beyond count (sum/avg/min/max/percentiles),,query,polylogue-fnm,domain-implementation-slice,,live-evidence;security/authorization;schema/contract;regression-fixture,polylogue-fnm.11,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/050_polylogue_fnm_1.md,origin-only=1,records-landed-work+records-residual-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.12,3,open,open,feature,"User-defined query macros: named, composable DSL shorthands in user.db",,query;surface,polylogue-fnm,domain-implementation-slice,,live-evidence;receipt/provenance;schema/contract;operator-gate,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/152_polylogue_fnm_12.md,origin-only=1,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:query;area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.13,3,open,open,feature,Set-algebra over query results: union/intersect/except between queries,,query;surface,polylogue-fnm,domain-implementation-slice,,bounded-resource;parity/completeness;schema/contract;operator-gate,,polylogue-fnm.9,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/052_polylogue_fnm_13.md,origin-only=1,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:query;area:surface;delivery:C-read-evidence-contract;design-ready;horizon:frontier;lane:read-contracts;spine +polylogue-fnm.2,3,open,open,feature,Projection predicates/windows + render/layout stages on attached units,,query,polylogue-fnm,surface/adaptation-slice,,schema/contract,,,,.agent/includes/fables-poly-findings.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/154_polylogue_fnm_2.md,origin-only=1;missing/planned=1,records-landed-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.4,3,open,open,feature,Shell completion + fuzzy selection as read-only projections of the grammar registries,,cli;query,polylogue-fnm,surface/adaptation-slice,,bounded-resource;parity/completeness;schema/contract;operator-gate,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/155_polylogue_fnm_4.md;tests/unit/cli/test_completion_matrix.py,both=1;origin-only=1,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T18:51:58Z,2026-07-16T18:51:58Z,area:cli;area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.6,3,open,open,feature,Wire the terminal stage to projections: | read / | context-image,,query,polylogue-fnm,surface/adaptation-slice,,schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/156_polylogue_fnm_6.md,origin-only=1,records-landed-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:query;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts +polylogue-fnm.7,3,open,open,feature,Generalized child-count predicates: count(unit where ...) comparisons,,query,polylogue-fnm,domain-implementation-slice,,parity/completeness;schema/contract;regression-fixture,,polylogue-fnm.1,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:32Z,2026-07-15T20:07:32Z,area:query;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts +polylogue-fnm.9,3,open,open,feature,Pipeline-as-subquery composition,,query,polylogue-fnm,domain-implementation-slice,,receipt/provenance,polylogue-fnm.1;polylogue-fnm.7,,,,no-explicit-paths,,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:57Z,2026-07-08T20:14:57Z,area:query;delivery:C-read-evidence-contract;horizon:vision;lane:read-contracts +polylogue-fs1,3,open,open,epic,Hermes bridge: state.db + runtime spans -> canonical evidence -> forensics/eval export,,ingest;substrate,,program/invariant-container,,receipt/provenance;bounded-resource;security/authorization;schema/contract,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/188_polylogue_fs1.md,origin-only=1,,ingest,retain program container; do not execute as a leaf,2026-07-15T19:37:35Z,2026-07-15T19:37:35Z,area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-fs1.11,3,open,open,feature,Hermes read-only recall and effective-context audit loop,,context;ingest;security;substrate,polylogue-fs1,domain-implementation-slice,Only the declared MCP tools are registered for the Hermes read role; an explicit prior-session recall works end to end; automatic recall fires only at declared moments and obeys measured latency/token budgets; the exact delivered bytes hash to a persisted context-delivery manifest with cutoff and i…,receipt/provenance;bounded-resource;security/authorization;schema/contract;regression-fixture,polylogue-37t.11.1;polylogue-fs1.7,polylogue-fs1.6;polylogue-x35k,,polylogue/api/archive.py;tests/unit/context/test_hermes_delivery_correlation.py;tests/unit/api/test_facade_contracts.py,both=3,records-landed-work+records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:context;area:ingest;area:security;area:substrate;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-fs1.12,3,open,open,task,Hermes evidence-and-continuity integration demo,,context;demos;evidence;ingest;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;bounded-resource;regression-fixture,polylogue-fs1.1;polylogue-fs1.11;polylogue-fs1.3;polylogue-fs1.4,,,,no-explicit-paths,records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:context;area:demos;area:evidence;area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-fs1.13,3,open,open,feature,Evaluate Hermes memory and skill revisions against later evidence,,analysis;context;evidence;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;bounded-resource;security/authorization;mutation/falsification,polylogue-37t.10;polylogue-7aw;polylogue-fs1.11;polylogue-fs1.4,polylogue-fs1.5,,,no-explicit-paths,records-landed-work+records-residual-work,analysis,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:analysis;area:context;area:evidence;area:substrate;horizon:mid +polylogue-h75b,3,open,open,task,Land a devtools lane wrapping the vulture+coverage+affordance dead-code intersection,,devtools,polylogue-1r9c,domain-implementation-slice,,unspecified/qualitative,,,,.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-codebase-structure-audit.md,origin-only=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:25:52Z,2026-07-09T19:25:52Z,area:devtools;discovered-from:polylogue-9e5.15 +polylogue-h8fr,3,open,open,task,Add scheduled (not per-PR) atheris fuzz-campaign CI workflow,,ci,polylogue-88jp,domain-implementation-slice,,bounded-resource;restart/crash;regression-fixture,polylogue-kj22,,,tests/fuzz;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-test-suite-meta-health.md;.github/workflows/fuzz-nightly.yml,both=1;origin-only=1;missing/planned=1,,ci,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:20:34Z,2026-07-09T19:20:34Z,area:ci;discovered-from:polylogue-9e5.18 +polylogue-iec,3,open,open,task,Schema optimization audit: storage shape earns its bytes and its reads,,audit;perf;storage,polylogue-a7xr,domain-implementation-slice,,live-evidence;bounded-resource;security/authorization;schema/contract,polylogue-20d.7,,,,no-explicit-paths,records-residual-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:48Z,2026-07-08T20:14:48Z,area:audit;area:perf;area:storage;delivery:B-storage-rebuild-bytes;lane:blob-integrity +polylogue-ixqt,3,open,open,task,Review polylogue/surfaces test suite for mechanical over-coverage,,test,polylogue-88jp,surface/adaptation-slice,"Build a candidate equivalence report for tests/unit/surfaces, then consolidate one proven cluster into parametrized/property coverage while preserving distinct contract cases.",mutation/falsification;schema/contract,,polylogue-9e5.11,,polylogue/surfaces;tests/unit/surfaces;tests/unit/surfaces/*.py;tests/property/;tests/unit/archive/,both=2;symbolic=3,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:test;horizon:mid +polylogue-jlme,3,open,open,epic,"Capture extension: reliability, coverage, and in-page presence",,ingest,,program/invariant-container,2026-07-15 portfolio correction: s8gb is the remaining live operational proof for bounded oversized capture/resume and belongs to capture reliability.,parity/completeness;security/authorization,,,,docs/design/browser-capture-redesign/,symbolic=1,records-residual-work,ingest,retain program container; do not execute as a leaf,2026-07-15T19:37:36Z,2026-07-15T19:37:36Z,area:ingest;delivery:G-live-performance;horizon:mid;lane:capture-reliability;spine +polylogue-jnj,3,open,open,epic,Product surface algebra: one rule per concern across CLI/config/onboarding,,cli,,program/invariant-container,Absorbs gh#2317 (root onboarding/facets) and gh#2309 (config intentionality).,parity/completeness;schema/contract;regression-fixture,,polylogue-38x,,docs/cli-reference.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07-design-reports/mcp-surface-collapse-design.report.md,both=1;origin-only=1,,cli,retain program container; do not execute as a leaf,2026-07-07T22:22:21Z,2026-07-07T22:22:21Z,area:cli;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:mid;lane:read-contracts;refactor +polylogue-jnj.11,3,open,open,task,Extend the fzf pattern from select to ambiguous-result moments,,cli,polylogue-jnj,domain-implementation-slice,,parity/completeness;schema/contract;regression-fixture,,,,,no-explicit-paths,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:cli;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.13,3,open,open,task,Bare-invocation triage: status + five most recent sessions with one-key open,,cli,polylogue-jnj,domain-implementation-slice,,live-evidence,,,,,no-explicit-paths,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:cli;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.3,3,open,open,task,Output dialect normalization (--format/--json + --to/--out),,cli,polylogue-jnj,domain-implementation-slice,,schema/contract,,,,polylogue/cli/query_output.py;polylogue/cli/shared/formatting.py,both=2,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:cli;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-jnj.6,3,open,open,task,Demo/import surface separation (import --demo -> polylogue demo),,cli,polylogue-jnj,surface/adaptation-slice,,schema/contract,,,,docs/cli-reference.md,both=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:cli;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-k7m7,3,open,open,task,Delete the ~30-symbol dead-code kill-list (9e5.15 execution half),,cleanup,polylogue-1r9c,domain-implementation-slice,,unspecified/qualitative,,,,.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-codebase-structure-audit.md,origin-only=1,,cleanup,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:cleanup;discovered-from:polylogue-9e5.15;horizon:frontier +polylogue-kj0u,3,open,open,task,Fix false dispatch.py comment claiming claude-code-session detection is Pydantic-validated,,sources,polylogue-2qx,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:29:30Z,2026-07-09T19:29:30Z,area:sources;discovered-from:polylogue-9e5.12 +polylogue-kph,3,open,open,feature,Provenance-carrying PRs: attach the authoring session's postmortem bundle,,devloop;substrate,polylogue-s7ae,domain-implementation-slice,,receipt/provenance;regression-fixture,,,,,no-explicit-paths,,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:devloop;area:substrate;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-kzld,3,open,open,task,"Delete 3 zero/tests-only-consumer facade methods, review 2 borderline",,api,polylogue-1r9c,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:25:51Z,2026-07-09T19:25:51Z,area:api;discovered-from:polylogue-9e5.14 +polylogue-l4kf,3,open,open,epic,"Ecosystem interop + origin breadth: more sources in, two-way citable export out",,ingest,,program/invariant-container,Epic closes when the operator's live surface list has zero uncaptured origins and at least one external tool consumes a Polylogue export by contract.,cancellation/cleanup;parity/completeness;schema/contract;regression-fixture,,,,,no-explicit-paths,,ingest,retain program container; do not execute as a leaf,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:ingest;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export;spine +polylogue-l8ee,3,open,open,bug,devtools: catalog-bypass entry points undermine catalog completeness,,devtools,,regression/repair-slice,,parity/completeness;schema/contract,,polylogue-o21;polylogue-utf,,.github/workflows/mutation-testing.yml;devtools/benchmark_compare_nightly.py;.github/workflows/nightly-scale.yml;devtools/pre_push_gate.py,both=4,records-residual-work,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:20:46Z,2026-07-16T10:20:46Z,area:devtools;discovered-from:dogfood-2 +polylogue-lio,3,open,open,task,Align cross-repo devloop contract on beads (Sinex parity),,devloop,polylogue-s7ae,domain-implementation-slice,"Update the shared contract in BOTH repos: operator directives = P0/P1 beads (bd human for operator decisions), contract-drift check extended to beads presence.",security/authorization;schema/contract;operator-gate,,,,,no-explicit-paths,,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:devloop;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-substrate +polylogue-lqxx,3,open,open,task,Zip decoder has no aggregate-uncompressed-size cap (only per-entry),,parsers;security,polylogue-kwsb,domain-implementation-slice,,unspecified/qualitative,,polylogue-38x,,polylogue/sources/decoder_zip.py,both=1,,parsers,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:parsers;area:security;horizon:frontier +polylogue-lyv4,3,open,open,bug,rebuild_session_insights_async targeted branch does an unscoped archive-wide wipe instead of matching its sync twins scoped refresh,,insights,polylogue-a7xr,regression/repair-slice,,cancellation/cleanup;parity/completeness;regression-fixture,,,,tests/unit/storage/test_session_insight_refresh.py,both=1,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:03:10Z,2026-07-16T11:03:10Z,area:insights;discovered-from:dogfood-2 +polylogue-mhx,3,open,open,epic,"Embedding substrate: provider-general, honest lifecycle, retrieval that earns its cost",,embeddings;substrate,,program/invariant-container,,bounded-resource;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/186_polylogue_mhx.md,origin-only=1,records-residual-work,embeddings,retain program container; do not execute as a leaf,2026-07-15T19:37:36Z,2026-07-15T19:37:36Z,area:embeddings;area:substrate;delivery:J-embeddings-retrieval;horizon:mid;lane:embeddings-retrieval +polylogue-myhg,3,open,open,task,Extract shared DaemonAPIHandler mock scaffolding (_MockServer/_MockHeaders/_make_handler) into tests/infra,,test,polylogue-88jp,domain-implementation-slice,,schema/contract,,,,tests/infra;tests/unit/daemon/test_daemon_http_security.py;tests/infra/;tests/unit/daemon,both=3;symbolic=1,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:44Z,2026-07-15T17:09:44Z,area:test +polylogue-nu4t,3,in_progress,in_progress,task,Flip Tier-C origin-scoped internal lookups off Provider round-trip,Sinity,substrate,polylogue-2qx,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T06:32:55Z,2026-07-16T06:32:55Z,area:substrate;discovered-from:polylogue-9e5.8 +polylogue-o21.2,3,open,open,feature,Generate extension scaffolds and actionable registration validation,,devtools;dx,polylogue-o21,canonical-authority-owner,One non-MCP family uses the same scaffold/validator protocol with domain-specific templates.,determinism/idempotency;security/authorization;schema/contract;regression-fixture,polylogue-o21.1,,,,no-explicit-paths,records-residual-work,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:devtools;area:dx;horizon:mid +polylogue-o21.3,3,open,open,task,"Adopt DeclarationSpec across source, query, marker, and maintenance families",,architecture;devtools;verification,polylogue-o21,domain-implementation-slice,,live-evidence;parity/completeness;security/authorization;schema/contract;regression-fixture,polylogue-o21.1;polylogue-o21.2,polylogue-9e5.31,,,no-explicit-paths,,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:architecture;area:devtools;area:verification;horizon:mid +polylogue-occ5,3,open,open,task,"CLI post-query interaction design: per-verb follow-through, next-action affordances, result-set handles",,cli;surface,polylogue-jnj,surface/adaptation-slice,,security/authorization;operator-gate,,polylogue-4p1;polylogue-jnj.11;polylogue-rsad;polylogue-rxdo.3;polylogue-rxdo.6;polylogue-tjx1,,.agent/reports,origin-only=1,records-landed-work+records-residual-work,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:cli;area:surface;horizon:frontier +polylogue-okpn,3,open,open,task,devtools lint: new hashlib/content_hash call sites must register in the hash-boundary census,,audit;storage,polylogue-o21,domain-implementation-slice,,receipt/provenance;security/authorization;schema/contract;regression-fixture,,polylogue-9e5.6,,docs/audits/2026-07-09-hash-boundary-census.md;devtools/verify_docs_drift.py,both=2,records-residual-work,audit,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:34Z,2026-07-15T17:09:34Z,area:audit;area:storage +polylogue-ox0.2,3,open,open,feature,Capture versioned Codex AppServer events as source evidence,,daemon;ingest;sources,polylogue-ox0,domain-implementation-slice,,live-evidence;parity/completeness;security/authorization;regression-fixture,polylogue-ox0.1,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:daemon;area:ingest;area:sources;horizon:mid +polylogue-ox0.3,3,open,open,feature,Expose authorized Codex AppServer start and resume control,,context;control;security,polylogue-ox0,domain-implementation-slice,,receipt/provenance;determinism/idempotency;security/authorization;mutation/falsification,polylogue-kwsb.2;polylogue-ox0.2,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:51:33Z,2026-07-15T19:51:33Z,area:context;area:control;area:security;horizon:mid +polylogue-p155,3,open,open,chore,Lint: detect numbered-migration slot collisions at verify time,,ops,polylogue-60i5,domain-implementation-slice,,schema/contract;regression-fixture,,,,,no-explicit-paths,,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:ops;horizon:frontier +polylogue-p8d5,3,open,open,task,Docs theming pass: apply polylogue/ui/theme.py tokens to the docs site (currently drifted),,docs,polylogue-hg8n,domain-implementation-slice,,security/authorization,,,,polylogue/ui/theme.py;devtools/pages_style.py,both=2,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:32:11Z,2026-07-09T19:32:11Z,area:docs;discovered-from:polylogue-6l6 +polylogue-pb0j,3,open,open,epic,A2A bridge: Agent Cards + task streams -> canonical evidence -> optional Polylogue A2A server/client,,ingest;interop,polylogue-l4kf,program/invariant-container,,receipt/provenance;security/authorization;schema/contract;regression-fixture,,polylogue-37t;polylogue-fs1,,,no-explicit-paths,records-landed-work,ingest,retain program container; do not execute as a leaf,2026-07-15T19:56:04Z,2026-07-15T19:56:04Z,area:ingest;area:interop;horizon:mid +polylogue-q22b,3,open,open,task,Post-unlock: check regenerated v0.3.0 release PR against the ame5 decision,,release,,domain-implementation-slice,,security/authorization,,,,,no-explicit-paths,records-landed-work+records-residual-work,release,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T19:32:40Z,2026-07-16T19:32:40Z,area:release;horizon:mid +polylogue-qa7b,3,open,open,task,Visual-freshness lint for docs screenshots/GIFs (3tl.9 AC#2 split),,docs,polylogue-hg8n,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:07Z,2026-07-09T19:47:07Z,area:docs;discovered-from:polylogue-3tl.9 +polylogue-qauw,3,open,open,task,Add Windows/WSL2 install story to docs/installation.md,,docs,polylogue-hg8n,domain-implementation-slice,,unspecified/qualitative,,,,docs/installation.md,both=1,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:04Z,2026-07-09T19:47:04Z,area:docs;discovered-from:polylogue-3tl.7 +polylogue-qzv9,3,open,open,task,Commit session_runs.status x terminal_state cross-tab as a re-runnable devtools script (9e5.9 rescoped first slice),,insights,polylogue-1vpm,domain-implementation-slice,"Extract the one-query cross-tab from .agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-substrate-honesty-audit.md section 9e5.9 into a small committed devtools script producing a standing, re-runnable accuracy signal for the terminal_state/error axis specifically.",unspecified/qualitative,,,,.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-substrate-honesty-audit.md,origin-only=1,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:29:29Z,2026-07-09T19:29:29Z,area:insights;discovered-from:polylogue-9e5.9 +polylogue-redt,3,open,open,task,"Storage read tier (archive_tiers/archive.py, 11.3k lines): helper-dependency graph before extraction",,architecture;storage,polylogue-1r9c,domain-implementation-slice,,parity/completeness;security/authorization;schema/contract,,,,docs/architecture-hotspots.md,both=1,,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:15Z,2026-07-15T17:09:15Z,area:architecture;area:storage;horizon:vision;refactor +polylogue-rii,3,open,open,epic,Live substrate intake: agents write work-events; evidence materializes in-loop,,substrate,,program/invariant-container,,live-evidence;security/authorization;operator-gate,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/164_polylogue_rii.md,origin-only=1,,substrate,retain program container; do not execute as a leaf,2026-07-15T19:38:14Z,2026-07-15T19:38:14Z,area:substrate;delivery:D-agent-context-coordination;horizon:mid;lane:context-memory +polylogue-rvh,3,open,open,task,Lesson reinforcement scheduling: judged memory on a forgetting curve,,context,polylogue-37t,domain-implementation-slice,,regression-fixture,polylogue-mhx.4,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T12:59:03Z,2026-07-07T12:59:03Z,area:context;delivery:D-agent-context-coordination;lane:context-memory +polylogue-rxdo.10,3,open,open,task,Archive-intelligence demo catalog: AI-D1 through AI-D9,,substrate,polylogue-rxdo,domain-implementation-slice,"One program, one implementation.",receipt/provenance;restart/crash;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,.agent/scratch/archive-intelligence-design-2026-07-13.md,missing/planned=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.10.1,3,open,open,feature,Archive demo AI-D1: frame-aware convergent questions,,substrate,polylogue-rxdo.10,canonical-authority-owner,This child uses the AI-D* archive-intelligence namespace; PF-D* belongs to polylogue-212.,receipt/provenance;parity/completeness;security/authorization;regression-fixture,polylogue-mhx.2,polylogue-rxdo.9.11,,,no-explicit-paths,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.10.2,3,open,open,feature,Archive demo AI-D3: prior observed recovery candidates with measured precision,,substrate,polylogue-rxdo.10,domain-implementation-slice,This child uses the AI-D* archive-intelligence namespace; PF-D* belongs to polylogue-212.,receipt/provenance;restart/crash;operator-gate;regression-fixture,polylogue-avna.2;polylogue-mhx.2;polylogue-rxdo.9.4,polylogue-mhx.3,,,no-explicit-paths,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.10.3,3,open,open,feature,Archive demo AI-D9: repeated-context mass and cost with causal savings separated,,substrate,polylogue-rxdo.10,domain-implementation-slice,This child uses the AI-D* archive-intelligence namespace; PF-D* belongs to polylogue-212.,receipt/provenance;security/authorization,polylogue-mhx.2,polylogue-f2qv;polylogue-lph4;polylogue-stc,,,no-explicit-paths,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.11,3,open,open,feature,Improvement-loop registry with two governed operational pilots,,substrate,polylogue-rxdo,canonical-authority-owner,until the shared operational contract is proven.,live-evidence;receipt/provenance;bounded-resource;parity/completeness;security/authorization;schema/contract;operator-gate,,polylogue-37t.17,,.agent/scratch/closed-loops-design-2026-07-13.md;polylogue/insights/improvement_loops.py;tests/unit/insights/test_improvement_loops.py,both=2;missing/planned=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.5,3,open,open,task,StandingQueryStage: watched queries re-evaluated on convergence; deltas become candidate findings,,daemon;substrate,polylogue-rxdo,domain-implementation-slice,,mutation/falsification;regression-fixture,polylogue-37t.15;polylogue-rxdo.4,polylogue-rxdo.2,,polylogue/daemon/convergence_stages.py;tests/unit/daemon/test_standing_queries_default_evaluator.py;polylogue/daemon/cli.py;tests/unit/daemon/test_standing_queries.py;tests/unit/daemon/test_convergence_stages.py;tests/unit/daemon/test_convergence_final_state.py;polylogue/archive/query/production_evaluator.py;tests/unit/archive/query/test_production_evaluator.py,both=8,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T15:33:19Z,2026-07-14T15:33:19Z,area:daemon;area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.6,3,open,open,task,DSL reference operands: from query:/result-set:/cohort: as provenance-preserving AST nodes,,query-dsl;substrate,polylogue-rxdo,domain-implementation-slice,,bounded-resource,polylogue-fnm.13,polylogue-rxdo.2,,polylogue/archive/query/evaluator.py;tests/unit/archive/query/test_evaluator.py;polylogue/cli/*.py;polylogue/mcp/*.py;polylogue/daemon/*.py;polylogue/cli/archive_query.py,both=3;symbolic=3,records-landed-work+records-residual-work,query-dsl,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T15:16:51Z,2026-07-14T15:16:51Z,area:query-dsl;area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.8,3,open,open,task,Analysis recipes as DB-native runtime objects; YAML as import/export serialization only,,substrate,polylogue-rxdo,domain-implementation-slice,"Corpus-reviewed decision (defended against two runner-ups): recipes/runs must be DB objects because complex analyses are interactive DAGs, not static phase lists — the durable truth is what actually ran (which queries, which batches, which model, what got superseded), which YAML cannot record and a…",schema/contract,polylogue-rxdo.3;polylogue-rxdo.7,polylogue-60i5,,,no-explicit-paths,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T15:17:14Z,2026-07-14T15:17:14Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9,3,open,open,spike,"Analysis rigor program: frame-exact validity, judgments, and experiments",,substrate,polylogue-rxdo,domain-implementation-slice,superseded.,receipt/provenance;schema/contract;operator-gate,,,,.agent/scratch/rigor-mechanisms-proposal-2026-07-13.md;docs/design/analysis-rigor.md;polylogue/insights/measurement/registration.py,both=2;missing/planned=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.1,3,open,open,feature,metric: — content-addressed metric definitions,,substrate,polylogue-rxdo.9,canonical-authority-owner,This bead is the sole canonical identity/schema owner for metric:.,receipt/provenance;parity/completeness;schema/contract,,polylogue-9l5.7,,docs/design/analysis-rigor.md;polylogue/insights/measurement/canon.py;tests/unit/insights/measurement/test_canon.py;tests/unit/insights/measurement/test_metric.py,both=4,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.10,3,open,open,feature,Experiment analysis projection over stc definitions and cohort relations,,substrate,polylogue-rxdo.9,consolidation-owner,Experiment-record consolidation 2026-07-15: absorbs polylogue-wnse.,receipt/provenance;security/authorization;regression-fixture,,polylogue-stc,,tests/unit/insights/judgment/test_experiments.py,both=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:56:03Z,2026-07-15T19:56:03Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.12,3,open,open,feature,Judges as actors (human or agent) with measured calibration,,substrate,polylogue-rxdo.9,domain-implementation-slice,,schema/contract,polylogue-h6r,,,tests/unit/insights/judgment/test_calibration.py,both=1,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.4,3,open,open,feature,Holdout cohorts: persistence class + planner enforcement,,substrate,polylogue-rxdo.9,domain-implementation-slice,,receipt/provenance;security/authorization;regression-fixture,,,,polylogue/storage/sqlite/holdout_cohorts.py;tests/unit/storage/test_holdout_cohorts.py;tests/unit/storage/test_durable_migrations.py,both=3,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.6,3,open,open,feature,Wire blinded judgment into live judgment surfaces,,substrate,polylogue-rxdo.9,canonical-authority-owner,Keep blinding as a projection policy over the canonical 37t.12 transaction.,receipt/provenance;security/authorization;mutation/falsification,polylogue-37t.12,polylogue-7ome,,polylogue/insights/judgment/blinding.py;tests/unit/insights/judgment/test_blinding.py,both=2,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:14:19Z,2026-07-16T17:14:19Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.7,3,open,open,feature,Render paired negative controls on live findings,,substrate,polylogue-rxdo.9,canonical-authority-owner,"Controls are declared comparison definitions and evidence refs on the canonical finding/judgment transaction, not ad-hoc baselines or a parallel result model.",live-evidence;receipt/provenance;security/authorization;mutation/falsification;regression-fixture,polylogue-37t.12,polylogue-7ome,,tests/unit/insights/judgment/test_controls.py,both=1,records-landed-work+records-residual-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T17:14:22Z,2026-07-16T17:14:22Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-rxdo.9.8,3,open,open,feature,Bootstrap CIs for sampled results only; exactness propagation everywhere,,substrate,polylogue-rxdo.9,domain-implementation-slice,,schema/contract,polylogue-rxdo.3,,,polylogue/insights/measurement/uncertainty.py;tests/unit/insights/measurement/test_uncertainty.py,both=2,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:33:03Z,2026-07-14T14:33:03Z,area:substrate;delivery:C-read-evidence-contract;horizon:mid;lane:read-contracts;tech-tree +polylogue-s7ae,3,open,open,epic,Agent coordination substrate: evidence-backed multi-agent work without tracker lock-in,,context;coordination;mcp,,program/invariant-container,,live-evidence;receipt/provenance;bounded-resource;schema/contract,,polylogue-37t.11;polylogue-3tl.18;polylogue-t8t,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/160_polylogue_s7ae.md,origin-only=1,records-residual-work,context,retain program container; do not execute as a leaf,2026-07-16T12:57:39Z,2026-07-16T12:57:39Z,area:context;area:coordination;area:mcp;delivery:D-agent-context-coordination;horizon:mid;lane:agent-coordination;size:L;spine +polylogue-s7ae.2,3,open,open,task,Pre-deployment MCP and hook coordination batch,,coordination;hooks;mcp;ops,polylogue-s7ae,surface/adaptation-slice,"Audit and implement the deployment-sensitive pieces in one pass: Polylogue MCP tools/prompts for coordination views, server tool contract registration, CLI/openapi/generated schema updates, Sinnix MCP registry implications if needed, Beads git hook health detection/reporting, and hook-mediated coor…",parity/completeness;schema/contract,polylogue-kwsb.1;polylogue-pj8,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/065_polylogue_s7ae_2.md,origin-only=1,records-landed-work+records-residual-work,coordination,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:43Z,2026-07-08T20:14:43Z,area:coordination;area:hooks;area:mcp;area:ops;delivery:D-agent-context-coordination;lane:agent-coordination;size:M;spine;wave:2 +polylogue-s7ae.5,3,open,open,task,"Live proof: two agents, separate worktrees, one repo — overlap, message, context, handoff",,context;coordination;mcp,polylogue-s7ae,proof/verification-slice,"RE-SCOPE 2026-07-13: the planned two-agent live proof is superseded by reality — a 30-lane fanout ran overnight (coordinated via bd + worktrees + a merge conductor) with measured failure modes: embedded-Dolt lock convoy (fixed: dsfr server mode), worktree-removal races, OOM scopes, prompt-quality e…",live-evidence;receipt/provenance;determinism/idempotency;operator-gate;regression-fixture,polylogue-37t.11.2;polylogue-37t.15;polylogue-d1y;polylogue-kwsb.1;polylogue-pj8;polylogue-s7ae.3;polylogue-s7ae.7,,,.agent/conductor-devloop;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/067_polylogue_s7ae_5.md,origin-only=1;missing/planned=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:01:25Z,2026-07-13T04:01:25Z,area:context;area:coordination;area:mcp;delivery:D-agent-context-coordination;lane:agent-coordination;size:L;spine +polylogue-scd,3,open,open,feature,Cross-surface handoff: polylogue open + copy-as-command everywhere,,surface;web,polylogue-jnj,surface/adaptation-slice,,parity/completeness;schema/contract;operator-gate;regression-fixture,,polylogue-bby.11,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:surface;area:web;delivery:C-read-evidence-contract;delivery:ac-patched;horizon:frontier;lane:read-contracts +polylogue-stc,3,open,open,feature,Experiment definitions as typed assertions with preregistered lifecycle,,analytics;context;substrate,polylogue-9l5,domain-implementation-slice,,receipt/provenance;bounded-resource;security/authorization;schema/contract;operator-gate;regression-fixture,polylogue-rxdo.2;polylogue-rxdo.3;polylogue-rxdo.9.1,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/118_polylogue_stc.md;.agent/demos,origin-only=2,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:analytics;area:context;area:substrate;delivery:I-analytics-experiments;horizon:frontier;lane:analytics-experiments;spine +polylogue-stzx,3,open,open,feature,Schema-fuzz the daemon HTTP surface against rendered OpenAPI (schemathesis lab lane),,daemon;test,polylogue-88jp,surface/adaptation-slice,,security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,polylogue-yeq,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T17:31:06Z,2026-07-08T17:31:06Z,area:daemon;area:test;horizon:frontier +polylogue-t46.1,3,open,open,task,Replace showcase QA with demo-driven CLI and visual tests,,test,polylogue-t46,surface/adaptation-slice,,bounded-resource;parity/completeness,,,,,no-explicit-paths,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:test;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-t46.7,3,open,open,task,Move compose_context_preamble git enrichment into context/preamble.py,,surface,polylogue-t46,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:36Z,2026-07-15T20:07:36Z,area:surface;delivery:C-read-evidence-contract;horizon:frontier;lane:read-contracts;refactor +polylogue-u5dw,3,open,open,task,"Storage repair (repair.py, 5.6k lines): resolve helper coupling + placement doctrine before extraction",,architecture;storage,polylogue-1r9c,domain-implementation-slice,"Three internal blocks (quarantined-accepted-raw repair ~1350 lines, browser-capture-origin repair ~2090 lines, raw-materialization-replay ~730 lines) share a pool of generic helpers (_open_archive_index_connection, _resolve_convergence_debt, _session_insight_* family) that don't belong to any one b…",receipt/provenance;parity/completeness;security/authorization;schema/contract,,,,docs/architecture-hotspots.md,both=1,,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T18:49:17Z,2026-07-16T18:49:17Z,area:architecture;area:storage;horizon:vision;refactor +polylogue-uh6c,3,open,open,spike,"Tags redesign: namespaces with separate membership, affinity, and confidence axes",,annotations,polylogue-1vpm,canonical-authority-owner,Agent-authored asserted membership is always a candidate with inject:false and flows through the canonical 37t.12 judgment transaction; operator-authored membership may use the declared direct-authority path.,receipt/provenance;operator-gate;regression-fixture,,,,,no-explicit-paths,,annotations,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:33Z,2026-07-15T20:07:33Z,area:annotations;horizon:frontier +polylogue-utf.1,3,open,open,task,devtools: workspace command group has 50% orphan rate,,devtools,polylogue-utf,domain-implementation-slice,,unspecified/qualitative,,,,devtools/command_catalog.py;.agent/demos/,both=1;symbolic=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:20:44Z,2026-07-16T10:20:44Z,area:devtools;delivery:M-substrate-consolidation;delivery:ac-patched;discovered-from:dogfood-2;lane:substrate-consolidation;wave:2 +polylogue-uwlu,3,open,open,task,Investigate Hermes-origin absence from claim-vs-evidence by_model rows,,insights,polylogue-1vpm,domain-implementation-slice,,determinism/idempotency,polylogue-fs1.1,,,,no-explicit-paths,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:02Z,2026-07-09T19:47:02Z,area:insights;discovered-from:polylogue-3tl.3 +polylogue-v22p,3,open,open,task,Write SECURITY.md stub pointing at docs/security.md + docs/daemon-threat-model.md,,docs,polylogue-hg8n,domain-implementation-slice,,unspecified/qualitative,,,,docs/security.md;docs/daemon-threat-model.md,both=2,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:05Z,2026-07-09T19:47:05Z,area:docs;discovered-from:polylogue-3tl.8 +polylogue-v2mg,3,open,open,task,"Drop model_prices and session_reported_costs tables (zombie, zero consumers)",,storage,polylogue-a7xr,domain-implementation-slice,,live-evidence;schema/contract,,,,,no-explicit-paths,,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:25:53Z,2026-07-09T19:25:53Z,area:storage;discovered-from:polylogue-9e5.5 +polylogue-v8dz,3,open,open,task,Add pytest.mark.flaky quarantine marker + lint (needs git_head fix first),,test,polylogue-88jp,domain-implementation-slice,,unspecified/qualitative,,polylogue-k6fm,,,no-explicit-paths,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:20:29Z,2026-07-09T19:20:29Z,area:test;discovered-from:polylogue-9e5.20 +polylogue-vyxq,3,open,open,task,Add devtools lab check to audit testmon file_fp coverage blind spots,,test,polylogue-88jp,domain-implementation-slice,,schema/contract,,polylogue-b054.1.1;polylogue-csg7,,polylogue/verification/manifests/models.py;docs/devtools.md,both=2,,test,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:21Z,2026-07-15T17:09:21Z,area:test +polylogue-x4s,3,open,open,task,Express devloop state in Polylogue substrate (dogfood target),,context;devloop,polylogue-rii,domain-implementation-slice,,live-evidence;receipt/provenance;bounded-resource;restart/crash;security/authorization,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/073_polylogue_x4s.md,origin-only=1,records-landed-work+records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:37Z,2026-07-15T20:07:37Z,area:context;area:devloop;delivery:D-agent-context-coordination;horizon:frontier;lane:context-memory +polylogue-xgw,3,open,open,task,Archive schema hygiene for evidence-cockpit read paths,,storage,polylogue-1xc,canonical-authority-owner,"Fix each additively in the canonical archive-tier DDL; treat the GH issue thread as input, not authority — this bead's scope statement wins where they conflict.",schema/contract,,,,,no-explicit-paths,,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:37Z,2026-07-15T20:07:37Z,area:storage;delivery:B-storage-rebuild-bytes;horizon:frontier;lane:storage-rebuild-scale +polylogue-xnws,3,open,closed,task,Audit silent exception swallowing against the loud-degradation doctrine,,audit;daemon;substrate,,proof/verification-slice,,receipt/provenance;cancellation/cleanup,,,,.agent/reports/narration/2026-07-16-xnws-pilot.jsonl;browser_capture/receiver.py;.agent/reports/narration/2026-07-17-xnws-sweep.jsonl,origin-only=2;missing/planned=1,export regressed after later close; restore closed,audit,restore closed; remove false active work,2026-07-16T23:43:40Z,2026-07-16T23:59:23Z,area:audit;area:daemon;area:substrate;horizon:frontier +polylogue-xul7,3,open,open,task,Measured trigram fallback lane for lexical search recall,,search,polylogue-mhx,domain-implementation-slice,"Measure the existing blocks_command_trigram structure first: if it can serve the lane, make it the one implementation; otherwise delete it before adding a replacement.",bounded-resource;regression-fixture,,polylogue-9jsi,,docs/search.md,both=1,records-landed-work+records-residual-work,search,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:07:09Z,2026-07-15T17:07:09Z,area:search +polylogue-xv1u,3,open,open,feature,Candidate curriculum v1: measurable teaching artifacts without self-authority,,context,polylogue-37t,domain-implementation-slice,,receipt/provenance;bounded-resource;determinism/idempotency;security/authorization;operator-gate,polylogue-37t.11.1;polylogue-stc,,,,no-explicit-paths,records-residual-work,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:context;horizon:mid +polylogue-xyel,3,open,open,task,Real PF-D1-receipts demo (212.2) re-emitted through demo-packet contract,,demos,polylogue-212,domain-implementation-slice,,receipt/provenance,,polylogue-212.7,,devtools/demo_packet.py;.agent/demos/_packet-contract-stub/;.agent/demos/registry.json;.agent/demos/d1-receipts/,both=1;origin-only=1;symbolic=2,records-landed-work+records-residual-work,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:demos;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-y4c,3,open,open,task,"Configuration doctrine: great defaults, DB-backed runtime prefs, Nix module parity",,ops;surface,polylogue-w8db,domain-implementation-slice,,live-evidence;bounded-resource;parity/completeness;security/authorization;schema/contract;operator-gate,,,,,no-explicit-paths,records-landed-work,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:14:02Z,2026-07-13T04:14:02Z,area:ops;area:surface;chore;delivery:E-variants-preferences;lane:variants-preferences;wave:2 +polylogue-yeq.4,3,open,open,task,"Evaluate cold comprehension, accessibility, and comparative operator value",,legibility;test;web,polylogue-yeq,domain-implementation-slice,"The first unaided external adoption receipt from hg8n.1 may seed recruitment, friction hypotheses, or one compatible observation, but it cannot satisfy the fixed comparative/accessibility protocol by itself.",live-evidence;receipt/provenance;determinism/idempotency;restart/crash;security/authorization;regression-fixture,polylogue-yeq.1;polylogue-yeq.2;polylogue-yeq.3;polylogue-z9gh.7,polylogue-hg8n.1,,,no-explicit-paths,records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:25Z,2026-07-15T20:07:25Z,area:legibility;area:test;area:web;horizon:mid +polylogue-ykhy,3,open,open,task,Extract a shared read-only SQLite open helper for devtools/,,unlabeled,,domain-implementation-slice,,live-evidence,,,,devtools/_sqlite.py,missing/planned=1,,unlabeled,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:43:09Z,2026-07-16T10:43:09Z, +polylogue-yp0,3,open,open,epic,"Daemon event fabric: publish once, react, reconcile slowly",,daemon,polylogue-a7xr,program/invariant-container,"Adding an event/subscriber requires one declared registry entry and completeness checks, not bespoke loop wiring.",bounded-resource;parity/completeness;restart/crash;schema/contract,polylogue-9e5.7,,,polylogue/daemon/event_bus.py;tests/unit/daemon/test_event_bus.py,both=2,records-landed-work+records-residual-work,daemon-events,retain as downstream event transport; soft-relate to avmq,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:daemon;delivery:M-substrate-consolidation;delivery:ac-patched;horizon:mid;lane:substrate-consolidation;refactor +polylogue-yqof,3,open,open,feature,Agent control (reverse channel) popup home,,capture,polylogue-yyvg,domain-implementation-slice,,receipt/provenance;determinism/idempotency;security/authorization;schema/contract,,,,,no-explicit-paths,,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:capture;delivery:L-external-legibility;horizon:mid +polylogue-yrx,3,open,open,feature,Session changes view: per-session diff/changelog composed from edit evidence,,insights;surface;web,polylogue-bby,domain-implementation-slice,,receipt/provenance;regression-fixture,,polylogue-bby.11,,,no-explicit-paths,,insights,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:49Z,2026-07-08T20:14:49Z,area:insights;area:surface;area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit;spine +polylogue-yyvg.1,3,open,open,feature,Reverse-organization channel: polylogue organizes the provider webUIs,,capture,polylogue-yyvg,domain-implementation-slice,Use the shared plan -> authorize -> apply -> receipt -> reconcile vocabulary without a generic mutation executor.,receipt/provenance;determinism/idempotency;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,records-landed-work+records-residual-work,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:capture;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-yyvg.2,3,open,open,task,Provider collections as first-class: projects/custom-GPTs/claude.ai projects modeled,,capture,polylogue-yyvg,domain-implementation-slice,,receipt/provenance;parity/completeness;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:capture;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-yyvg.3,3,open,open,feature,Archive-context overlay: cross-provider intelligence blended into native webUIs,,capture,polylogue-yyvg,domain-implementation-slice,Every element is read-or-suggest; no silent writes (writes belong to the reverse-organization sibling).,receipt/provenance;bounded-resource;security/authorization;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,,capture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:29Z,2026-07-15T20:07:29Z,area:capture;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-ze5,3,open,open,task,Decision: user.db vocabulary — separate epistemic records from workspace state,,context;substrate,polylogue-37t,consolidation-owner,"Sufficiency gaps for the epistemic class specifically: no inter-record relations (supersedes / contradicts / refines — needed the moment two lessons conflict), no revision history (edits overwrite; for an epistemics tool, belief CHANGE is data), confidence is absent (predictions h10 bolt it on), an…",bounded-resource;parity/completeness;schema/contract,,,,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T20:07:37Z,2026-07-15T20:07:37Z,area:context;area:substrate;decision;delivery:D-agent-context-coordination;horizon:frontier;lane:agent-coordination +polylogue-zkmi,3,open,open,task,"sources: aistudio-drive detector (drive.looks_like) is dead code, never wired into dispatch",,parsers;sources,,domain-implementation-slice,,unspecified/qualitative,,,,polylogue/sources/parsers/drive.py,both=1,,parsers,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:21:29Z,2026-07-16T10:21:29Z,area:parsers;area:sources;discovered-from:dogfood-2 +polylogue-0cg,4,open,open,feature,OTel GenAI semantic-conventions ingest: any instrumented agent framework becomes an origin,,sources;substrate,polylogue-l4kf,domain-implementation-slice,,parity/completeness;determinism/idempotency;schema/contract;regression-fixture,polylogue-2qx.1.1;polylogue-wmj,,,,no-explicit-paths,,sources,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:03:05Z,2026-07-13T04:03:05Z,area:sources;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-1jc,4,open,open,feature,Learned defaults: the archive proposes your configuration as judged candidates,,analytics;context;surface,polylogue-w8db,domain-implementation-slice,,bounded-resource;regression-fixture,polylogue-20d.14;polylogue-37t.12;polylogue-y4c,polylogue-37t.10,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T03:59:58Z,2026-07-13T03:59:58Z,area:analytics;area:context;area:surface;delivery:E-variants-preferences;lane:variants-preferences +polylogue-1vpm.2,4,open,open,task,"Episode unit: tables, 4-signal scorer with false-merge floor, assertion-calibrated",,substrate,polylogue-1vpm,domain-implementation-slice,,determinism/idempotency;security/authorization;operator-gate;regression-fixture,,polylogue-1vpm.3;polylogue-4ts;polylogue-mhx,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/114_polylogue_1vpm_2.md,origin-only=1,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:21Z,2026-07-15T17:11:21Z,area:substrate;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-1vpm.4,4,open,open,task,Turn-pair unit with prompt-burst semantics (no double-claimed answers),,substrate,polylogue-1vpm,canonical-authority-owner,"A state machine accumulates consecutive eligible human_authored/operator_command prompts into one burst, skips runtime/tool protocol material without erasing timing, attaches at most one following assistant-authored answer, and emits abandoned trailing bursts.",bounded-resource;security/authorization;schema/contract;operator-gate;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/115_polylogue_1vpm_4.md,origin-only=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:04Z,2026-07-15T17:11:04Z,area:substrate;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-1vpm.5,4,open,open,task,Correction-edge runtime query: resolve correction assertions to corrected blocks/tools/models,,substrate,polylogue-1vpm,domain-implementation-slice,,receipt/provenance;schema/contract,,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:00Z,2026-07-15T17:11:00Z,area:substrate;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-20d.12,4,open,open,feature,"Daemon result cache + post-ingest warming: precomputed answers, cursor-keyed invalidation",,daemon;perf,polylogue-20d,domain-implementation-slice,,bounded-resource;restart/crash;regression-fixture,polylogue-20d.1,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/092_polylogue_20d_12.md,origin-only=1,records-landed-work+records-residual-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:01:30Z,2026-07-13T04:01:30Z,area:daemon;area:perf;delivery:G-live-performance;lane:interactive-performance;spine +polylogue-20d.15,4,open,open,task,"Bulk ingest throughput + resource envelope: parallel parse, batched writes, bounded RSS/IO",,daemon;ops;perf,polylogue-20d,domain-implementation-slice,,live-evidence;bounded-resource;operator-gate;regression-fixture,polylogue-20d.14,polylogue-20d.6;polylogue-b5l,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/090_polylogue_20d_15.md,origin-only=1,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:13Z,2026-07-08T20:15:13Z,area:daemon;area:ops;area:perf;delivery:G-live-performance;lane:interactive-performance;size:M;spine +polylogue-212,4,open,open,epic,Proof-world demo portfolio: PF-D1/PF-D2/PF-D4/PF-D5/PF-D8,,demos,,program/invariant-container,"Only non-primitive DATA TRANSFORMS or COMPUTATIONS belong to the ""file it as a primitive first"" rule; agent-in-the-loop decision-making does not.",live-evidence;receipt/provenance;bounded-resource;mutation/falsification;schema/contract;operator-gate;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-06/D-demos.md;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/191_polylogue_212.md;docs/demos.md;.agent/handoffs/polylogue-legibility-kit-2026-07-10/02b-demo-portfolio-expanded.md,both=1;origin-only=3,records-residual-work,demos,retain program container; do not execute as a leaf,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:demos;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-212.1,4,open,open,task,Post-hoc forensic Q&A demo: questions a tracer cannot answer,,demos;legibility,polylogue-212,domain-implementation-slice,,live-evidence;regression-fixture,polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/122_polylogue_212_1.md,origin-only=1,,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:20Z,2026-07-08T20:15:20Z,area:demos;area:legibility;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-212.2,4,open,open,task,PF-D1 'The receipts': claim-vs-evidence on a real PR,,demos,polylogue-212,domain-implementation-slice,,unspecified/qualitative,polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/123_polylogue_212_2.md;.agent/handoffs/polylogue-legibility-kit-2026-07-10/fork-prompts/02-polylogue-receipts-demo.md;.agent/handoffs/polylogue-legibility-kit-v2-2026-07-10/07-BEADS-DELTA.md,origin-only=3,records-landed-work,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:demos;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-212.3,4,open,open,task,PF-D2 'Where did the money actually go': cost by outcome,,demos;usage,polylogue-212,domain-implementation-slice,,regression-fixture,polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/124_polylogue_212_3.md,origin-only=1,,demos,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:demos;area:usage;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-212.5,4,open,open,task,PF-D5 'The session that watched itself': live capture proof,,daemon;demos,polylogue-212,domain-implementation-slice,,live-evidence;schema/contract,polylogue-9e5.28;polylogue-9e5.29;polylogue-9e5.30;polylogue-cpf.5;polylogue-cpf.6;polylogue-svfj,,,.agent/demos/,symbolic=1,records-landed-work,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:daemon;area:demos;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-2jj,4,open,open,task,IssueBench: real issues as coding-agent effectiveness benchmarks,,analytics,polylogue-rxdo,domain-implementation-slice,,schema/contract,,,,,no-explicit-paths,records-landed-work+records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:02:33Z,2026-07-13T04:02:33Z,area:analytics;delivery:N-horizon;horizon:vision;lane:horizon-spec;research +polylogue-2n6,4,open,open,feature,Harness remote-control lane: drive Claude Code / Codex sessions from Polylogue surfaces,,context,polylogue-s7ae,surface/adaptation-slice,,regression-fixture,polylogue-37t.8,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:36Z,2026-07-08T20:15:36Z,area:context;delivery:N-horizon;delivery:ac-patched;lane:horizon-spec +polylogue-3tl,4,open,open,epic,"External legibility: a stranger can understand, run, and cite Polylogue",,legibility,,program/invariant-container,,unspecified/qualitative,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/190_polylogue_3tl.md;.agent/handoffs/polylogue-legibility-kit-2026-07-10/;docs/demos.md;docs/findings/claim-vs-evidence.md;docs/public-claims.yaml;docs/sinex-interop.md;.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md,both=4;origin-only=2;symbolic=1,records-landed-work+records-residual-work,legibility,retain program container; do not execute as a leaf,2026-07-11T11:37:26Z,2026-07-11T11:37:26Z,area:legibility;delivery:L-external-legibility;horizon:mid;lane:docs-demos-launch +polylogue-3tl.10,4,open,open,task,Launch kit: announcement artifacts prepared so publication is one decision,,legibility,polylogue-3tl,domain-implementation-slice,,live-evidence;bounded-resource;security/authorization;schema/contract;operator-gate,polylogue-212.1;polylogue-212.2;polylogue-212.3;polylogue-212.4;polylogue-212.8;polylogue-3tl.16;polylogue-3tl.4;polylogue-3tl.7;polylogue-cfk,,,docs/launch/;.agent/handoffs/polylogue-legibility-kit-2026-07-10/;docs/legibility,missing/planned=1;symbolic=2,,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:42:26Z,2026-07-14T14:42:26Z,area:legibility;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-3tl.12,4,open,open,task,README de-meta / de-persuasion pass with reproducible capability claims,,legibility,polylogue-3tl,canonical-authority-owner,"Place that proof + its visual immediately below the fold, the run command directly after it, compress capabilities to three sections (find/read, audit lineage+cost, compile reviewed context) instead of five, keep the pre-1.0 trusted-single-host boundary visible near the proof not buried at the end.",live-evidence;receipt/provenance;determinism/idempotency;security/authorization;operator-gate;regression-fixture,polylogue-3tl.16,,,.agent/handoffs/polylogue-readme-positioning-2026-07-14/;.agent/handoffs/polylogue-legibility-kit-2026-07-10/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/126_polylogue_3tl_12.md;docs/public-claims.yaml;docs/examples/visual-tapes/evidence-receipt.png,both=2;origin-only=1;symbolic=2,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:53Z,2026-07-16T12:56:53Z,area:legibility;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-3tl.15,4,open,open,task,"Anti-grep proof card: the ""why not grep ~/.claude"" answer, grounded in one finding",,docs;legibility,polylogue-3tl,domain-implementation-slice,,regression-fixture,,,,.agent/demos;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md;.agent/demos/claim-vs-evidence/;docs/getting-started.md;docs/search.md;docs/findings/claim-vs-evidence.md;docs/demos.md,both=4;origin-only=2;symbolic=1,records-landed-work+records-residual-work,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-10T17:14:24Z,2026-07-10T17:14:24Z,area:docs;area:legibility;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch;tech-tree +polylogue-3tl.3,4,open,open,task,"Claim-vs-evidence leaderboard variant (multi-model, incl. open models)",,legibility,polylogue-3tl,domain-implementation-slice,,regression-fixture,,,,.agent/demos/claim-vs-evidence;.agent/demos/claim-vs-evidence/claim-vs-evidence.report.json;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md,origin-only=1;missing/planned=2,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:45:16Z,2026-07-09T19:45:16Z,area:legibility;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-3tl.4,4,open,open,task,Findings publishing lane: campaign artifacts on the docs site,,legibility,polylogue-3tl,consolidation-owner,(5) LIVING PAGES: re-running a finding does NOT mint a new URL and does NOT silently replace numbers — it appends a dated CHANGELOG entry attributing each changed number to the provenance delta (new cursor/commit/run date) and supersedes in place.,live-evidence;receipt/provenance;bounded-resource;determinism/idempotency;schema/contract;regression-fixture,polylogue-3tl.16;polylogue-bby.15;polylogue-rxdo.4;polylogue-svfj,polylogue-cpf,,docs/findings/;devtools/render_findings.py;devtools/command_catalog.py;devtools/render_all.py;docs/findings/**;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/128_polylogue_3tl_4.md;docs/findings/claim-vs-evidence.md,both=3;origin-only=1;missing/planned=1;symbolic=2,records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-10T14:50:01Z,2026-07-10T14:50:01Z,area:legibility;delivery:L-external-legibility;lane:docs-demos-launch;spine;wave:2 +polylogue-3tl.6,4,open,open,feature,Publish the normalized session model as a versioned interchange schema,,legibility;substrate,polylogue-3tl,domain-implementation-slice,,receipt/provenance;schema/contract;regression-fixture,polylogue-2qx.1.1;polylogue-rxdo.1;polylogue-svfj,,,docs/schemas/cli-output/,symbolic=1,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:00:51Z,2026-07-13T04:00:51Z,area:legibility;area:substrate;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-3tl.7,4,open,open,task,Release is a decision: proven install matrix across package managers and OSes,,legibility;ops,polylogue-3tl,domain-implementation-slice,,unspecified/qualitative,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/129_polylogue_3tl_7.md;docs/installation.md;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md;.agent/handoffs/polylogue-readme-positioning-2026-07-14/polylogue-02-time-to-proof.md,both=1;origin-only=3,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:53Z,2026-07-16T12:56:53Z,area:legibility;area:ops;delivery:L-external-legibility;lane:docs-demos-launch;wave:1 +polylogue-3tl.8,4,open,open,task,GitHub surface polish: the repo page itself is a landing page,,legibility,polylogue-3tl,surface/adaptation-slice,,bounded-resource;security/authorization,polylogue-3tl.1,polylogue-3tl.4,,docs/security.md;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md;docs/daemon-threat-model.md;.agent/handoffs/polylogue-readme-positioning-2026-07-14/,both=2;origin-only=1;symbolic=1,records-landed-work+records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:42:25Z,2026-07-14T14:42:25Z,area:legibility;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-3tl.9,4,open,open,task,Docs-and-visuals ownership: coverage lint + regenerable visuals as a standing devloop gate,,devloop;devtools;legibility,polylogue-3tl,domain-implementation-slice,,security/authorization;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/193_polylogue_3tl_9.md;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md;tests/infra/mcp.py;devtools/verify_doc_commands.py;devtools/render_topology_status.py;devtools/docs_surface.py;devtools/verify_docs_coverage.py;docs/plans/docs-coverage-baseline.yaml;devtools/render_mcp_tool_index.py;docs/mcp-reference.md;tests/unit/devtools/test_verify_docs_coverage.py,both=9;origin-only=2,records-landed-work+records-residual-work,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T12:56:54Z,2026-07-16T12:56:54Z,area:devloop;area:devtools;area:legibility;delivery:L-external-legibility;lane:docs-demos-launch +polylogue-3xx,4,open,open,feature,"Verb-behavior and ops preferences bundle: confirmations, judge defaults, copy formats, spend, quiesce",,ops;surface,polylogue-w8db,domain-implementation-slice,,live-evidence;restart/crash;security/authorization;regression-fixture,polylogue-37t.12;polylogue-y4c,,,,no-explicit-paths,records-residual-work,ops,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:30Z,2026-07-08T20:15:30Z,area:ops;area:surface;delivery:E-variants-preferences;lane:variants-preferences +polylogue-45i,4,open,open,feature,Datasette lane: the archive as an explorable SQLite exhibit,,legibility;surface,polylogue-3tl,domain-implementation-slice,,receipt/provenance;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,records-residual-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:35Z,2026-07-08T20:15:35Z,area:legibility;area:surface;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-4822,4,open,open,feature,Curated polylogue.sdk + frozen public models: the external-consumer boundary lynchpin needs,,api,polylogue-hg8n,canonical-authority-owner,"SEQUENCE 2026-07-13: after t46.8's MCP verb algebra — the SDK's ~20 curated verbs and the MCP's collapsed verb core should be the SAME verbs over the same contracts (one boundary, two transports).",security/authorization;schema/contract,polylogue-3tl.6;polylogue-rxdo.1,,,,no-explicit-paths,,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:10:00Z,2026-07-15T17:10:00Z,area:api;delivery:M-substrate-consolidation;horizon:mid;lane:substrate-consolidation;tech-tree +polylogue-48h,4,open,open,chore,Consolidate SQLite introspection helpers (10 copies of _table_exists and friends),,substrate,polylogue-a7xr,domain-implementation-slice,"One module: storage/sqlite/introspection.py with table_exists(conn, name, schema='main'), index_exists, column_exists — sync versions + thin async wrappers (or a single implementation taking both connection protocols; decide by reading the aiosqlite call sites).",parity/completeness;schema/contract,,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:34Z,2026-07-08T20:15:34Z,area:substrate;delivery:M-substrate-consolidation;delivery:ac-patched;lane:substrate-consolidation;refactor;wave:2 +polylogue-4g5,4,open,open,feature,Expose the archive as an HPI module and Promnesia source,,legibility,polylogue-l4kf,domain-implementation-slice,,live-evidence;schema/contract;regression-fixture,,,,,no-explicit-paths,,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:30Z,2026-07-07T13:00:30Z,area:legibility;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-4smp,4,open,open,epic,Content variants: language-aware transformed archive objects with alignment,,context;mcp;query;surface;web,,program/invariant-container,,receipt/provenance;schema/contract;regression-fixture,,polylogue-37t.1;polylogue-4p1;polylogue-jnj.1;polylogue-s7ae,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/079_polylogue_4smp.md,origin-only=1,records-residual-work,context,retain program container; do not execute as a leaf,2026-07-08T20:14:59Z,2026-07-08T20:14:59Z,area:context;area:mcp;area:query;area:surface;area:web;delivery:E-variants-preferences;horizon:vision;lane:variants-preferences;size:L;spine +polylogue-4ts.7,4,open,open,task,"Physical session identity collision beneath origin collapse: same native_id, two source families, one row",,lineage;substrate,polylogue-4ts,domain-implementation-slice,,regression-fixture,polylogue-2qx.1.1,,,,no-explicit-paths,,lineage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:10:54Z,2026-07-15T17:10:54Z,area:lineage;area:substrate;delivery:F-lineage-compaction;horizon:mid;lane:lineage-compaction;tech-tree +polylogue-5dx,4,open,open,chore,"Dependency leverage policy: [analytics]/[ml] extras, evaluated adoptions",,devtools,polylogue-a7xr,domain-implementation-slice,,unspecified/qualitative,,,,,no-explicit-paths,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:32Z,2026-07-08T20:15:32Z,area:devtools;chore;delivery:M-substrate-consolidation;lane:substrate-consolidation +polylogue-611,4,open,open,feature,Grok (xAI) conversation export importer,,ingest,polylogue-l4kf,domain-implementation-slice,,live-evidence;regression-fixture,polylogue-2qx.1.1,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:33Z,2026-07-07T13:00:33Z,area:ingest;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export +polylogue-6bu,4,open,open,chore,"Docs-site verification lane (pages cache, link integrity)",,devloop,polylogue-3tl,domain-implementation-slice,,live-evidence,,,,,no-explicit-paths,,devloop,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:38Z,2026-07-08T20:15:38Z,area:devloop;delivery:L-external-legibility;horizon:frontier;lane:docs-demos-launch +polylogue-6kh,4,open,open,feature,"Query-scope preferences bundle: default time window, scope filters, logical fold",,query;surface,polylogue-w8db,domain-implementation-slice,,live-evidence,polylogue-fnm.12;polylogue-y4c,,,,no-explicit-paths,records-landed-work,query,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:09Z,2026-07-08T20:15:09Z,area:query;area:surface;delivery:E-variants-preferences;lane:variants-preferences +polylogue-7k7,4,open,open,feature,Research-tooling export lane: inspect-ai / Docent formats,,substrate,polylogue-l4kf,domain-implementation-slice,,receipt/provenance;schema/contract;operator-gate;regression-fixture,polylogue-fs1.3;polylogue-fs1.5,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:29Z,2026-07-07T13:00:29Z,area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-9l5.1,4,open,open,feature,Outcome-conditioned analytics: cost/duration/retries/tools by structural success,,analytics,polylogue-9l5,domain-implementation-slice,Anchored examples (all one step from existing substrate): cost of failed vs clean sessions; failure-rate by model VERSION; retry cascade depth; 'sessions where >30% of tool calls errored' (needs the child-count DSL predicate or the relation directly).,regression-fixture,polylogue-9l5.7.2,polylogue-1vpm,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/180_polylogue_9l5_1.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:19Z,2026-07-08T20:15:19Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.10,4,open,open,feature,"Process mining: workflow motifs, transition models, bottleneck discovery",,analytics,polylogue-9l5,canonical-authority-owner,,receipt/provenance;parity/completeness;mutation/falsification;regression-fixture,polylogue-9l5.7.3,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:33Z,2026-07-08T20:15:33Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.11,4,open,open,feature,Predictive advisories: calibrated classical models on structural labels,,analytics;context,polylogue-9l5,domain-implementation-slice,,live-evidence;receipt/provenance;operator-gate,polylogue-9l5.7.3,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:32Z,2026-07-08T20:15:32Z,area:analytics;area:context;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.12,4,open,open,feature,"Information-theoretic and graph measures: redundancy, diversity, tree shapes",,analytics,polylogue-9l5,domain-implementation-slice,,regression-fixture,polylogue-9l5.7.3,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:32Z,2026-07-08T20:15:32Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.13,4,open,open,task,activity_spans materializer: edit/test/build/idle/delegate intervals with evidence tiers,,analytics;insights,polylogue-9l5,domain-implementation-slice,,receipt/provenance;determinism/idempotency;regression-fixture,polylogue-9l5.19,polylogue-1vpm;polylogue-9l5.6;polylogue-9l5.7.2,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/117_polylogue_9l5_13.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:14Z,2026-07-15T17:11:14Z,area:analytics;area:insights;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-9l5.14,4,open,open,task,Efficiency measure pack v1: scorecard vector over spans/episodes/delegations — no magic score,,analytics;insights,polylogue-9l5,domain-implementation-slice,,regression-fixture,polylogue-9l5.13;polylogue-9l5.7.2,polylogue-1vpm.1,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:10Z,2026-07-15T17:11:10Z,area:analytics;area:insights;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-9l5.15,4,open,open,task,Triage frontier: worth_reviewing_score + TRIAGED lifecycle — an inbox that empties,,analytics;insights,polylogue-9l5,domain-implementation-slice,"Honesty corrections from review: hard-zero ONLY truly disposable sessions — in-flight and superseded branches become VISIBLE demoted buckets, never hidden drops (a queue that hides rows falsely looks empty); low-confidence enrichment factors visibly marked and down-weighted.",bounded-resource;operator-gate;regression-fixture,polylogue-9l5.7.2,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/185_polylogue_9l5_15.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:03Z,2026-07-08T20:15:03Z,area:analytics;area:insights;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-9l5.16,4,open,open,task,"Trajectory Quality Index: reward-shaping composite, never truth",,analytics,polylogue-9l5,canonical-authority-owner,,parity/completeness;operator-gate;regression-fixture,polylogue-9l5.7.2,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:21Z,2026-07-07T13:00:21Z,area:analytics;delivery:I-analytics-experiments;delivery:ac-patched;horizon:vision;lane:analytics-experiments;tech-tree +polylogue-9l5.17,4,open,open,task,"Model-drift observatory: candidate changepoints with validity gates, never causal claims",,analytics,polylogue-9l5,canonical-authority-owner,,parity/completeness;security/authorization;mutation/falsification;operator-gate;regression-fixture,polylogue-9l5.7.3,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:20Z,2026-07-07T13:00:20Z,area:analytics;delivery:I-analytics-experiments;delivery:ac-patched;horizon:vision;lane:analytics-experiments;tech-tree +polylogue-9l5.19,4,open,open,task,Thinking-vs-doing drift: experimental coverage-gated measure of reasoning share vs tool-active share,,analytics,polylogue-9l5,domain-implementation-slice,,live-evidence;bounded-resource;security/authorization,,polylogue-9l5.7,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:24Z,2026-07-08T20:15:24Z,area:analytics;delivery:I-analytics-experiments;horizon:mid;lane:analytics-experiments;tech-tree +polylogue-9l5.2,4,open,open,feature,Cross-provider comparative analytics,,analytics,polylogue-9l5,domain-implementation-slice,,receipt/provenance;regression-fixture,polylogue-9l5.7.3,polylogue-1vpm,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/181_polylogue_9l5_2.md,origin-only=1,records-residual-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:18Z,2026-07-08T20:15:18Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.3,4,open,open,feature,Pathology epidemiology: corpus-level rates and trends,,analytics,polylogue-9l5,canonical-authority-owner,,parity/completeness;determinism/idempotency;regression-fixture,polylogue-9l5.7.2,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:40Z,2026-07-08T20:15:40Z,area:analytics;delivery:I-analytics-experiments;delivery:ac-patched;lane:analytics-experiments +polylogue-9l5.4,4,open,open,feature,Token-economy analytics: cache-lane and attention accounting,,analytics;usage,polylogue-9l5,canonical-authority-owner,,bounded-resource;parity/completeness;operator-gate;regression-fixture,polylogue-9l5.7.2,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:40Z,2026-07-08T20:15:40Z,area:analytics;area:usage;delivery:I-analytics-experiments;delivery:ac-patched;lane:analytics-experiments +polylogue-9l5.5,4,open,open,feature,Ship opinionated saved views as product defaults,,analytics;query,polylogue-9l5,canonical-authority-owner,"That unifies the example library, completions, and the golden-path workflow registry into one mechanism (workflow registry entries become saved queries with docs).",parity/completeness;regression-fixture,,polylogue-9l5.7.2,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:39Z,2026-07-08T20:15:39Z,area:analytics;area:query;delivery:I-analytics-experiments;delivery:ac-patched;lane:analytics-experiments +polylogue-9l5.8,4,open,open,feature,"Temporal analytics: trends, rolling baselines, changepoint detection",,analytics,polylogue-9l5,domain-implementation-slice,,bounded-resource;schema/contract;regression-fixture,polylogue-9l5.7.3,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/183_polylogue_9l5_8.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:11Z,2026-07-08T20:15:11Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-9l5.9,4,open,open,feature,"Survival analysis: session duration, abandonment hazard, time-to-outcome",,analytics,polylogue-9l5,domain-implementation-slice,"Time axes: wall-clock AND turn-count (turn-axis is often the honest one — wall-clock confounds with human absence; declare both, confound-flag wall-clock in the registry).",live-evidence;parity/completeness;schema/contract;regression-fixture,polylogue-7yk5;polylogue-9l5.7.3,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T05:44:39Z,2026-07-13T05:44:39Z,area:analytics;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-a7xr.15,4,open,open,chore,payloads.py: generic from_row for the 74 identical-name copy lines (keeps typed wire contract),,substrate,polylogue-a7xr,domain-implementation-slice,,parity/completeness;schema/contract,,polylogue-20d.2,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:22Z,2026-07-08T20:15:22Z,area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine;tech-tree +polylogue-a7xr.16,4,open,open,task,Table-drive the hand-aligned column triplicates in archive_tiers write/read hot core,,substrate,polylogue-a7xr,domain-implementation-slice,,unspecified/qualitative,,,,tests/unit/storage/test_crud.py,missing/planned=1,records-landed-work,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:37:05Z,2026-07-14T14:37:05Z,area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine;tech-tree +polylogue-a7xr.8,4,open,open,task,Index-tier sibling-path derivation pasted ~7x with divergent existence rules,,substrate,polylogue-a7xr,domain-implementation-slice,,receipt/provenance;regression-fixture,,,,polylogue/paths,both=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:23Z,2026-07-08T20:15:23Z,area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine;tech-tree +polylogue-a7xr.9,4,open,open,chore,"Mechanical helper dedup sweep: scalar coercion quadruplet, _table_exists x40, provenance vocab x6, title/tags mixin",,substrate,polylogue-a7xr,canonical-authority-owner,One PR per letter or one sweep PR — batching judgment to the executor.,receipt/provenance;schema/contract,,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T00:08:55Z,2026-07-15T00:08:55Z,area:substrate;delivery:M-substrate-consolidation;horizon:frontier;lane:substrate-consolidation;spine;tech-tree +polylogue-a820,4,open,open,chore,rendering: remove dead build_projection_html_messages / get_render_projection code path,,rendering,,surface/adaptation-slice,,unspecified/qualitative,,polylogue-4p1,,,no-explicit-paths,,rendering,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T11:18:06Z,2026-07-16T11:18:06Z,area:rendering;discovered-from:dogfood-2 +polylogue-ale,4,open,open,feature,External link archival: sessions cite URLs; the evidence should not rot,,context;ingest,polylogue-l4kf,domain-implementation-slice,,bounded-resource;parity/completeness;regression-fixture,,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:25Z,2026-07-07T13:00:25Z,area:context;area:ingest;delivery:K-interop-origin-export;lane:origin-interop-export +polylogue-b1n,4,open,open,feature,WebUI-driven posting: operator drives web chats from the workbench,,web,polylogue-bby,domain-implementation-slice,"The web behavior for `polylogue-b1n` is backed by the shared API contract, handles loading/stale/error states explicitly, and has a seeded visual or interaction smoke test.",schema/contract;regression-fixture,polylogue-37t.15;polylogue-kwsb.1;polylogue-ptx,polylogue-bby.11,,,no-explicit-paths,,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:36Z,2026-07-08T20:15:36Z,area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-bby,4,open,open,epic,Web workbench: from result list to evidence cockpit,,web,,program/invariant-container,Runtime/prototype/campaign material is largely superseded by #2673/#2675 and existing children; no parallel graph imported.,live-evidence;regression-fixture,,,,.agent/reports/chatgpt-pro-branch-assimilation-2026-07-11.md,origin-only=1,,web,retain program container; do not execute as a leaf,2026-07-11T11:37:23Z,2026-07-11T11:37:23Z,area:web;delivery:H-web-cockpit;horizon:mid;lane:web-evidence-cockpit +polylogue-bby.10,4,open,open,task,Timeline and firehose: the archive as a scrubbable stream,,surface;web,polylogue-bby,domain-implementation-slice,,live-evidence;receipt/provenance;parity/completeness;security/authorization;operator-gate;regression-fixture,polylogue-20d.12;polylogue-20d.13,,,,no-explicit-paths,records-landed-work,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:30Z,2026-07-08T20:15:30Z,area:surface;area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-bby.11,4,open,open,feature,Webui architecture v2: the stack that can carry the ambition,,legibility;web,polylogue-bby,domain-implementation-slice,"(5) MIGRATION: strangler pattern — v2 mounts at /app serving new views against the same API; old SPA remains until view parity, then dies in one PR (no long dual maintenance); bby.6's extraction is superseded for the JS (CSS tokens still shared) — re-note bby.6.",live-evidence;bounded-resource;parity/completeness;security/authorization;schema/contract;regression-fixture,polylogue-1ilk,polylogue-4smp;polylogue-rlsb,,polylogue/daemon/static/dist/;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/194_polylogue_bby_11.md;.agent/reports/decision-brief-2026-07-08.md,origin-only=2;symbolic=1,records-landed-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-11T15:52:34Z,2026-07-11T15:52:34Z,area:legibility;area:web;decision;delivery:H-web-cockpit;lane:web-evidence-cockpit;spine;wave:2 +polylogue-bby.12,4,open,open,feature,Session replay: play a session back the way it happened,,web,polylogue-bby,domain-implementation-slice,,regression-fixture,polylogue-4ts.4;polylogue-4ts.6,,,,no-explicit-paths,,web,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:03:31Z,2026-07-13T04:03:31Z,area:web;delivery:H-web-cockpit;lane:web-evidence-cockpit +polylogue-bby.3,4,open,open,feature,Aggregate analytics views in the web UI,,analytics;web,polylogue-bby,domain-implementation-slice,"The web behavior for `polylogue-bby.3` is backed by the shared API contract, handles loading/stale/error states explicitly, and has a seeded visual or interaction smoke test.",live-evidence;schema/contract;regression-fixture,,polylogue-9l5.7.2;polylogue-fnm.1,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T07:00:18Z,2026-07-13T07:00:18Z,area:analytics;area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-bby.4,4,open,open,feature,Live session tailing as a first-class mode,,daemon;web,polylogue-bby,domain-implementation-slice,"The web behavior for `polylogue-bby.4` is backed by the shared API contract, handles loading/stale/error states explicitly, and has a seeded visual or interaction smoke test.",bounded-resource;schema/contract;regression-fixture,polylogue-20d.13,,,,no-explicit-paths,,daemon,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:39Z,2026-07-08T20:15:39Z,area:daemon;area:web;delivery:H-web-cockpit;delivery:ac-patched;lane:web-evidence-cockpit +polylogue-c36,4,open,open,task,"Native-compilation probe: mypyc first, only where profiles demand it",,perf,polylogue-20d,domain-implementation-slice,,unspecified/qualitative,polylogue-20d.15,,,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,,perf,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T18:39:45Z,2026-07-08T18:39:45Z,area:perf;decision;delivery:N-horizon;lane:horizon-spec +polylogue-ca4,4,open,open,task,Decision: DuckDB as the optional OLAP engine over the archive,,analytics;perf,polylogue-20d,domain-implementation-slice,,live-evidence;bounded-resource;parity/completeness;regression-fixture,,,,.agent/reports/decision-brief-2026-07-08.md,origin-only=1,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:02:28Z,2026-07-13T04:02:28Z,area:analytics;area:perf;decision;delivery:N-horizon;lane:horizon-spec +polylogue-d4zk,4,open,open,feature,"User and agent UX for creating, reviewing, and messaging about variants",,context;coordination;mcp;web,polylogue-4smp,domain-implementation-slice,The user can accept/reject/supersede or otherwise mark variant status without changing original source content.,receipt/provenance;regression-fixture,polylogue-0v9p;polylogue-37t.15;polylogue-arso;polylogue-rlsb,polylogue-90y;polylogue-bby.11;polylogue-pj8;polylogue-s7ae.3,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/078_polylogue_d4zk.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:58Z,2026-07-08T20:14:58Z,area:context;area:coordination;area:mcp;area:web;delivery:E-variants-preferences;lane:variants-preferences;size:M +polylogue-dab.1,4,open,open,task,Drop payload_json/search_text duplication from run-projection tables; hydrate from typed columns,,storage,polylogue-a7xr,surface/adaptation-slice,,live-evidence;parity/completeness;schema/contract;regression-fixture,,,,,no-explicit-paths,records-residual-work,storage,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T19:16:40Z,2026-07-15T19:16:40Z,area:storage;delivery:M-substrate-consolidation;lane:substrate-consolidation;refactor +polylogue-elkv,4,open,open,task,Audit issue templates for genuinely stranger-facing bug/question framing,,docs,polylogue-hg8n,proof/verification-slice,,security/authorization;regression-fixture,,,,,no-explicit-paths,,docs,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:47:07Z,2026-07-09T19:47:07Z,area:docs;discovered-from:polylogue-3tl.8 +polylogue-exb,4,open,open,task,"Layering: substrate rings import the api facade (6 sites, 2 private-symbol reaches)",,substrate,polylogue-1r9c,canonical-authority-owner,"Per-site relocation, then close the gate: (1) _active_archive_root -> config/core (it is runtime-root resolution, nothing facade-y about it); (2) run_coroutine_sync -> a core/asyncbridge module (two substrate rings need it; the api.sync home is an accident of history); (3) _rebuild_archive_session_…",live-evidence,,,,docs/plans/layering.yaml;polylogue/api;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/189_polylogue_exb.md,both=2;origin-only=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:15Z,2026-07-08T20:15:15Z,area:substrate;delivery:M-substrate-consolidation;lane:substrate-consolidation;refactor;wave:2 +polylogue-f94,4,open,open,task,Kill-or-commit the TUI (~373 lines of skeletal Textual screens),,cli,polylogue-t46,domain-implementation-slice,,parity/completeness,,,,polylogue/ui/tui/,symbolic=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:01:41Z,2026-07-13T04:01:41Z,area:cli;delivery:M-substrate-consolidation;delivery:ac-patched;lane:substrate-consolidation +polylogue-fs1.10,4,open,open,feature,Spec-cards: sessions as portable benchmark items (leakage-gated export),,eval;ingest;substrate,polylogue-fs1,domain-implementation-slice,,parity/completeness;determinism/idempotency;security/authorization;schema/contract;regression-fixture,,,,,no-explicit-paths,records-residual-work,eval,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:10:57Z,2026-07-15T17:10:57Z,area:eval;area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export;tech-tree +polylogue-fs1.2,4,open,open,feature,Importer: NeMo Relay ATOF/ATIF runtime spans,,ingest;substrate,polylogue-fs1,domain-implementation-slice,,parity/completeness;determinism/idempotency;security/authorization;schema/contract;regression-fixture,polylogue-2qx.1.1;polylogue-fs1.2.1,,,tests/unit/sources/parsers/test_hermes_spans.py,both=1,records-landed-work+records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T20:25:28Z,2026-07-14T20:25:28Z,area:ingest;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-fs1.4,4,open,open,feature,Report: polylogue forensics for Hermes sessions,,ingest;query;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;security/authorization;regression-fixture,polylogue-fs1.1;polylogue-fs1.3,,,polylogue/cli/read;docs/demo;docs/README/demo;.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/105_polylogue_fs1_4.md;polylogue/insights/postmortem.py;polylogue/insights/session_commit.py,both=2;origin-only=1;missing/planned=3,records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T00:53:41Z,2026-07-14T00:53:41Z,area:ingest;area:query;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-fs1.5,4,open,open,feature,Export: Atropos/eval JSONL downstream of the canonical archive,,ingest;query;substrate,polylogue-fs1,domain-implementation-slice,,receipt/provenance;schema/contract;regression-fixture,polylogue-fs1.1,,,.agent/handoffs/polylogue-gpt-pro-2026-07-06/bundles/rnd-bundle-1-of-6.md,origin-only=1,records-landed-work+records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T00:53:52Z,2026-07-14T00:53:52Z,area:ingest;area:query;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-fs1.6,4,open,open,feature,"Fully-sovereign loop demo: local Hermes -> archive -> local embeddings -> judged memory -> injection, air-gapped",,context;demos;ingest;substrate,polylogue-fs1,domain-implementation-slice,,bounded-resource;security/authorization;schema/contract;regression-fixture,polylogue-37t.5,polylogue-fs1.11,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-10T10:33:45Z,2026-07-10T10:33:45Z,area:context;area:demos;area:ingest;area:substrate;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-fs1.8,4,open,open,task,Nous Chat browser-capture adapter,,ingest;substrate,polylogue-fs1,surface/adaptation-slice,,live-evidence;determinism/idempotency,polylogue-2qx.1.1,,,browser-extension/src/content/chatgpt.js,both=1,records-landed-work+records-residual-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T00:52:55Z,2026-07-14T00:52:55Z,area:ingest;area:substrate;delivery:K-interop-origin-export;horizon:vision;lane:origin-interop-export +polylogue-gikp,4,open,open,task,"API facade (api/archive.py, 5.9k lines): investigate verb-group mixins",,api;architecture,polylogue-1r9c,domain-implementation-slice,"Inventory facade methods by domain capability, shared state, error/transaction semantics, and public protocol.",cancellation/cleanup;parity/completeness;security/authorization;schema/contract,,,,docs/architecture-hotspots.md,both=1,,api,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:57Z,2026-07-15T17:09:57Z,area:api;area:architecture;horizon:vision;refactor +polylogue-gjg,4,open,open,epic,"Compaction lifecycle: pre-compaction snapshot, loss forensics, post-compaction re-grounding",,context;ingest,,program/invariant-container,,security/authorization;operator-gate,polylogue-4ts.5;polylogue-d1y,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/177_polylogue_gjg.md,origin-only=1,records-landed-work,context,retain program container; do not execute as a leaf,2026-07-08T20:15:10Z,2026-07-08T20:15:10Z,area:context;area:ingest;delivery:F-lineage-compaction;horizon:vision;lane:lineage-compaction +polylogue-gjg.1,4,open,open,task,compaction_events + compaction_loss_items derived tables; identity survives rebuild + re-ingest,,context;ingest;substrate,polylogue-gjg,domain-implementation-slice,,security/authorization;schema/contract;regression-fixture,polylogue-4ts.5,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/084_polylogue_gjg_1.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:07Z,2026-07-08T20:15:07Z,area:context;area:ingest;area:substrate;delivery:F-lineage-compaction;horizon:mid;lane:lineage-compaction;tech-tree +polylogue-gjg.2,4,open,open,task,"Pre-compaction snapshot capture: hook payload when available, manifest-of-refs otherwise, honesty ladder always",,context;daemon;ingest,polylogue-gjg,domain-implementation-slice,,live-evidence;operator-gate;regression-fixture,polylogue-d1y,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/085_polylogue_gjg_2.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:07Z,2026-07-08T20:15:07Z,area:context;area:daemon;area:ingest;delivery:F-lineage-compaction;horizon:mid;lane:lineage-compaction;tech-tree +polylogue-gjg.3,4,open,open,task,Deterministic loss-forensics: 4-tier structural diff + lost-but-later-needed ranking,,context;ingest;insights,polylogue-gjg,domain-implementation-slice,,regression-fixture,polylogue-4ts.5;polylogue-gjg.1;polylogue-gjg.2,polylogue-svfj,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/086_polylogue_gjg_3.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T03:59:01Z,2026-07-13T03:59:01Z,area:context;area:ingest;area:insights;delivery:F-lineage-compaction;horizon:mid;lane:lineage-compaction;tech-tree +polylogue-gjg.4,4,open,open,task,compaction_forgot + compaction_reground surfaces; re-grounding packs survive the next compaction,,context;ingest;mcp,polylogue-gjg,surface/adaptation-slice,,live-evidence;security/authorization;schema/contract,polylogue-37t.11.1;polylogue-gjg.1;polylogue-gjg.3,,,,no-explicit-paths,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:27Z,2026-07-08T20:15:27Z,area:context;area:ingest;area:mcp;delivery:F-lineage-compaction;horizon:mid;lane:lineage-compaction;tech-tree +polylogue-gqx,4,open,open,task,Desktop presence spike: Polylogue in the operator's ambient environment,,legibility;ops,polylogue-hg8n,domain-implementation-slice,,schema/contract,polylogue-20d.13;polylogue-scd,,,,no-explicit-paths,records-landed-work,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T18:40:41Z,2026-07-08T18:40:41Z,area:legibility;area:ops;delivery:N-horizon;lane:horizon-spec +polylogue-gz9p,4,open,open,chore,".agent/tools/conductor_compact.py is dead code, safe to excise",,devtools,,domain-implementation-slice,,unspecified/qualitative,,,,.agent/tools/conductor_compact.py,origin-only=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:20:46Z,2026-07-16T10:20:46Z,area:devtools;discovered-from:dogfood-2 +polylogue-h10,4,open,open,task,Prediction and calibration tracking: agents scored on what they said would happen,,analytics;context,polylogue-9l5,domain-implementation-slice,,unspecified/qualitative,polylogue-9l5.7.3;polylogue-h6r,,,,no-explicit-paths,records-landed-work,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:31Z,2026-07-08T20:15:31Z,area:analytics;area:context;delivery:I-analytics-experiments;lane:analytics-experiments +polylogue-kchb,4,open,open,task,"Daemon HTTP (daemon/http.py, 4.6k lines): extend web_shell_*.py satellite pattern",,architecture;daemon;web,polylogue-1r9c,canonical-authority-owner,Shared helpers move only when reference analysis proves one owner or a neutral home; no arbitrary line-range split.,parity/completeness;security/authorization;schema/contract,,,,docs/architecture-hotspots.md,both=1,records-residual-work,architecture,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:09:54Z,2026-07-15T17:09:54Z,area:architecture;area:daemon;area:web;horizon:vision;refactor +polylogue-l4kf.1,4,open,open,feature,polylogue-export origin + CIF envelope: import(export(A)) is a content-hash no-op,,ingest;interop,polylogue-l4kf,canonical-authority-owner,,receipt/provenance;parity/completeness;regression-fixture,polylogue-2qx.1.1;polylogue-rxdo.1;polylogue-rxdo.4;polylogue-svfj,,,,no-explicit-paths,records-landed-work,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:11:07Z,2026-07-15T17:11:07Z,area:ingest;area:interop;delivery:K-interop-origin-export;horizon:mid;lane:origin-interop-export;schema:source-v3;spine;tech-tree +polylogue-l4kf.2,4,open,open,feature,Federation: .well-known/ai-sessions manifest + selective content-hash sync,,ingest;interop,polylogue-l4kf,domain-implementation-slice,,receipt/provenance;regression-fixture,polylogue-2qx.1.1;polylogue-l4kf.1,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:22Z,2026-07-07T13:00:22Z,area:ingest;area:interop;delivery:K-interop-origin-export;delivery:ac-patched;horizon:vision;lane:origin-interop-export;spine;tech-tree +polylogue-l4kf.3,4,open,open,feature,Outbound provenance: git notes (refs/notes/polylogue) + PR/issue citation footers + SARIF pathology export,,ingest;interop,polylogue-l4kf,domain-implementation-slice,,receipt/provenance;regression-fixture,polylogue-rxdo.4;polylogue-svfj,,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:02:50Z,2026-07-13T04:02:50Z,area:ingest;area:interop;delivery:K-interop-origin-export;delivery:ac-patched;horizon:vision;lane:origin-interop-export;spine;tech-tree +polylogue-lu1,4,open,open,task,"Ambient theming: terminal respects the environment, webui gains a theme system",,surface;web,polylogue-bby,domain-implementation-slice,,live-evidence;bounded-resource;determinism/idempotency;restart/crash;security/authorization,,polylogue-9xuk,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T18:40:40Z,2026-07-08T18:40:40Z,area:surface;area:web;delivery:N-horizon;lane:horizon-spec +polylogue-mhx.1,4,open,open,feature,"Provider abstraction: one OpenAI-compatible embedding client, local and cloud, model registry in meta",,embeddings;substrate,polylogue-mhx,canonical-authority-owner,"Provider abstraction: one OpenAI-compatible embedding client, local and cloud, model registry in meta",live-evidence;receipt/provenance;parity/completeness;security/authorization;schema/contract;regression-fixture,,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/106_polylogue_mhx_1.md,origin-only=1,records-landed-work,embeddings,retain-distinct; no duplicate/contradiction found in this pass,2026-07-15T17:04:32Z,2026-07-15T17:04:32Z,area:embeddings;area:substrate;delivery:J-embeddings-retrieval;lane:embeddings-retrieval;spine +polylogue-mhx.4,4,open,open,feature,Semantic recall leg in context compilation: the memory actually retrieves,,context;embeddings;substrate,polylogue-mhx,domain-implementation-slice,,live-evidence;security/authorization;regression-fixture,polylogue-37t.11.1;polylogue-mhx.2,,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/109_polylogue_mhx_4.md,origin-only=1,,context,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:15Z,2026-07-08T20:15:15Z,area:context;area:embeddings;area:substrate;delivery:J-embeddings-retrieval;lane:embeddings-retrieval;spine +polylogue-mhx.5,4,open,open,feature,"Semantic analytics surfaces: topics/clustering, novelty, near-duplicate assist",,analytics;embeddings;substrate,polylogue-mhx,surface/adaptation-slice,,live-evidence;bounded-resource;regression-fixture,polylogue-9l5.7.2;polylogue-mhx.2,,,,no-explicit-paths,,analytics,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:35Z,2026-07-08T20:15:35Z,area:analytics;area:embeddings;area:substrate;delivery:J-embeddings-retrieval;delivery:ac-patched;lane:embeddings-retrieval +polylogue-mhx.6,4,open,open,task,"Embedding storage/spend efficiency: quantization, matryoshka, and scoped drain",,embeddings;ops;perf;substrate,polylogue-mhx,domain-implementation-slice,,bounded-resource;regression-fixture,polylogue-mhx.3,,,,no-explicit-paths,records-residual-work,embeddings,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:34Z,2026-07-08T20:15:34Z,area:embeddings;area:ops;area:perf;area:substrate;delivery:J-embeddings-retrieval;delivery:ac-patched;lane:embeddings-retrieval +polylogue-r47,4,open,open,feature,Obsidian/PKM export profile: sessions and findings as wiki-linked Markdown,,legibility;surface,polylogue-l4kf,domain-implementation-slice,,live-evidence;receipt/provenance;determinism/idempotency;schema/contract;operator-gate;regression-fixture,,,,,no-explicit-paths,,legibility,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:28Z,2026-07-07T13:00:28Z,area:legibility;area:surface;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-rlsb,4,open,open,feature,"Variant-aware projection, query, and reader render profiles",,cli;mcp;query;surface,polylogue-4smp,canonical-authority-owner,Prefer nudging these into one coherent registry/contract over adding another RendererProfile/overlay silo.,parity/completeness;schema/contract,polylogue-0v9p;polylogue-arso;polylogue-svfj,polylogue-1lm;polylogue-4p1;polylogue-ap7;polylogue-fnm.10;polylogue-fnm.2;polylogue-fnm.6;polylogue-jnj.1,,.agent/handoffs/polylogue-gpt-pro-2026-07-07/prework-v2/task_packets/077_polylogue_rlsb.md,origin-only=1,,cli,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:14:58Z,2026-07-08T20:14:58Z,area:cli;area:mcp;area:query;area:surface;delivery:E-variants-preferences;lane:variants-preferences;size:L +polylogue-ttu,4,open,open,task,"Docs information architecture: tiered index, orphan sweep, stale-doc triage",,devtools;legibility,polylogue-3tl,domain-implementation-slice,,live-evidence;schema/contract,,,,docs/archive/;docs/*.md;docs/retro/2026-05-24-1498-cascade.md;.agent/handoffs/polylogue-deep-research-2026-07-09/2026-07-09-external-legibility-audit.md;docs/security.md;docs/topology-status.md;devtools/docs_surface.py;docs/onboarding.md;docs/export.md;docs/site/pages.toml,both=7;origin-only=1;symbolic=2,records-landed-work+records-residual-work,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-14T14:41:43Z,2026-07-14T14:41:43Z,area:devtools;area:legibility;delivery:L-external-legibility;lane:docs-demos-launch;wave:2 +polylogue-uiw,4,open,open,feature,Origin breadth: enumerate the target set + generic openai-chat-shape detector,,ingest,polylogue-l4kf,domain-implementation-slice,,parity/completeness;determinism/idempotency;schema/contract;regression-fixture,polylogue-2qx.1.1,polylogue-611,,,no-explicit-paths,,ingest,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:37Z,2026-07-08T20:15:37Z,area:ingest;delivery:K-interop-origin-export;delivery:ac-patched;lane:origin-interop-export +polylogue-uqqi,4,open,open,task,Investigate why has_user_event is universally 0 in local state_5.sqlite,,cost,polylogue-2qx,domain-implementation-slice,,regression-fixture,,,,,no-explicit-paths,,cost,retain-distinct; no duplicate/contradiction found in this pass,2026-07-09T19:22:32Z,2026-07-09T19:22:32Z,area:cost;discovered-from:polylogue-ivsc +polylogue-utf,4,open,open,task,Devtools surface economy: usage-ranked consolidation of the 67-command catalog,,devtools,polylogue-t46,canonical-authority-owner,"(4) Mechanical execution: command_catalog.py is the single registry, so consolidation is spec edits + `render all`; add a catalog lint: new CommandSpec requires a whenToUse and a group cap check.",parity/completeness,,,,docs/devtools.md,both=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-13T04:14:31Z,2026-07-13T04:14:31Z,area:devtools;delivery:M-substrate-consolidation;delivery:ac-patched;lane:substrate-consolidation;wave:2 +polylogue-w8db,4,open,open,epic,Configuration doctrine + DB-backed runtime preferences,,ops,,program/invariant-container,"Guardrail shared with at44: typed key registry from day one, no free-form global KV; secrets stay out of user.db.",cancellation/cleanup;security/authorization,,,,,no-explicit-paths,,ops,retain program container; do not execute as a leaf,2026-07-08T20:15:08Z,2026-07-08T20:15:08Z,area:ops;delivery:E-variants-preferences;horizon:mid;lane:variants-preferences;spine +polylogue-wmj,4,open,open,feature,OTel GenAI trace export lane,,substrate,polylogue-l4kf,domain-implementation-slice,,live-evidence;security/authorization;schema/contract,,,,,no-explicit-paths,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:38Z,2026-07-08T20:15:38Z,area:substrate;delivery:K-interop-origin-export;horizon:vision;lane:origin-interop-export +polylogue-wohv,4,open,open,task,messages_fts UNINDEXED columns are write-only noise in a contentless table: drop or annotate,,substrate,polylogue-a7xr,domain-implementation-slice,,bounded-resource;security/authorization;schema/contract,,,,tests/;tests/unit/storage,both=1;symbolic=1,,substrate,retain-distinct; no duplicate/contradiction found in this pass,2026-07-07T13:00:23Z,2026-07-07T13:00:23Z,area:substrate;delivery:M-substrate-consolidation;lane:substrate-consolidation +polylogue-y0b,4,open,open,feature,"Generated codebase atlas: the grok report as a rendered, drift-checked doc",,devtools;legibility,polylogue-3tl,domain-implementation-slice,,parity/completeness;security/authorization,,,,docs/atlas.md,missing/planned=1,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:37Z,2026-07-08T20:15:37Z,area:devtools;area:legibility;delivery:L-external-legibility;delivery:ac-patched;lane:docs-demos-launch +polylogue-y8w,4,open,open,feature,"Reading preferences bundle: per-scope views, fold budgets, rows, pager",,surface,polylogue-w8db,canonical-authority-owner,Each pref = one registry key with scope-chain resolution (y4c machinery) + the consuming surface reading it: default view -> read view resolution (after 4pm fixes defaults); fold budgets -> 1lm preset selection; columns -> x7d row contract; auto-read -> find result cardinality check.,live-evidence;schema/contract,polylogue-37t.12;polylogue-y4c,polylogue-1lm;polylogue-4pm;polylogue-x7d,,,no-explicit-paths,,surface,retain-distinct; no duplicate/contradiction found in this pass,2026-07-08T20:15:30Z,2026-07-08T20:15:30Z,area:surface;delivery:E-variants-preferences;lane:variants-preferences +polylogue-z7xg,4,open,open,chore,devtools: move bead-lint-allow.txt to docs/plans/*.yaml for allowlist convention consistency,,devtools,,domain-implementation-slice,,unspecified/qualitative,,polylogue-okpn,,docs/plans/*.yaml;.agent/tools/bead-lint-allow.txt;.agent/tools/;docs/plans/backlog-hygiene-allowlist.yaml,origin-only=1;missing/planned=1;symbolic=2,,devtools,retain-distinct; no duplicate/contradiction found in this pass,2026-07-16T10:20:47Z,2026-07-16T10:20:47Z,area:devtools;discovered-from:dogfood-2 diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/AUDIT-STATS.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/AUDIT-STATS.json new file mode 100644 index 000000000..817d81b10 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/AUDIT-STATS.json @@ -0,0 +1,234 @@ +{ + "beads": { + "area_counts_multi_label": { + "adoption": 1, + "analysis": 1, + "analytics": 43, + "annotations": 3, + "api": 6, + "architecture": 16, + "attachments": 5, + "audit": 16, + "beads": 9, + "blob": 1, + "blobs": 1, + "browser": 4, + "browser-capture": 2, + "capture": 16, + "ci": 1, + "cleanup": 1, + "cli": 25, + "config": 5, + "context": 70, + "contracts": 1, + "control": 1, + "coordination": 12, + "correctness": 1, + "cost": 3, + "daemon": 52, + "delegation": 1, + "delegations": 1, + "demo": 1, + "demos": 15, + "devloop": 16, + "devtools": 28, + "docs": 8, + "durability": 1, + "dx": 1, + "embeddings": 9, + "eval": 1, + "events": 1, + "evidence": 5, + "experiment": 1, + "experiments": 2, + "git": 1, + "hooks": 2, + "identity": 1, + "ingest": 47, + "insights": 17, + "integration": 1, + "interop": 8, + "legibility": 35, + "lineage": 10, + "mandate": 2, + "mcp": 28, + "ops": 31, + "orchestration": 5, + "parsers": 2, + "perf": 30, + "performance": 2, + "pipeline": 2, + "planning": 2, + "privacy": 3, + "protocol": 4, + "query": 50, + "query-dsl": 3, + "release": 1, + "reliability": 1, + "rendering": 3, + "schema": 3, + "search": 3, + "security": 11, + "sinex": 1, + "sources": 29, + "storage": 53, + "substrate": 110, + "surface": 48, + "test": 36, + "test-harness": 5, + "unlabeled": 15, + "usage": 3, + "verification": 21, + "web": 36 + }, + "effective_active_after_history_repair": 554, + "effective_p0_p1": 114, + "effective_priority_counts": { + "P0": 15, + "P1": 99, + "P2": 138, + "P3": 194, + "P4": 108 + }, + "effective_status_counts": { + "in_progress": 19, + "open": 535 + }, + "hotspot_counts": { + "adoption": 1, + "analysis": 1, + "analytics": 43, + "annotations": 3, + "api": 6, + "architecture": 11, + "attachments": 5, + "audit": 14, + "beads": 2, + "beads-sync": 1, + "beads-sync-policy": 1, + "blob": 1, + "blobs": 1, + "browser": 3, + "browser-capture": 2, + "capture": 11, + "ci": 1, + "cleanup": 1, + "cli": 22, + "config": 4, + "context": 61, + "contracts": 1, + "coordination": 4, + "correctness": 1, + "cost": 3, + "daemon": 36, + "daemon-events": 1, + "daemon-supervision": 3, + "declaration-kernel": 2, + "definition-closure": 1, + "delegation": 1, + "demos": 10, + "destructive-mutations": 3, + "devloop": 7, + "devtools": 16, + "docs": 8, + "embeddings": 5, + "eval": 1, + "evidence": 2, + "extension-actions": 1, + "extension-capture": 1, + "extension-ux": 1, + "external-campaign": 1, + "ingest": 25, + "insights": 9, + "integration": 1, + "interop": 1, + "legibility": 23, + "lineage": 9, + "maintenance-declarations": 1, + "mandate": 2, + "marker-work-events": 2, + "mcp": 5, + "ops": 8, + "orchestration": 1, + "parsers": 2, + "perf": 5, + "pipeline": 1, + "privacy": 1, + "query": 23, + "query-declarations": 1, + "query-dsl": 2, + "query-execution": 1, + "query-scale-proof": 1, + "query-transaction": 1, + "raw-authority": 3, + "raw-authority-live-gate": 1, + "release": 1, + "rendering": 1, + "schema-workload-generation": 4, + "search": 1, + "security": 2, + "sources": 8, + "status-snapshots": 2, + "storage": 12, + "substrate": 43, + "surface": 12, + "test": 11, + "unlabeled": 14, + "verification": 2, + "verification-harness": 1, + "verification-risk": 1, + "web": 7 + }, + "snapshot_active": 555, + "snapshot_p0_p1": 114, + "snapshot_priority_counts": { + "P0": 15, + "P1": 99, + "P2": 138, + "P3": 195, + "P4": 108 + }, + "snapshot_status_counts": { + "in_progress": 18, + "open": 537 + }, + "total": 976 + }, + "graph": { + "active_children_with_closed_parent": [ + [ + "polylogue-b054.1.1", + "polylogue-b054.1" + ] + ], + "active_to_active_supersedes": [ + [ + "polylogue-20d.17", + "polylogue-703" + ] + ], + "effective_active_hard_edges": 235, + "proposed_changes": 19, + "snapshot_active_hard_cycle": false, + "snapshot_active_hard_edges": 235 + }, + "job_id": "analysis-01", + "limitations": [ + "No Beads mutation was performed.", + "No live daemon, browser, archive apply, deployment, or full test run was executed.", + "The operator/live values recorded in Bead notes were treated as tracker evidence, not independently reproduced.", + "The test-suite diet package was explicitly slightly stale and used only as a cross-check." + ], + "snapshot_generated_at": "2026-07-17T04:32:02Z", + "source_authority": { + "archived_tracked_files_with_content_changes_vs_local_head": 0, + "archived_untracked_file": "browser-extension/package-lock.json", + "archived_working_tree_commit": "f654480cadb7cc4c194704e24dfd483199547b35", + "archived_working_tree_dirty": true, + "bundled_origin_master": "0d081e5bc0b5e5bedcb3256d1779da8c0f091c65", + "origin_ahead_by_commits": 5, + "origin_delta_deletions": 117, + "origin_delta_files": 22, + "origin_delta_insertions": 1058 + } +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/DECISIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/DECISIONS.md new file mode 100644 index 000000000..5504791cb --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/DECISIONS.md @@ -0,0 +1,153 @@ +# Decisions + +Each record separates observed fact, source-supported inference, unresolved uncertainty, recommendation, preserved information, and falsification evidence. “Supersede” means the target record closes with a durable pointer and all unique acceptance/evidence is moved to the owner; it never means silently deleting context. + +## D-01 — Status authority + +**Observed fact.** `20d.17` explicitly supersedes active `703`; source has multiple rich status assemblers and only a whole-payload cache. + +**Inference.** `703` is a structural symptom/earlier formulation of the stronger component-snapshot contract, not an independent product authority. + +**Uncertainty.** The final home of the storage-free component protocol and exact component inventory are not implemented in source. + +**Decision.** Retain `20d.17`; supersede/close `703`; execute one serialized status branch. + +**Preserve.** 703's three-source inventory, one-assembly rule, fact parity across CLI/daemon/MCP/Python/web, and citation-drift fixture. Add 20d.17 freshness, deadline, last-good, source-fingerprint, component isolation, and resumable-detail semantics. + +**Falsification.** A current source/acceptance requirement owned by 703 that cannot be represented as a component or projection of 20d.17. + +## D-02 — Destructive mutation product model + +**Observed fact.** `t46.9` mandates a universal executor; `kwsb.2` explicitly rejects one. Current source removed a previous speculative generic operation framework because it had only one consumer. + +**Inference.** Keeping both open would create two authorization/dispatch authorities and invite direct-adapter bypasses. + +**Uncertainty.** The first two concrete domain pilots and exact shared transaction API are not yet in current source. + +**Decision.** Retain `kwsb.2`; supersede/close `t46.9`; use a shared `MutationTransaction` with domain-owned PlanSpec/actuator. + +**Preserve.** Operation inventory, declaration-derived discovery/help/schemas, bound preview/confirmation token, actor/archive/version/target digest, conflict/idempotency policy, receipts/postflight, and storage defense in depth. + +**Falsification.** Two production mutation domains demonstrate a single concrete executor that preserves domain planning/actuation without speculative unused kinds. + +## D-03 — Daemon supervision versus event transport + +**Observed fact.** P1 `avmq` hard-depends on P3 `yp0`; `avmq` calls EventBus adjacent transport. EventBus core has no production consumer; `run_daemon_services` owns tasks directly. + +**Inference.** The hard edge is a false blocker and priority inversion. Supervision can be implemented and tested without event-driven wakeups. + +**Uncertainty.** The final service registry may expose event subscriptions as optional metadata, but that does not make EventBus a prerequisite. + +**Decision.** Replace hard edge with soft relation. Implement `avmq`, `09rn`, and `enj7` together; wire `yp0` afterward. + +**Preserve.** Event vocabulary and failure isolation; slow reconciliation; service ownership/prerequisites/readiness/retry/shutdown/profiles/status identity. + +**Falsification.** A production supervisor cannot account for or start any required service without a wired EventBus. + +## D-04 — Raw-authority safety chain + +**Observed fact.** Scale proof, fixed-point execution, live operator authorization, and final closure have different acceptance semantics and code/live boundaries. Source contains substantive typed planner/apply/recovery/proof machinery. + +**Inference.** Merging the Beads would erase independent safety gates; missing dependency edges, not duplicate records, are the defect. + +**Uncertainty.** Tracker notes' live archive cardinalities and backup state were not reproduced. + +**Decision.** Add `hjpx → hjpx.2` and `yla8 → hjpx`; retain `lkrc → yla8`. Code cluster `hjpx.2/hjpx/lkrc`; separate live phase `yla8`. + +**Preserve.** Immutable plan/evidence identity, complete census, finite retry/fairness, bounded resources, stopped daemon, current backup, quiescence, exact operator authorization, postflight receipt, judgment path. + +**Falsification.** Equivalent accepted evidence already closes one prerequisite, or product scope is formally narrowed to remove that gate. + +## D-05 — Query execution residual ownership + +**Observed fact.** Shared execution control is implemented and production-routed at three surfaces, but many direct MCP/HTTP reads remain. The export regressed `z9gh.1` from a later in-progress record. + +**Inference.** Reimplementing execution control would duplicate landed authority; remaining work divides into core fairness/scale and transaction/surface migration. + +**Uncertainty.** The complete set of direct opens that are true query transactions versus harmless bounded metadata reads needs route-by-route confirmation during implementation. + +**Decision.** Restore `z9gh.1` in progress; keep fairness/core cleanup there; hard-depend on `4s3c`; move direct surface migration, HTTP disconnect, and resumed receipt to `z9gh.9.1`. + +**Preserve.** Dedicated read-only worker, progress interrupt, exact cleanup, shared admission, safe receipts, semantic no-refusal, advancing continuation, and generated adapter parity. + +**Falsification.** A direct site is not a query transaction, or accepted live evidence already covers the delegated scale/surface requirement. + +## D-06 — Declaration kernel and consumers + +**Observed fact.** No declaration package exists. Multiple Beads claim a shared kernel relationship, while maintenance already has a catalog/dispatch split and marker/work-event APIs are absent. + +**Inference.** Soft relations are insufficient; parallel registries will form unless consumers block on kernel identity/completeness. + +**Uncertainty.** The exact package name and minimum declaration schema are not fixed by current source. + +**Decision.** Add hard dependencies from `z9gh.3`, `9e5.31.1`, `71ey`, and `rii.1` to `o21.1`; relate `rii.1` to `37t.2.1`. + +**Preserve.** Domain semantic ownership and separate lowering/actuation adapters. Share only declaration identity, ownership, completeness, schema/introspection, and event-kind vocabulary. + +**Falsification.** `o21.1` scope explicitly excludes a consumer, or a newer accepted product decision authorizes a separate registry. + +## D-07 — Schema workload execution + +**Observed fact.** Root and three P1 children are all in progress on the same generated-schema hotspot; some journal work landed, while privacy and default-role questions remain. + +**Inference.** Parallel branches risk artifact/provenance drift; merging children would lose distinct acceptance semantics. + +**Uncertainty.** The feature branch versus current origin merge status may change after the snapshot. + +**Decision.** Keep all children; one owner/branch; order `.1` replay/memory/cancellation/live receipt, `.2` privacy/promotion, `.3` catalog roles/default, then parent closure. + +**Preserve.** All positive evidence families, privacy blocker/review distinction, deterministic output, cleanup, runtime resolution, default rationale, and promotion receipt. + +**Falsification.** Current merged source proves the children touch disjoint artifacts and can pass independently without generated-output conflict. + +## D-08 — Browser extension and campaign authority + +**Observed fact.** Provider-neutral transport/receipt and extension worker exist; capture/UX residuals remain; no campaign IDs leak into extension/receiver. + +**Inference.** Transport, capture truth, presentation policy, and multi-step orchestration are layered, not duplicates. + +**Uncertainty.** Live provider/project/failure matrix and deployed parity were not available. + +**Decision.** Keep `ptx`, `3v1`, `yyvg.7`, `yyvg.6`; soft-relate `yyvg.7` to `3v1`; serialize extension/receiver writes; keep campaign branch separate. + +**Preserve.** Durable `outcome_unknown` handling, exact reconciliation receipt, gap evidence, exception-only attention, external private campaign ledger, and no extension-owned campaign state. + +**Falsification.** A new accepted product decision moves campaign identity/ledger into the receiver, with corresponding source and migration plan. + +## D-09 — Beads monotonic synchronization + +**Observed fact.** Repository instructions describe stale branch reimport; two current rows are older than Git history; guard coverage is hook-local. + +**Inference.** These are real regression fixtures for `gxjh.1`, not isolated bookkeeping mistakes. + +**Uncertainty.** The live Dolt DB was not available, so the exact checkout sequence was reconstructed from Git/history and repository documentation. + +**Decision.** Restore `z9gh.1` and `xnws`; add both to `gxjh.1`; keep `8jg9.1` blocked by it; apply all tracker changes in one immediately merged branch. + +**Preserve.** Per-row latest status/assignee/parent/dependencies/close reason, transactionality, conflict refusal, complete receipt, and post-checkout re-verification. + +**Falsification.** A later tracker revision or a different proven corruption mechanism explains either row. + +## D-10 — Verification parentage + +**Observed fact.** Active `b054.1.1` is parented to closed audit `b054.1`; `88jp` is the active verification-risk program. + +**Inference.** The closed parent is historical provenance, not an executable owner. + +**Uncertainty.** Another active verification program may be preferred by maintainers, but none is better supported in the snapshot. + +**Decision.** Reparent to `88jp`; retain historical relation to `b054.1`. + +**Preserve.** Bounded seed/resume, deterministic cleanup, zero unowned baseline quarantine, repeated witness SLO, and child proof lanes. + +**Falsification.** `b054.1` is intentionally reopened as the implementation program or a better active parent is adopted. + +## D-11 — Layered lookalikes retained + +**Observed fact.** Several clusters share vocabulary or files but have different semantic authority: query algebra/transaction/adapters, rebuild exclusivity/raw authority/freshness, scenario catalog/live gate, inbound/outbound assets, and parent/implementation/proof families. + +**Inference.** Collapsing them would reduce count while discarding safety or acceptance meaning. + +**Decision.** Retain them as distinct. Use dependencies and branch serialization where they write the same hotspot. + +**Falsification.** A source-validated owner demonstrably covers every distinct acceptance criterion and preserves all evidence/authority boundaries. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..2b8f7ef09 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EVIDENCE.md @@ -0,0 +1,185 @@ +# Evidence ledger + +## Evidence authority and snapshot integrity + +The primary archive was inspected directly, including its working-tree tar, all-ref Git bundle, rendered Bead export, manifest, snapshot audit, repository instructions, and source slices. The outer and nested tar member names were checked for traversal. The working-tree tar has 2,779 members and one safe symlink, `AGENTS.md → CLAUDE.md`. + +The manifest contains 42 artifact entries. Every non-self artifact with a digest matched its SHA-256. `polylogue-manifest.json` intentionally has `sha256: null`; its recorded 18,941 bytes differ from the final 18,945-byte file because a manifest cannot stably hash/count its own post-serialization content without an external convention. No ordinary artifact digest failed. + +The archived source declares local `master` `f654480cadb7cc4c194704e24dfd483199547b35` and `dirty=true`. Hash comparison found no content change in any archived tracked file relative to that commit and one untracked file, `browser-extension/package-lock.json`; many tracked hidden/agent files were intentionally absent from the working-tree tar but present through other snapshot slices/Git. The all-ref bundle contains later `origin/master` `0d081e5bc0b5e5bedcb3256d1779da8c0f091c65`, five commits and 22 files ahead (+1058/−117). Those later commits are: + +- `0dc5773a9` — preserve complete raw frontier counts (#2965) +- `805d49286` — stream safe raw authority replay (#2966) +- `9ddfc8d81` — raw authority scale proof runner (#2967) +- `067c87e49` — bound schema journal replay transactions (#2968) +- `0d081e5bc` — guard and meter raw authority proofs (#2970) + +The source basis is therefore explicit in every conclusion: archived local source for snapshot reality, later bundled origin where it contains a superseding committed implementation. + +## Repository workflow evidence + +`CLAUDE.md:263-276` defines Beads as durable tracker/devloop and requires graph lint before shipping Bead deltas. `CLAUDE.md:278-289` documents that checkout/merge hooks re-import the checked-out branch's `.beads/issues.jsonl`, and that an older branch can make a closed Bead appear open. `CLAUDE.md:291-304` forbids sibling bookkeeping branches and recommends folding tracker state into the code branch or merging bookkeeping immediately. `CLAUDE.md:305-308` requires re-reading a Bead after checkout/merge/worktree creation. `CLAUDE.md:309-317` requires hand-merging JSONL by ID and preferring later `updated_at`, rather than using `bd export` during conflict resolution. + +`.agent/scripts/bd-reimport-guard.py:2-19` confirms plain-upsert behavior and wraps hooks with before/after snapshots. Lines 103-139 restore rows only when the pre-hook `updated_at` is newer or the row vanished, then re-export. This is useful but not the full monotonic/import/merge/receipt contract described by `gxjh.1`. + +## Tracker-history reconstruction + +All historical `.beads/issues.jsonl` versions reachable from bundled refs were parsed by issue ID. There were 11,162 row versions for 1,067 IDs. Exactly two current export rows had a strictly later per-ID record in Git history: + +| Bead | Export | Later history | Decision | +| --- | --- | --- | --- | +| `z9gh.1` | open, unassigned; updated 2026-07-16 12:56:52Z | in_progress, Sinity; updated 2026-07-17 00:25:42Z at `6610f3b0` | restore in_progress/assignee; do not infer closed from the commit subject | +| `xnws` | open; updated 2026-07-16 23:43:40Z | closed; updated 2026-07-16 23:59:23Z at `6610f3b0`; close reason cites PR #2963 | restore closed | + +The `z9gh.1` commit subject says “close,” but the actual later row is `in_progress`; the row, not the subject, controls. This distinction prevents inventing a closure that the tracker record does not support. + +## Complete active-graph evidence + +The current export contains 976 issues: 537 open, 18 in progress, and 421 closed. Snapshot-active count is 555; effective active count is 554 after restoring `xnws` closed. Priorities among snapshot-active rows are P0=15, P1=99, P2=138, P3=195, P4=108. The active hard graph has 235 edges and no cycle. There are no exact duplicate active titles and no active row has multiple parent-child edges. The only active-to-active explicit supersession is `20d.17 → 703`; the only active child with a closed parent is `b054.1.1 → b054.1`; the only P0/P1 hard dependency on a P3/P4 row is `avmq → yp0`. + +`ACTIVE-CENSUS.csv` is the complete row-level evidence for this section. + +## High-value Bead records + +| Bead | State | Authority evidence | Recorded dependencies | +| --- | --- | --- | --- | +| polylogue-20d.17 | open P1 | Define one StatusComponentSpec and StatusSnapshot protocol reused by daemon/archive and agent-coordination status. | parent-child→polylogue-20d, relates-to→polylogue-20d.14, supersedes→polylogue-703, relates-to→polylogue-cuxz, relates-to→polylogue-s7ae.8 | +| polylogue-703 | open P2 | The convergence-snapshot bead (4bu) defines the shared payload for converging-state; this bead is the structural follow-through: ONE status assembly module in the substrate comput… | parent-child→polylogue-t46 | +| polylogue-kwsb.2 | open P1 | The missing abstraction is a shared transaction protocol, not a universal mutation executor. | parent-child→polylogue-kwsb | +| polylogue-t46.9 | open P1 | Polylogue already declares operation safety in operations/specs.py and action_contracts.py, but CLI, API, MCP, daemon, maintenance, and repair adapters still enforce role, dry-run… | relates-to→polylogue-a7xr.18, relates-to→polylogue-jn40, relates-to→polylogue-jnj.5, parent-child→polylogue-t46, relates-to→polylogue-t46.8 | +| polylogue-avmq | open P1 | Supervise every daemon background service through one lifecycle contract | blocks→polylogue-yp0 | +| polylogue-yp0 | open P3 | Adding an event/subscriber requires one declared registry entry and completeness checks, not bespoke loop wiring. | blocks→polylogue-9e5.7, parent-child→polylogue-a7xr | +| polylogue-hjpx | in_progress P0 | 2026-07-17 static follow-up from yla8 read-only preflight: current live execution is blocked by one non-stream-safe authority component over the 1 GiB bounded replay envelope. | parent-child→polylogue-lkrc, discovered-from→polylogue-yla8 | +| polylogue-hjpx.2 | in_progress P1 | hjpx AC6 remains unproven after the execution-foundation phase. Small unit fixtures exercise 25 singleton raws and skewed cohorts, but they do not match the 2026-07-15 preflight s… | parent-child→polylogue-hjpx, blocks→polylogue-hjpx.1 | +| polylogue-yla8 | in_progress P0 | Chunk 1 rejected two full replays while preserving the accepted index: (1) a 25,898,236-byte ChatGPT browser capture for session 69d5383e-69d0-8327-a899-94a89ff35ea4 hit "conflict… | parent-child→polylogue-1xc, relates-to→polylogue-1xc.13, relates-to→polylogue-b5l.2, relates-to→polylogue-n2wy | +| polylogue-lkrc | in_progress P0 | Polylogue has accumulated separate repair actuators and incident Beads for origin-mismatched browser raws, competing canonical heads, duplicate raw identities, replaced snapshots… | parent-child→polylogue-1xc, relates-to→polylogue-1xc.13, related→polylogue-2qx, relates-to→polylogue-b5l.1, blocks→polylogue-yla8, discovered-from→polylogue-yla8.10 | +| polylogue-z9gh.1 | open P0 | Cancellation, deadlines, off-event-loop execution, and admission control belong to the shared query transaction rather than MCP-specific wrappers. | relates-to→polylogue-1xc.14, related→polylogue-20d.14, related→polylogue-oxz, parent-child→polylogue-z9gh.9 | +| polylogue-z9gh.9.1 | open P0 | Land the shared query transaction across every read surface | supersedes→polylogue-20d.5, relates-to→polylogue-7q16, relates-to→polylogue-9l5.6, relates-to→polylogue-rxdo.3, blocks→polylogue-z9gh.1, blocks→polylogue-z9gh.2, relates-to→polylogue-z9gh.3, parent-child→polylogue-z9gh.9 | +| polylogue-o21.1 | open P1 | Missing producer, handler, role gate, schema, example, generated output, or consumer edge produces one source-locatable Diagnostic containing declaration id, owner path, and exact… | relates-to→polylogue-1xc.14, parent-child→polylogue-o21 | +| polylogue-z9gh.3 | open P0 | One query declaration registry generates MCP schemas/descriptions, a searchable capability/coverage catalog resource, typed structured-plan input, DSL completions/help, OpenAPI/do… | related→polylogue-o21, relates-to→polylogue-o21.1, related→polylogue-t46.8, parent-child→polylogue-z9gh, relates-to→polylogue-z9gh.9.1 | +| polylogue-9e5.31.1 | open P1 | Seed representative policies: one durable data family, one event/write-effect family, one registry/declaration family, one query parse-to-render path, and one CLI/MCP/HTTP/Python… | parent-child→polylogue-9e5.31 | +| polylogue-71ey | open P1 | The canonical maintenance target catalog advertises seven targets, but resumable replay has a private six-target _REPLAY_DISPATCH that omits superseded_raw_snapshots. The generate… | discovered-from→polylogue-9e5.31, parent-child→polylogue-o21, relates-to→polylogue-o21.1, relates-to→polylogue-sl1 | +| polylogue-rii.1 | open P2 | record_work_event/emit_decision write surface routed through the existing idempotent ingest seam (no parallel writer); flows into the run-projection read models. Today agents can… | parent-child→polylogue-rii | +| polylogue-37t.2.1 | open P1 | Implement the provider-neutral author-declared structure channel: collision-tested line/inline syntax parsed at block enrichment, exact message/block provenance, malformed evidenc… | parent-child→polylogue-37t.2, relates-to→polylogue-o21, blocks→polylogue-o21.1 | +| polylogue-ptx | in_progress P0 | One versioned BrowserActionIntent contract can create a new Chat conversation or reply to an exact existing conversation with text plus multiple hash-pinned attachments; successfu… | blocks→polylogue-3v1.1, blocks→polylogue-83u.3, blocks→polylogue-83u.4, parent-child→polylogue-bby, blocks→polylogue-kwsb.1 | +| polylogue-3v1 | in_progress P1 | The extension (MV3, popup + badge, content bridges for chatgpt.com/claude.ai/grok.com/x.com) works end-to-end but its trust surface is thin: the operator cannot tell at a glance w… | parent-child→polylogue-jlme | +| polylogue-yyvg.7 | in_progress P0 | F4/ys30/yyvg.4 owns capture dot and Save action in provider-native per-message action rows, resolved by one ProviderAdapter identity contract; no ordinal/text-only durable authori… | parent-child→polylogue-yyvg | +| polylogue-yyvg.6 | in_progress P0 | The orchestrator owns MissionId, RunId, IterationId, DeliverableId, PackageRevisionId, readable prompt/attachment/result filenames, retry/alternative/supersedes lineage, prompt pr… | parent-child→polylogue-yyvg | +| polylogue-gxjh.1 | open P1 | Import compares project/database/branch identity and per-row revision/updated_at in one transaction, creates missing rows, accepts demonstrably newer rows, preserves equal rows, a… | parent-child→polylogue-8jg9 | +| polylogue-8jg9.1 | open P1 | Standalone .agent/tools/bead-lint.py removed (superseded); allowlist at .agent/tools/bead-lint-allow.txt unchanged. | parent-child→polylogue-b054, blocks→polylogue-gxjh.1 | +| polylogue-b054.1 | closed P1 | Audit the full open set for cases where one executable abstraction, normalized relation, declarative registry, transaction boundary, or authority rule can make several special cas… | parent-child→polylogue-b054 | +| polylogue-b054.1.1 | in_progress P1 | Fresh-worktree and warm-cache runs emit machine-readable receipts consumed by devtools verify and agent guidance, with one tested configuration contract across focused, xdist, see… | relates-to→polylogue-09rn, parent-child→polylogue-b054.1, discovered-from→polylogue-hjpx, relates-to→polylogue-vyxq, relates-to→polylogue-wple, relates-to→polylogue-y6tb | +| polylogue-88jp | open P2 | Absorbs remaining metric-only scope of c52g, znwj, and n4hb; their landed PR #2787 behavior tests become evidence inputs. | relates-to→polylogue-09rn | +| polylogue-xnws | open P3 | WHY: the evidence-honesty doctrine says degraded modes are loud and unknown never renders as zero/blank, but no census exists of except-and-continue sites in production code. Dogf… | — | + +Short, decision-bearing record text: + +- `20d.17` design defines one `StatusComponentSpec`/`StatusSnapshot` protocol; its note says it “absorbs polylogue-703,” and the dependency list has `supersedes→polylogue-703`. +- `703` requires one shared status assembly and a CLI/daemon/MCP/Python/web parity suite with a citation-drift fixture. That evidence must move, not disappear. +- `kwsb.2` says: “The missing abstraction is a shared transaction protocol, not a universal mutation executor.” +- `t46.9` says: “Make OperationSpec the single executable declaration and add an OperationExecutor used by every external and internal mutation route.” +- `avmq` says event delivery is adjacent to service supervision and that EventBus remains transport; `yp0` says the core is landed and production loop conversion is follow-up work. +- `hjpx.2` says `hjpx` AC6 remains unproven and owns the July-15-shape scale proof; `hjpx` calls itself the executable successor to the failed live gate; `yla8` retains operator authorization and fail-closed protections; `lkrc` owns final authority closure. +- `rii.1` says marker kinds and work-event kinds are one channel with two encodings; `37t.2.1` already hard-depends on `o21.1`. +- `gxjh.1` requires per-row revision/`updated_at` monotonicity, conflict refusal, transactionality, and receipts; `8jg9.1` already hard-depends on it. + +## Source evidence by hotspot + +### Status + +- `polylogue/daemon/status_snapshot.py:50-75` defines a single whole-payload `StatusSnapshot` with captured time, age, fresh/stale state, and refresh error. +- `status_snapshot.py:292-336` returns that whole cached payload and refreshes it from `daemon_status_payload`; refresh is process-global and lock-guarded, not independently componentized. +- `polylogue/daemon/status.py:1986-2033` begins the large `build_daemon_status` and synchronously assembles DB/storage/FTS/freshness/raw frontier/replay/cursor/debt/failure/embedding facts. +- `polylogue/cli/commands/status.py:1376-1404` first calls `/api/status`, then falls back to independent direct JSON/text status assembly. +- `polylogue/coordination/envelope.py:249-291` has a separate coordination projection path. +- Repository-wide search found no `StatusComponentSpec` symbol. + +This source supports absorption of 703 into 20d.17 but does not prove the component protocol has landed. + +### Destructive mutations + +- `polylogue/operations/specs.py:47-62` defines `OperationSpec` as metadata: consumes/produces/path/code/surfaces, mutability, previewability, idempotency, effects, and safety guards. +- `polylogue/operations/operation_contract.py:3-12` records that the previous generic `OperationRequest`/`OperationAck` framework was removed because only IMPORT had a production consumer, and says to reintroduce a shared base only when a second operation lands. +- `operation_contract.py:14-26` retains only genuinely reused scheduling status/follow-up shapes and says the wire envelope is unchanged. + +This directly contradicts using `t46.9` to preemptively make a universal executor the product authority. It does not reject shared transaction primitives; that is why `kwsb.2` survives. + +### Daemon lifecycle and event transport + +- `polylogue/daemon/cli.py:1089-1108` exposes one very large `run_daemon_services` composition function. +- `daemon/cli.py:1252,1281,1311,1321,1387,1417,1424` creates maintenance, browser receiver, API, UDS, periodic, catch-up, and watcher tasks directly; `1434-1449` gathers them. +- `polylogue/daemon/event_bus.py:1-48` explicitly says the core lands typed pub/sub and production loop conversion is follow-up work. +- `event_bus.py:120-129` says one bus is meant to be constructed in `run_daemon_services`, but repository-wide production search found no reference to `EventBus` or its event types outside that module. +- No `DaemonServiceSpec` or `ServiceHarness` symbol exists. + +This supports separate lifecycle and transport authorities and falsifies the current hard blocker. + +### Raw authority + +Later bundled origin source supplies the current committed implementation: + +- `polylogue/storage/raw_reconciler.py:51-80` defines mutually exclusive frontier states and admitted actuators; only safely rekeyable and duplicate-alias states are executable. +- `raw_reconciler.py:99-147` binds every item to state, actuator, evidence digest, preconditions, witness, inputs, and stable plan ID; the census records complete state counts and plan inventories. +- `raw_reconciler.py:949-985` persists a complete census under the offline/daemon exclusion boundary, computes plans, and records a dry-run receipt with complete frontier counts. +- `raw_reconciler.py:1197-1243` requires a unique selected plan set, verifies membership in the preview, re-derives current plans, refuses non-executable plans, and records the apply census. +- `raw_reconciler.py:1316-1349` recovers interrupted planned applications from typed durable evidence. +- `devtools/raw_authority_scale_proof.py:40-68` records pass cardinality, fixed point, wall time, RSS/PSS/swap, CPU, and I/O. +- `raw_authority_scale_proof.py:109-124` samples Linux process and pressure evidence. +- `raw_authority_scale_proof.py:164-219` invokes the real `repair_raw_materialization`, rejects incomplete metrics, requires a census receipt, and emits resource/convergence evidence. + +The source proves meaningful implementation has landed but not the Bead-level live closure. It supports the ordered proof/executor/live-gate/closure chain. + +### Query execution and residual surface migration + +- `polylogue/archive/query/execution_control.py:126-183` defines immutable query identity, workload class, deadline, cancellation event, receipt, and abort checks. +- `execution_control.py:186-223` defines weighted admission, FIFO within class, and reserved interactive capacity. +- `execution_control.py:296-337` opens one dedicated read-only `ArchiveStore` and installs a cancellation/deadline progress guard. +- `execution_control.py:417-474` runs reads off the event loop, translates disconnect cancellation into exact connection interrupt, and drains/receipts cleanup. +- `execution_control.py:477-503` supplies the synchronous HTTP variant sharing the process-wide admission controller. +- Production consumers exist at `polylogue/api/archive.py:2907-2958`, `polylogue/mcp/server_tools.py:303-333`, and `polylogue/daemon/http.py:3507-3533`. +- Direct MCP opens remain at `server_tools.py:240,394,699,966,1079` and in `server_resources.py`/`server_prompts.py`; direct daemon HTTP opens remain at `http.py:2202,2570,2816,2911,3096,3947`. + +This is source evidence for a shipped core plus a residual transaction/surface migration, not a reason to reopen a parallel execution architecture. + +### Declaration, maintenance, and event-kind registry + +- `polylogue/declarations/` is absent in both archived local and later origin trees. +- `polylogue/maintenance/targets.py:21-68` defines `MaintenanceTargetSpec` and its canonical catalog metadata. +- `targets.py:158-234` advertises `superseded_raw_snapshots` among maintenance targets. +- `polylogue/maintenance/replay.py:101-128` maintains a separate private `_REPLAY_DISPATCH`; that dispatch lacks `superseded_raw_snapshots`. +- `replay.py:594-624` turns an advertised-but-unwired target into a typed unsupported failure, explicitly telling maintainers to edit the private dispatch. +- `polylogue/storage/sqlite/queries/session_events.py:48-118` provides typed session-event reads. +- Repository-wide source search found no `record_work_event` or `emit_decision` API. + +This supports one shared declaration kernel and separate domain adapters. It also gives 71ey a concrete current-source pilot rather than an abstract architecture exercise. + +### Schema workload generation + +- `polylogue/schemas/generation/observation_journal.py:159` defines the journal; `:210` creates a permission-restricted local SQLite journal; later iterators replay profile/unit/membership/terminal evidence; `:1044` owns close/cleanup. +- `workload_profiles.py:370-404` emits aggregate structural profiles with explicit privacy policy/classification. +- `archive_workload_profile.py:715-795` builds privacy review metadata and writes deterministic gzip artifacts for staging/promotion review. +- `provider_bundle_packages.py:74-97,269-316` selects and publishes latest/default/recommended catalog roles. +- Feature branch `feature/feat/schema-workload-profiles` contains additional cleanup, order invariance, privacy, provenance, and receipt commits; later origin #2968 bounds journal replay transactions. + +These files overlap heavily, but the three child acceptance semantics remain different. The evidence supports serialized execution, not merging the children. + +### Browser action, capture, UX, and campaign boundary + +- `polylogue/browser_capture/models.py:347-375` defines provider-neutral operations, terminal statuses, and outcomes including `outcome_unknown`. +- `models.py:450-464` defines the exact provider-observation receipt; `:477-495` defines durable receiver-authoritative `BrowserActionIntent` state. +- `polylogue/browser_capture/actions.py:343-370` converts an expired durable submit intent to `outcome_unknown`, rather than retrying blindly. +- `actions.py:430-508` enforces terminal idempotency/conflict and lease-owner updates; `:511-533` only reconciles an `outcome_unknown` action with an exact receipt. +- `browser-extension/src/background.js:2048-2217` contains the action transport/worker and ambiguous-submit handling. +- Search found no `MissionId`, `CampaignId`, `mission_id`, or `campaign_id` in extension/receiver source. + +This supports four distinct authorities and a hard boundary against extension-owned campaign orchestration. + +## Stale test-suite package cross-check + +The separate package labels itself “slightly stale,” so it was not used to override source or Beads. It independently names `lkrc`/`yla8` as evidence-authority and authorization owners, `z9gh.1` as query cancellation/bounds owner, and `20d.17` as the status component-snapshot contract. That agreement increases confidence in the retained ownership boundaries but adds no authority beyond current source/tracker history. + +## Negative evidence and limits + +Negative source searches were used only where the snapshot was broad enough to make them meaningful: no `StatusComponentSpec`, `DaemonServiceSpec`, `ServiceHarness`, declaration package, production EventBus consumer, `record_work_event`, `emit_decision`, or campaign-ID leakage. Absence in source does not prove absence in a live uncommitted worktree outside the snapshot. + +No live browser, daemon, deployment, archive apply, or full test evidence was available or claimed. No Bead mutation was performed. The proposed changes remain recommendations until a local implementer applies them, re-reads the changed rows after checkout, runs graph lint, and verifies current source/tests. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EXECUTION-CLUSTERS.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EXECUTION-CLUSTERS.csv new file mode 100644 index 000000000..47f2599fe --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/EXECUTION-CLUSTERS.csv @@ -0,0 +1,17 @@ +cluster_id,name,branch_policy,beads,write_hotspots,acceptance_gate,ordering +C00,tracker-repair,same bookkeeping branch; merge immediately,"z9gh.1,xnws,20d.17,703,kwsb.2,t46.9,avmq,yp0,b054.1.1,88jp,gxjh.1",No production code,Restore per-ID latest records; apply supersedes/reparent/edge fixes; run graph lint; re-read every changed row after checkout.,First +C01,raw-authority-code,one serialized code branch,"hjpx.2,hjpx,lkrc",polylogue/storage/raw_reconciler.py;polylogue/storage/raw_authority.py;polylogue/storage/repair.py;polylogue/storage/sqlite/archive_tiers/archive.py;polylogue/archive/revision_authority.py;polylogue/sources/revision_backfill.py;polylogue/sources/live/batch.py;polylogue/sources/live/append_ingest.py;devtools/raw_authority_scale_proof.py,"Scale receipt proves finite-retry convergence, bounded RSS/PSS/swap/temp/I/O, plan conservation, and two-census fixed point.",After C00 +C02,raw-authority-live-gate,separate no-code operator execution phase,"yla8,lkrc",Live archive and receipts only,Stopped daemon; verified current backup; quiescent preflight; exact immutable plan authorization; bounded apply; postflight census; rollback/judgment path.,After C01 review +C03,query-execution-core,one branch,"z9gh.1,4s3c",polylogue/archive/query/execution_control.py;query executor tests;live scale harness,Cross-class fairness and live resource envelope; no remaining core cleanup leak; dedicated reader interruption remains production-routed.,After C00 +C04,query-transaction-surfaces,one serialized branch after C03,"z9gh.9.1,t46.8.2,t46.8.2.1",polylogue/mcp/**;polylogue/daemon/http.py;polylogue/api/archive.py;query paging/spool contracts,Migrate remaining read sites; HTTP disconnect; resumed receipt; MCP generated adapter parity; no duplicate tool alias.,After C03 and t46.8.1 +C05,query-declarations,shared branch with explicit semantic/protocol boundary,"o21.1,z9gh.3,t46.8.1",new declaration kernel;query declaration/lowering;MCP declaration projection,One identity/completeness kernel; query semantics stay z9gh.3; MCP projection stays t46.8.1; no second registry.,Kernel first +C06,status-snapshots,one serialized hotspot branch,20d.17,polylogue/daemon/status.py;polylogue/daemon/status_snapshot.py;polylogue/cli/commands/status.py;polylogue/coordination/envelope.py;MCP/HTTP/web projections,"Component isolation, deadlines, last-good, source-fingerprint invalidation, compact/detail parity, live p95 and byte budgets; 703 evidence embedded.",After C00 +C07,daemon-supervision,one branch,"avmq,09rn,enj7",polylogue/daemon/cli.py;new service registry/supervisor;lifecycle tests,Inventory every spawned task; deterministic dependency order; isolated failure; bounded shutdown; production-derived named profiles; no orphan task.,Independent of yp0 +C08,daemon-events,separate downstream branch,"yp0,14t7",polylogue/daemon/event_bus.py;publisher/subscriber wiring,Committed-domain events only; slow reconciliation remains truth; first production consumer; reduced polling measurement.,After C07 registry is stable +C09,destructive-mutations,"one security-sensitive branch per domain pilot, common transaction substrate","kwsb.2,t46.9(absorbed),jn40",mutation transaction;domain PlanSpec/actuators;surface adapters;storage guards,Bound actor/archive/version/target digest; preview-stale; role parity; durable receipt; no universal speculative executor.,After C00 +C10,declaration-consumers,"kernel first, then separate domain branches","9e5.31.1,71ey,rii.1,37t.2.1",definition closure;maintenance targets/replay;marker/work-event lowering;session_events/services,All consume o21.1 identity/completeness; no parallel registry; one marker/work-event vocabulary with distinct adapters.,After C05 kernel +C11,schema-workload-generation,one serialized owner branch,"1xc.14.1,1xc.14.1.1,1xc.14.1.2,1xc.14.1.3",polylogue/schemas/generation/**;promotion/audit/runtime resolution;generated artifacts,Order: bounded journal/replay → privacy/promotion → catalog roles/default → joint live canaries and receipts.,After C00 claim normalization +C12,extension-actions-capture-ux,serialized branches over shared extension/receiver hotspot,"ptx,3v1,yyvg.7",polylogue/browser_capture/**;browser-extension/src/**;capture/status tests,"Provider-neutral action receipts, capture gap truth, exception-only UX, live provider/project/failure/deployed parity; no campaign ledger in extension.",Transport before UX closure +C13,external-campaign,separate local-tooling branch,yyvg.6,scripts/local orchestration/private ledger only,Campaign retries/convergence consume ptx/3v1 evidence without leaking campaign IDs or ownership into extension/receiver.,After transport contracts stable +C14,beads-sync,one branch; no sibling bookkeeping branches,"gxjh.1,8jg9.1",.agent/scripts/bd-reimport-guard.py;Beads import/export/merge tests;policy consumer,"Per-row monotonic union, incomparable conflict refusal, atomic receipt, status/parent/dependency preservation, z9gh.1/xnws regression fixtures.",Before broad graph edits if implementation is ready +C15,verification-harness,one harness root with separate proof lanes,b054.1.1 and children;88jp,verification selection/seed receipts;process cleanup;baseline manifest,"Bounded fresh-checkout seed, durable resume, zero unowned quarantine, repeated witness SLOs, baseline-aware affected selection.",After reparent diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/GRAPH-CHANGES.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/GRAPH-CHANGES.csv new file mode 100644 index 000000000..74d49aff7 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/GRAPH-CHANGES.csv @@ -0,0 +1,20 @@ +change_id,priority,action,source_id,relation,target_id,phase,evidence,preserve,falsification +G01,P0,restore-status,polylogue-z9gh.1,status,in_progress,tracker-repair,Export row updated 2026-07-16T12:56:52Z is older than per-ID Git history row updated 2026-07-17T00:25:42Z at 6610f3b0; later row is in_progress and assigned Sinity.,"Keep PR #2964 implementation evidence and residual fairness, scale, migration, disconnect, and resumed-receipt obligations.","A still later row for z9gh.1, or a merged close commit whose issue record is newer than 00:25:42Z." +G02,P1,restore-status,polylogue-xnws,status,closed,tracker-repair,Export row updated 2026-07-16T23:43:40Z is older than Git history row updated/closed 2026-07-16T23:59:23Z at 6610f3b0; close reason cites merged PR #2963.,"Keep the 266→35 calibrated census, all 35 classifications, 14 direct fixes, and the explicit 160-site lower-tier disposition.",A later valid reopen record with new residual acceptance criteria. +G03,P1,supersede-close,polylogue-20d.17,supersedes,polylogue-703,tracker-repair,20d.17 has the only active→active supersedes edge and its note explicitly says it absorbs 703; 20d.17 defines the stronger component-snapshot protocol.,"Move 703 fact inventory, one-assembly invariant, and CLI/daemon/MCP/Python/web parity plus citation-drift fixture into 20d.17.",A source path where 703 owns semantics not expressible as a StatusComponentSpec component/projection. +G04,P1,supersede-close,polylogue-kwsb.2,supersedes,polylogue-t46.9,tracker-repair,kwsb.2 requires a shared transaction protocol and explicitly rejects a universal mutation executor; t46.9 requires a universal OperationExecutor. Current operation_contract.py documents removal of the previous speculative generic framework.,"Map OperationSpec inventory to domain PlanSpec/actuators, derive surface help/schema/role discovery, retire direct adapter calls after parity, retain storage guards.",Two production mutation domains already share one concrete executor without erasing domain-owned planning/actuation semantics. +G05,P1,remove-hard-add-soft,polylogue-avmq,blocks→relates-to,polylogue-yp0,graph-repair,"avmq calls EventBus adjacent transport, not lifecycle authority. yp0 core exists but has no production references outside its module. The edge makes P1 supervision wait on P3 transport wiring.",Keep yp0 event vocabulary/failure isolation and let it consume the future service registry downstream.,A service-supervisor acceptance criterion that cannot be implemented without a wired EventBus. +G06,P0,add-hard-dependency,polylogue-hjpx,blocks,polylogue-hjpx.2,graph-repair,hjpx acceptance requires the July-15-shaped bounded convergence proof; hjpx.2 is that proof and says hjpx AC6 remains unproven.,Keep the fixed-point executor separate from the scale harness while making proof an explicit completion prerequisite.,"hjpx acceptance is rewritten to exclude scale closure, or equivalent accepted proof is attached elsewhere." +G07,P0,add-hard-dependency,polylogue-yla8,blocks,polylogue-hjpx,graph-repair,yla8 notes retain the live authorization gate only after the successor fixed-point proof; hjpx says it is the executable successor to the failed live gate.,"Keep operator authorization, stopped-daemon/backup/quiescence checks, and live receipts outside the code implementation branch.",A reviewed live gate that does not rely on fixed-point executable-plan conservation. +G08,P0,retain-edge,polylogue-lkrc,blocks,polylogue-yla8,graph-validation,Existing edge correctly keeps final authority/reconciler closure behind the authorized live gate.,"Classification, immutable plan identity, postflight census, and accountable closure.",lkrc is redefined as purely offline classification with no live closure acceptance. +G09,P0,add-hard-dependency,polylogue-z9gh.1,blocks,polylogue-4s3c,graph-repair,z9gh.1 residual AC requires live RSS/PSS/swap steady-state proof; the later z9gh.1 row delegates this envelope to 4s3c.,Execution core/fairness remains z9gh.1; live-scale envelope proof remains 4s3c.,An accepted z9gh.1 live-scale receipt already covers 4s3c scope. +G10,P0,move-residual-scope,polylogue-z9gh.1,consumer-ownership,polylogue-z9gh.9.1,description-rewrite,"Shared execution_control.py is implemented and used by one API, MCP, and HTTP query path, while many direct MCP/HTTP reads remain. z9gh.9.1 owns transaction/paging/disconnect/resume semantics.","Move remaining direct read-site migration, HTTP disconnect, and resumed receipt work; retain cross-class fairness in z9gh.1.",Remaining direct opens are proven outside query transaction scope or mutation-only. +G11,P1,add-hard-dependency,polylogue-z9gh.3,blocks,polylogue-o21.1,graph-repair,z9gh.3 needs a query declaration registry; o21.1 is the shared storage-free declaration kernel and no declarations package exists yet.,z9gh.3 keeps query semantics/lowering; o21.1 owns declaration identity/completeness substrate.,o21.1 explicitly excludes query declarations or current source lands a distinct approved kernel. +G12,P1,add-hard-dependency,polylogue-9e5.31.1,blocks,polylogue-o21.1,graph-repair,"9e5.31.1 notes say DefinitionClosureGraph waits for and consumes o21.1, but the graph has no hard edge.",Definition closure remains its own semantic graph and must not become a declaration registry.,The closure graph can be completed without declaration identity/ownership/completeness. +G13,P1,add-hard-dependency,polylogue-71ey,blocks,polylogue-o21.1,graph-repair,Maintenance has both MaintenanceTargetSpec and a separate private _REPLAY_DISPATCH; 71ey is the pilot to unify them through the shared kernel.,"Maintenance target semantics, unsupported-target typed failure, and superseded_raw_snapshots regression.",71ey is narrowed to a local repair-table fix with no declaration-kernel dependency. +G14,P2,add-hard-and-soft-relations,polylogue-rii.1,blocks+relates-to,polylogue-o21.1;polylogue-37t.2.1,graph-repair,rii.1 says marker kinds and work-event kinds are one channel with two encodings. 37t.2.1 already depends on o21.1; source has session_events but no record_work_event/emit_decision API.,One event-kind vocabulary; separate marker parser/lowering and work-event emission adapters; no marker-specific tables.,Product decision changes to genuinely separate channels with different durable identity/retention semantics. +G15,P0,add-soft-relation,polylogue-yyvg.7,relates-to,polylogue-3v1,graph-repair,UX exception/attention policy consumes capture truth and gap evidence from 3v1 but does not own capture authority.,yyvg.7 presentation/convergence semantics and 3v1 durable capture/status semantics.,yyvg.7 acceptance can be verified without capture-state evidence. +G16,P1,reparent,polylogue-b054.1.1,parent-child,polylogue-88jp,graph-repair,"b054.1 is closed audit work, while b054.1.1 is an active verification harness/proof owner. It is the only active child with a closed parent; 88jp is the active verification-risk program.",Retain a historical relates/discovered link to b054.1 and all child proof slices.,"b054.1 is reopened as the durable implementation program, or another active verification parent is adopted." +G17,P1,normalize-claims-and-order,polylogue-1xc.14.1,execution-order,1xc.14.1.1→1xc.14.1.2→1xc.14.1.3→parent closure,tracker-repair,"Root and all three children are simultaneously in_progress on the same source hotspot. Canonical origin includes journal replay fixes, but privacy/promotion and catalog-default semantics remain distinct.","Replay/memory/cancellation proof, privacy promotion audit, catalog role/default semantics, then joint live canaries.",Independent branches can modify disjoint files with independent acceptance and no shared generated artifacts. +G18,P1,add-regression-fixtures,polylogue-gxjh.1,validates,polylogue-z9gh.1;polylogue-xnws,acceptance-rewrite,Both rows demonstrate ordinary branch reimport restoring an older valid row. CLAUDE.md documents this exact hazard; current guard compares updated_at only around hooks.,"Per-row latest revision, status, assignee, parent/dependencies, close reason, atomic import/export, and receipts.",A different mechanism is proven responsible for either regression. +G19,P1,rewrite-shipped-vs-residual,polylogue-ptx;polylogue-3v1;polylogue-yyvg.7,description,current-source-residuals,description-rewrite,"BrowserActionIntent/Receipt, receiver authority, outcome_unknown, and extension worker are present; remaining live-provider/project/failure and convergence evidence is not fully proved.","Transport/receipt, capture truth, and UX policy remain separate authorities; serialize shared extension/receiver writes.",Source removal or a newer accepted live proof closes all residuals. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/NEXT-ACTIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/NEXT-ACTIONS.md new file mode 100644 index 000000000..735da170f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/NEXT-ACTIONS.md @@ -0,0 +1,81 @@ +# Next actions + +These actions are proposals only. This audit did not run `bd update`, `bd close`, `bd dep`, `bd export`, an archive apply, or any production mutation. + +## Phase 0 — Repair tracker truth before implementation + +Use one bookkeeping branch, created from the newest merged default branch, and merge it before any unrelated checkout. + +1. Restore `z9gh.1` to the later per-ID state: `in_progress`, assignee `Sinity`, updated record content from `6610f3b0`. Do not close it from the misleading commit subject. +2. Restore `xnws` closed with the later close reason from `6610f3b0`/PR #2963. +3. Record `20d.17` as the sole status owner and close `703` as superseded after copying its unique evidence/acceptance. +4. Record `kwsb.2` as the destructive-mutation transaction owner and close `t46.9` as superseded after copying its unique inventory/discovery/guard requirements. +5. Replace hard `avmq → yp0` with a soft relation. +6. Add the raw chain edges, declaration-kernel edges, `yyvg.7 → 3v1` soft relation, and reparent `b054.1.1` to `88jp`. +7. Normalize the schema-workload root/child claim notes so one branch and explicit order are visible. +8. Add `z9gh.1` and `xnws` as `gxjh.1` regression cases. + +After every checkout/merge/worktree operation, re-read every changed row from the live tracker. Parse every JSONL line, inspect per-ID `updated_at`, run repository graph lint, and confirm there is exactly one canonical parent per issue. Do not use a temporary-worktree `bd export` to resolve conflicts. + +**Phase-0 acceptance:** the effective active count is 554; `703`, `t46.9`, and `xnws` are not executable leaves; `z9gh.1` is claimed; no active child has a closed parent; no P0/P1 hard dependency points to P3/P4 solely for adjacent transport; graph remains acyclic. + +## Phase 1 — Land shared kernels and remove false parallelism + +### Status (`20d.17`) + +Inventory every shared fact in daemon status, CLI direct fallback, workload diagnostics, MCP/HTTP/web, and coordination. Define component identity, dependencies, detail/cost class, deadline, source fingerprint/event invalidation, privacy, freshness/staleness, last-good evidence, and projection fields. Convert request paths to snapshot reads before deleting old probes. + +Verify a deliberately stalled raw/debt/embedding/Beads/archive/handoff component cannot delay healthy components. Record cold/warm component timing, p50/p95, output bytes, omissions, fingerprints, cache decisions, and exact detail refs. Keep 703's parity and citation-drift fixtures. + +### Daemon supervision (`avmq`, `09rn`, `enj7`) + +Generate an inventory test from every `asyncio.create_task`/server task in `run_daemon_services`. Introduce one service registry and supervisor. Prove deterministic ordering, no duplicate starts, declared failure policy, partial-start rollback, bounded cancel/await, orphan diagnostics, missing-tier behavior, and production-derived named test profiles. Do not wait for EventBus wiring. + +### Declaration kernel (`o21.1` first) + +Land the smallest storage-free identity/ownership/completeness/introspection kernel. Then adapt query declarations (`z9gh.3`), MCP projection (`t46.8.1`), definition closure (`9e5.31.1`), maintenance (`71ey`), and marker/work-event vocabulary (`rii.1`/`37t.2.1`) in separate domain commits/branches. A completeness test must fail any unowned declaration or any second registry for the same identity. + +### Destructive transactions (`kwsb.2`) + +Choose at least two real mutation domains before generalizing. For each, define a domain PlanSpec/target resolver/actuator and route all surfaces through a shared transaction that binds actor, archive identity, operation/version, target-set/preview digest, expiry, conflict policy, receipt, and postflight. Verify direct adapter calls fail the inventory/parity test. Keep storage guards behind the transaction. + +## Phase 2 — Close code/proof clusters in dependency order + +### Raw authority + +Run the generated/sanitized scale proof first. It must exercise real planner/executor cardinality, finite retry injection, no starvation, monotonic executable backlog decrease, complete per-plan outcomes, bounded RSS/PSS/swap/temp/CPU/I/O, responsive health, and two quiescent fixed-point census identities. Only then close `hjpx`; only after code review and a current verified backup may an operator consider `yla8` live execution. `lkrc` closes after postflight authority/reconciler evidence. + +A local implementer must verify no cursor/head regression, no non-executable plan crosses apply, preview membership is revalidated, crash-left applications recover from typed evidence, and complete frontier counts include `proven_current` while blocking derives from residual state. + +### Query execution and surfaces + +For `z9gh.1`, prove cross-class aging/fairness and exact cleanup under cancel-before-admission, during SQLite, during Python-side production, deadline, disconnect, and worker failure. `4s3c` must supply live steady-state RSS/PSS/swap/temp evidence. + +For `z9gh.9.1`/`t46.8.2`, classify every remaining direct MCP/HTTP/archive open. Migrate true query transactions to shared execution/paging/resume. Prove HTTP disconnect, advancing continuation, resumed receipt, no raw expression leakage, generated MCP schema/help/role parity, and duplicate-alias removal. Do not convert writer/mutation routes into read transactions. + +### Schema workload + +Use one branch and the sequence recorded in `EXECUTION-CLUSTERS.csv`. `.1` must finish bounded journal/replay, cancellation/process-death cleanup, live receipt, and memory/I/O proof. `.2` must block unsafe property names/credentials while separately reporting review-only values and keeping local provenance out of public artifacts. `.3` must retain all evidence families while separating latest/recommended/default/promoted semantics with machine-readable rationale. Parent closure requires regenerated artifacts, runtime-resolution parity, promotion receipt, and canaries. + +### Extension/capture/UX + +Finish transport/provider/project/failure matrix under `ptx`, capture gap/status truth under `3v1`, and exception-only UX convergence under `yyvg.7`. Verify expired submit intent becomes `outcome_unknown`, no implicit retry occurs, reconciliation requires exact provider evidence, and deployed receiver/extension status agrees. Keep `yyvg.6` external and prove no campaign identity appears in extension/receiver storage or APIs. + +## Phase 3 — Wire downstream consumers + +Wire `yp0` only after the daemon service registry is stable. Register committed-domain publishers and subscribers through the lifecycle metadata, but retain slow reconciliation. Measure reduced idle polling/SQLite reads and prove missed event/subscriber failure does not lose correctness. + +Wire definition closure, maintenance target actuation, and marker/work-event adapters only after `o21.1` identity/completeness is stable. For maintenance, eliminate the catalog/private-dispatch mismatch or make the declaration own both advertised capability and actuator binding. For markers/work events, lower into existing typed services/session-event channels; do not create marker-specific durable tables. + +## Phase 4 — Re-audit after state changes + +Regenerate the package from a fresh archive after graph repair and merged implementation. Compare: + +- active/effective counts and per-ID latest history; +- hard-edge cycles, priority inversions, active supersessions, and closed parents; +- source symbols versus stale descriptions; +- branch/write-hotspot ownership; +- accepted live receipts for raw, query, status, schema, and extension; +- whether any consumer created a parallel registry/protocol despite the new edges. + +Another iteration is low-value against the unchanged snapshot. It becomes high-value after Phase 0 or when fresh live receipts/current merged source are available, because it can test whether these decisions survived implementation rather than repeat the same static evidence. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/REPORT.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/REPORT.md new file mode 100644 index 000000000..3f63a29e7 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-01/a01/extracted/REPORT.md @@ -0,0 +1,177 @@ +# Bead overlap and contradiction audit + +**Job:** `analysis-01` +**Snapshot time:** `2026-07-17T04:32:02Z` +**Archived source:** local `master` `f654480cad`; every archived tracked file that was present matched that commit, with one untracked `browser-extension/package-lock.json`. +**Later bundled committed source:** `origin/master` `0d081e5bc0`, five commits ahead, used where its raw-authority/schema changes supersede the archived source. +**Mutation policy:** no Bead, source, daemon, archive, or deployment state was mutated. + +## Executive conclusion + +The active portfolio is large but not structurally chaotic. The snapshot contains **555 open/in-progress rows** across **976 total Beads**; history reconstruction removes one false active (`polylogue-xnws`) and restores one regressed claim (`polylogue-z9gh.1`), leaving **554 effective active rows**. The hard dependency graph has **235 active-to-active hard edges and no cycle**. The highest-value repair is therefore targeted adjudication, not a wholesale re-model. + +Five changes materially improve execution safety: + +1. Close `polylogue-703` into `polylogue-20d.17`; it is the only explicit active-to-active supersession and its parity evidence is already claimed by the stronger component-snapshot owner. +2. Close `polylogue-t46.9` into `polylogue-kwsb.2`; the two encode incompatible destructive-operation products, and current source explicitly warns against reviving a speculative universal operation framework. +3. Remove the hard `polylogue-avmq → polylogue-yp0` blocker. Supervision can land without production event-bus wiring; the bus should consume the service registry downstream. +4. Make the raw-authority completion order explicit: `hjpx.2` scale proof before `hjpx`, then `yla8` authorized live gate, then `lkrc` closure. These are distinct safety authorities and should not be merged. +5. Make `o21.1` a real prerequisite for query declarations, definition closure, maintenance declarations, and work-event/marker vocabulary. They keep domain ownership but must not create parallel declaration registries. + +The report proposes **19 graph/record changes** and **16 execution clusters**. The full 555-row snapshot-active census is in `ACTIVE-CENSUS.csv`; every row records source area, intended authority class/claim, hard and soft dependencies, acceptance semantics, explicit source paths, staleness signal, hotspot, and disposition. + +## What was audited + +The review parsed every current Bead row and every dependency edge, reconstructed per-ID tracker history from all bundled Git refs, inspected repository workflow instructions, compared the archived source with local and later bundled master refs, and read current implementations at the active P0/P1 write hotspots. It also used the separate test-suite package only as a stale cross-check. + +The authority order applied was: + +1. archived source at `f654480cad` for what the snapshot actually contained; +2. later committed `origin/master` `0d081e5bc0` where those five commits supersede local source; +3. repository instructions, especially the documented Beads branch-reimport hazard; +4. latest per-ID Bead record reconstructed from Git history; +5. older notes/plans and the slightly stale test-suite package. + +This distinction matters. The snapshot overview calls local `origin/master` `f654480cad`, but the all-refs bundle also contains a newer `refs/snapshot/origin/master` `0d081e5bc0`. The latter adds five raw-authority/schema commits (`#2965`, `#2966`, `#2967`, `#2968`, `#2970`; 22 files, +1058/−117). The report never treats live values in notes as independently reproduced evidence. + +## Portfolio and graph diagnostics + +| Signal | Observed | +| --- | --- | +| Total Beads | 976 | +| Snapshot open/in-progress | 555 | +| Effective open/in-progress after history repair | 554 | +| Snapshot P0/P1 active | 114 | +| Active hard edges | 235 | +| Active hard cycles | none | +| Active→active supersedes | `20d.17 → 703` only | +| Active children with closed parent | `b054.1.1 → b054.1` only | +| Exact duplicate active titles | none | +| P0/P1 hard dependency on P3/P4 | `avmq → yp0` only | + +The absence of cycles and exact duplicate titles is not proof that the portfolio is clean. The real defects are semantic: incompatible authority claims, stale descriptions after code landed, false hard blockers, and missing prerequisite edges around shared registries and live gates. + +## Decision-ready graph changes + +| ID | Priority | Change | Why | +| --- | --- | --- | --- | +| G01 | P0 | polylogue-z9gh.1 status in_progress | Export row updated 2026-07-16T12:56:52Z is older than per-ID Git history row updated 2026-07-17T00:25:42Z at 6610f3b0; later row is in_progress and assigned Sinity. | +| G02 | P1 | polylogue-xnws status closed | Export row updated 2026-07-16T23:43:40Z is older than Git history row updated/closed 2026-07-16T23:59:23Z at 6610f3b0; close reason cites merged PR #2963. | +| G03 | P1 | polylogue-20d.17 supersedes polylogue-703 | 20d.17 has the only active→active supersedes edge and its note explicitly says it absorbs 703; 20d.17 defines the stronger component-snapshot protocol. | +| G04 | P1 | polylogue-kwsb.2 supersedes polylogue-t46.9 | kwsb.2 requires a shared transaction protocol and explicitly rejects a universal mutation executor; t46.9 requires a universal OperationExecutor. Current operation_contract.py documents removal of the previous speculati… | +| G05 | P1 | polylogue-avmq blocks→relates-to polylogue-yp0 | avmq calls EventBus adjacent transport, not lifecycle authority. yp0 core exists but has no production references outside its module. The edge makes P1 supervision wait on P3 transport wiring. | +| G06 | P0 | polylogue-hjpx blocks polylogue-hjpx.2 | hjpx acceptance requires the July-15-shaped bounded convergence proof; hjpx.2 is that proof and says hjpx AC6 remains unproven. | +| G07 | P0 | polylogue-yla8 blocks polylogue-hjpx | yla8 notes retain the live authorization gate only after the successor fixed-point proof; hjpx says it is the executable successor to the failed live gate. | +| G08 | P0 | polylogue-lkrc blocks polylogue-yla8 | Existing edge correctly keeps final authority/reconciler closure behind the authorized live gate. | +| G09 | P0 | polylogue-z9gh.1 blocks polylogue-4s3c | z9gh.1 residual AC requires live RSS/PSS/swap steady-state proof; the later z9gh.1 row delegates this envelope to 4s3c. | +| G10 | P0 | polylogue-z9gh.1 consumer-ownership polylogue-z9gh.9.1 | Shared execution_control.py is implemented and used by one API, MCP, and HTTP query path, while many direct MCP/HTTP reads remain. z9gh.9.1 owns transaction/paging/disconnect/resume semantics. | +| G11 | P1 | polylogue-z9gh.3 blocks polylogue-o21.1 | z9gh.3 needs a query declaration registry; o21.1 is the shared storage-free declaration kernel and no declarations package exists yet. | +| G12 | P1 | polylogue-9e5.31.1 blocks polylogue-o21.1 | 9e5.31.1 notes say DefinitionClosureGraph waits for and consumes o21.1, but the graph has no hard edge. | +| G13 | P1 | polylogue-71ey blocks polylogue-o21.1 | Maintenance has both MaintenanceTargetSpec and a separate private _REPLAY_DISPATCH; 71ey is the pilot to unify them through the shared kernel. | +| G14 | P2 | polylogue-rii.1 blocks+relates-to polylogue-o21.1;polylogue-37t.2.1 | rii.1 says marker kinds and work-event kinds are one channel with two encodings. 37t.2.1 already depends on o21.1; source has session_events but no record_work_event/emit_decision API. | +| G15 | P0 | polylogue-yyvg.7 relates-to polylogue-3v1 | UX exception/attention policy consumes capture truth and gap evidence from 3v1 but does not own capture authority. | +| G16 | P1 | polylogue-b054.1.1 parent-child polylogue-88jp | b054.1 is closed audit work, while b054.1.1 is an active verification harness/proof owner. It is the only active child with a closed parent; 88jp is the active verification-risk program. | +| G17 | P1 | polylogue-1xc.14.1 execution-order 1xc.14.1.1→1xc.14.1.2→1xc.14.1.3→parent closure | Root and all three children are simultaneously in_progress on the same source hotspot. Canonical origin includes journal replay fixes, but privacy/promotion and catalog-default semantics remain distinct. | +| G18 | P1 | polylogue-gxjh.1 validates polylogue-z9gh.1;polylogue-xnws | Both rows demonstrate ordinary branch reimport restoring an older valid row. CLAUDE.md documents this exact hazard; current guard compares updated_at only around hooks. | +| G19 | P1 | polylogue-ptx;polylogue-3v1;polylogue-yyvg.7 description current-source-residuals | BrowserActionIntent/Receipt, receiver authority, outcome_unknown, and extension worker are present; remaining live-provider/project/failure and convergence evidence is not fully proved. | + +`GRAPH-CHANGES.csv` carries the exact evidence, information to preserve, falsification condition, and application phase for each row. + +## High-priority adjudications + +### Status: one owner, one absorbed structural task + +**Observed fact.** `polylogue-20d.17` has an explicit `supersedes` edge to still-open `polylogue-703`, and its note says it absorbs 703. Source still contains a whole-payload `StatusSnapshot`, a large `build_daemon_status`, independent CLI direct fallback assembly, and a separate coordination envelope. There is no `StatusComponentSpec`. + +**Decision.** Keep `20d.17` as the canonical component-snapshot owner and close `703` as superseded. Move, do not discard, 703's inventory of shared facts, one-assembly invariant, cross-surface parity suite, and citation-drift fixture. Implement in one serialized status branch because the files are large and overlapping. + +**Acceptance boundary.** A stalled component cannot delay healthy components; every component reports freshness, age, deadline, last-good evidence, state, and detail ref; compact projections stay within byte/latency budgets; exact diagnostics are explicit, bounded, cancellable, and resumable. A local implementer must prove CLI, daemon HTTP, MCP, Python, web, and coordination consume the same snapshot semantics without rebuilding rich status inline. + +### Destructive mutation: adjudicate the product contradiction + +**Observed fact.** `t46.9` says `OperationSpec` becomes the single executable declaration and every mutation route uses a universal `OperationExecutor`. `kwsb.2` says the missing abstraction is a shared transaction protocol, **not** a universal mutation executor, with domain-owned PlanSpec/actuator semantics. Current `OperationSpec` is metadata, while `operation_contract.py` records that an earlier generic request/ack framework was collapsed because only IMPORT used it. + +**Decision.** Keep `kwsb.2`; supersede `t46.9`. The shared authority is `MutationTransaction`: resolve target, authorize, bind preview/token/digest, invoke a domain-owned actuator, receipt, and postflight. `OperationSpec` remains an inventory/discovery source and maps to transaction policy, but it is not the universal authorization/dispatch object. + +**Preserved from `t46.9`.** Complete operation inventory; declaration-derived schemas/help/role discovery; removal of direct adapter mutation calls after parity; retirement of legacy confirm booleans after bound-token coverage; storage guards and `ArchiveWriteGateway` as defense in depth. + +### Daemon lifecycle: remove a priority inversion, retain separate authorities + +**Observed fact.** `avmq` is P1 and hard-blocked by P3 `yp0`, even though `avmq` explicitly calls EventBus adjacent transport. `run_daemon_services` remains a monolithic composition root spawning HTTP, API, watcher, and periodic tasks. `event_bus.py` contains a tested typed core but no production references outside that module. + +**Decision.** Remove the hard edge and add a soft relation. Implement `avmq`, `09rn`, and `enj7` together: one `DaemonServiceSpec` registry, one supervisor, production-derived named profiles, bounded shutdown, and orphan diagnostics. Wire `yp0` afterward as event transport consuming the lifecycle registry; retain slow reconciliation as correctness truth. + +### Raw authority: add the missing safety chain, do not merge it + +**Observed fact.** Current origin source has a typed frontier census, executable actuator classification, preview-bound apply with revalidation and non-executable refusal, crash recovery, and a real scale proof runner that calls `repair_raw_materialization` and requires a census receipt. The active Beads still divide four different responsibilities: scale evidence (`hjpx.2`), fixed-point executor (`hjpx`), operator-authorized live gate (`yla8`), and final authority/reconciler closure (`lkrc`). + +**Decision.** Keep all four. Add `hjpx → hjpx.2` and `yla8 → hjpx`; retain `lkrc → yla8`. Execute as: + +`hjpx.2 scale proof → hjpx fixed-point acceptance → yla8 stopped-daemon authorized live gate → lkrc closure`. + +The code work for `hjpx.2`, `hjpx`, and `lkrc` belongs on one serialized raw-authority branch because they touch the same planner/census/replay files. `yla8` remains a separate no-code live execution phase after review and explicit authorization. No live apply, cursor reset, evidence deletion, or manual SQL repair is authorized by this report. + +### Query execution: shipped core, residual ownership split + +**Observed fact.** PR #2964's shared `QueryExecutionContext`, weighted `QueryAdmissionController`, dedicated `InterruptibleSQLiteRead`, async cancellation/connection interrupt, and sync HTTP runner exist and are production-used by one API, MCP, and HTTP query path. Many direct MCP/HTTP `ArchiveStore.open_existing` routes remain. The exported `z9gh.1` row is also older than a later in-progress/assigned record. + +**Decision.** Restore `z9gh.1` to `in_progress`. Keep cross-class fairness and execution-core cleanup in `z9gh.1`; add hard dependency on `4s3c` for live RSS/PSS/swap proof. Move remaining surface migration, HTTP disconnect, and resumed-delivery receipt scope to `z9gh.9.1`, which already owns transaction/paging/resume semantics. Keep `t46.8.2` as the generated MCP adapter migration and `t46.8.2.1` as its duplicate-alias regression, in the same serialized surface branch after declarations. + +### Declaration family: shared kernel, domain-owned semantics + +**Observed fact.** There is no `polylogue/declarations` package. Maintenance already has a public `MaintenanceTargetSpec` catalog plus a separate private `_REPLAY_DISPATCH`; `superseded_raw_snapshots` is advertised but absent from that dispatch. Existing `session_events` storage/read paths exist, while `record_work_event` and `emit_decision` do not. Several Beads say they consume or wait for `o21.1` but lack hard edges. + +**Decision.** Add hard prerequisites from `z9gh.3`, `9e5.31.1`, `71ey`, and `rii.1` to `o21.1`; relate `rii.1` to `37t.2.1`. `o21.1` owns identity, declaration completeness, ownership, and storage-free introspection. Query declarations own query semantics; definition closure owns graph semantics; maintenance owns target planning/actuation; marker and work-event adapters share one event-kind vocabulary and lower into existing typed storage. None creates a second generic registry. + +### Schema workload hotspot: serialize, do not collapse + +**Observed fact.** `1xc.14.1` and all three P1 children are simultaneously `in_progress` with the same assignee and overlapping generation files. Later origin source includes bounded journal replay transaction work, but the children retain distinct acceptance: replay/memory/cancellation and live receipt; privacy/promotion; catalog-role/default semantics. + +**Decision.** Keep the children, but normalize execution to one branch/owner and one visible order: `.1` then `.2` then `.3`, then parent canaries/receipts/promotion closure. Rewrite `.1` to the residual proof only so landed journal work is not reimplemented. + +### Extension/campaign: four authorities, serialized shared writes + +**Observed fact.** Source has `BrowserActionIntent`, exact `BrowserActionReceipt`, receiver-owned durable state, lease expiry to `outcome_unknown`, explicit reconciliation, and a real extension action worker. No campaign ID vocabulary appears in extension/receiver source. + +**Decision.** Retain `ptx` for action transport/receipts, `3v1` for capture truth/status, `yyvg.7` for exception-driven UX policy, and `yyvg.6` for external campaign orchestration. Add a soft `yyvg.7 → 3v1` relation. Serialize branches touching `browser-extension/src/**` and `polylogue/browser_capture/**`; keep campaign orchestration on a separate local-tooling branch with a private ledger. + +### Beads synchronization: current portfolio supplies regression fixtures + +**Observed fact.** The repository instructions warn that checkout hooks re-import branch-local JSONL by blind upsert, and the guard only snapshots around hooks and compares `updated_at`. The current export contains two strictly older rows than Git history: `z9gh.1` and `xnws`. + +**Decision.** Keep `gxjh.1` as the sole monotonic synchronization owner and `8jg9.1` as its policy consumer. Add both regressions as acceptance fixtures that must preserve status, assignee, parent/dependency identity, close reason, and newer per-row revision under ordinary import/checkout/merge. Apply tracker repair in one immediately merged bookkeeping branch, never sibling bookkeeping branches. + +### Verification hierarchy: repair the only active-child/closed-parent edge + +**Observed fact.** `b054.1` is a completed audit, while active `b054.1.1` is a bounded verification harness/proof owner. It is the only active child whose parent is closed. + +**Decision.** Reparent `b054.1.1` to active verification-risk program `88jp`; retain a historical relates/discovered link to `b054.1`. Keep its children as distinct proof lanes under one harness root. + +## Clusters that share a branch versus remain separate + +The executable cluster map is in `EXECUTION-CLUSTERS.csv`. The governing rule is semantic ownership plus write overlap: + +- Same branch when Beads share a source-of-truth file set and acceptance cannot be reviewed independently: status, daemon supervision, raw-authority code, query transaction surfaces, schema workload generation, and extension/receiver work. +- Separate branch or phase when authority must remain independent: EventBus after supervision; live raw apply after code review; external campaign after transport; domain declaration consumers after the shared kernel. +- Same bookkeeping branch for all graph/status corrections, merged before any unrelated checkout, because repository instructions document stale-row reimport. + +## Other clusters explicitly retained as distinct + +The census records no merge for layered pairs that look similar but own different semantics: `4p1` read algebra versus `t46` surface consolidation versus `z9gh` query transaction; `b5l.1` rebuild exclusivity versus raw-authority repair; `1xc.13` source freshness projection versus raw authority; `t8t` scenario catalog versus `z9gh.7` terminal live gate; `5k5l` inbound assistant-output asset acquisition versus `83u.3` uploaded input bytes and `ptx` outbound browser action transport; and the parent/implementation/proof families under `1vpm`, `ovme`, `60i5`, `303r.2`, and `cuxz`. + +## Limitations and unresolved uncertainty + +This is a static, source-validated graph audit. It did not run the live daemon, browser extension, archive repair/apply, deployment, or full test suite. Live archive sizes, memory figures, cursor counts, backup state, and provider behavior reported in Bead notes remain tracker evidence. The working-tree tar omits many tracked hidden/agent files by packaging design, although the XML slices and Git bundle supplied those contexts; every archived tracked file that was present matched local head. The manifest validates all ordinary artifacts; its self-entry has no SHA-256 and a pre-serialization byte count, which is a packaging/self-reference limitation rather than evidence corruption. + +The strongest residual uncertainty is not another duplicate hunt. It is whether the proposed graph repairs survive the next merged code/Beads state and whether live acceptance receipts close the raw, query, status, schema, and extension gates. Another unchanged static pass has low marginal value. A second iteration becomes high-value after the bookkeeping repair or when it includes: a fresh Bead export, merged current master, live daemon/query/status receipts, the schema promotion receipt, and extension provider/project/failure matrix evidence. + +## Package map + +- `REPORT.md` — conclusions, portfolio diagnosis, and cluster decisions. +- `EVIDENCE.md` — exact source/Bead/history evidence, negative evidence, and limitations. +- `DECISIONS.md` — decision records separating facts, inference, uncertainty, recommendation, preservation, and falsification. +- `NEXT-ACTIONS.md` — ordered non-mutating execution plan and local verification gates. +- `ACTIVE-CENSUS.csv` — all 555 snapshot-active Beads with effective status repair. +- `GRAPH-CHANGES.csv` — proposed record/edge changes with evidence and preservation requirements. +- `EXECUTION-CLUSTERS.csv` — branch/phase grouping and acceptance gates. +- `AUDIT-STATS.json` — compact machine-readable scope and graph statistics. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/DECISIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/DECISIONS.md new file mode 100644 index 000000000..09a0a0fa2 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/DECISIONS.md @@ -0,0 +1,119 @@ +# Decisions for the next external-agent campaign + +These decisions extend the repository’s existing external campaign workspace. They do not add campaign concepts to browser-extension, receiver, capture, or product-domain models. + +## D1. Preserve distinct funnel entities + +**Decision:** Reports and ledgers must keep action/run, provider conversation, provider turn, artifact, package revision, integration track, PR, merge, and Bead outcome as separate identities. Never force all stages onto a single package denominator. + +**Owner:** `polylogue-yyvg.6`; `.agent/handoffs/external-agent-campaigns/`. + +**Acceptance:** A report can represent 27 conversations, 26 package-bearing conversations, 28 package revisions, and 45 downloads without overwriting or silently reconciling any count. Every rate names its entity and denominator. + +**Falsification:** A fixture that adds a same-chat package revision or a non-package proof conversation changes only the correct entity counts. If all stage counts change together, the model is still wrong. + +## D2. Replace prose-only stage state with immutable evidence events + +**Decision:** Advance `schemas/result.schema.json` to version 2 and add timestamped, source-attributed events. Keep per-attempt `result.json` immutable and `results/index.json` rebuildable, as the current workspace already specifies. + +**Owner:** `polylogue-yyvg.6`; exact target `.agent/handoffs/external-agent-campaigns/schemas/result.schema.json`. + +**Acceptance:** Submission, conversation binding, first/terminal turn, artifact offer/acquisition/hash validation, package validation, triage, worktree, apply check, repair, verification, PR, merge, and Bead-state transitions each carry `occurred_at`, `recorded_at`, `source_kind`, and `evidence_ref`. Unknown historical stages remain absent or explicitly unknown; import code does not synthesize timestamps. + +**Falsification:** Rebuilding `results/index.json` from immutable receipts must reproduce campaign counts. Deleting any one fixture event must make the corresponding stage count fail. + +`RESULT-TELEMETRY-V2.schema.json` is the exact proposed replacement shape. + +## D3. Gate scale on canonical terminal-output custody + +**Decision:** Do not expand a campaign wave while any prior attempt has a terminal assistant output with no canonical artifact outcome. Manual download is allowed only as an explicit degraded acquisition source, never silently treated as canonical capture success. + +**Owner:** `polylogue-3v1` for capture completeness; `polylogue-ptx` for generic actions; `polylogue-yyvg.6` for orchestration. + +**Acceptance:** For each submitted attempt, the ledger ends in one of: terminal with no artifact expected; artifact acquired and hash-valid; explicit provider/output failure; explicit irrecoverable historical gap. Offered artifact count equals acquired refs plus explicit failed/declined outcomes. A closed-tab live fixture and a replay fixture both pass through ordinary canonical capture. + +**Falsification:** Seed a terminal output attachment, remove its captured ref or blob, and require dispatch gating and campaign completeness to fail. + +## D4. Route by mission readiness, not by a universal “implementation package” template + +**Decision:** Use three explicit mission shapes: + +1. `implementation`: current source, one owning Bead, dependency-ready, named production route, bounded write/avoid set, apply-ready patch, real-route tests. +2. `repair`: same conversation or successor attempt, concrete failed receipt/review finding, supersedes lineage, cohesive replacement package. +3. `analysis_or_research`: cross-cutting decisions, dependency-premature work, architecture reconciliation, or external evidence; no pressure to manufacture a product patch. + +**Owner:** the shared ChatGPT Pro contracts and workload `campaign.json` files. + +**Acceptance:** `prompt_shape` is recorded. Implementation dispatch fails when the owner is closed/superseded without an explicit continuation, a dependency is unmet, the snapshot is stale, the allowed footprint is absent, or the production route is not named. Analysis/research output is evaluated on decision closure, not patch presence. + +**Falsification:** Reclassify a known dependency-wait package as implementation; `check-dispatch.py` must block it. A broad analysis job must pass without a placeholder patch. + +## D5. Measure package composition; do not impose a naive byte cap + +**Decision:** Triage records compressed/uncompressed size, member count, patch bytes, test/verification bytes, design/metadata bytes, fixture/snapshot/archive bytes, copied-input detection, and largest members. Large evidence is permitted when declared and necessary. Undeclared bulk, copied project-state archives, padding, and synthetic fixtures presented as runtime proof fail triage. + +**Owner:** `triage-package.py` and result schema. + +**Acceptance:** The validator explains why each member above a configurable threshold exists. The exact supplied project-state archive or equivalent copied input is rejected. A 20 MiB necessary fixture can pass with a declaration and owning test; an 8 MiB irrelevant replacement/snapshot copy fails. + +**Falsification:** Add a giant inert member without changing patch or tests; package quality metrics must not improve, and triage must flag it. + +## D6. Make same-chat repair a first-class attempt and package revision + +**Decision:** A retry or repair never overwrites an earlier result. It receives a new `attempt_id`, optional `package_revision`, `supersedes_attempt_id`, reason code, and explicit evidence of what failed. + +**Owner:** `polylogue-yyvg.6`, prompt renderer, results importer. + +**Acceptance:** The two prior same-chat package pairs backfill as distinct revisions linked to one conversation. A repair can be compared to its predecessor by artifact hash, touched paths, patch retention, review findings resolved, and eventual delivery outcome. + +**Falsification:** Importing two artifacts from the same conversation with different hashes must not deduplicate them by conversation ID or overwrite the first receipt. + +## D7. Keep local integration as an independent authority stage + +**Decision:** Package self-verification can support triage but cannot publish work. A local implementer must reconcile current source and Beads, create a clean worktree, inspect the patch, record `git apply --check`, repair against current architecture, run production-route and anti-vacuity tests, and record a PR/merge/Bead receipt. + +**Owner:** integration coordinator; `results///integration/`. + +**Acceptance:** Every merged track has a clean-base identity, applied/retained/replaced patch record, local repair reason, exact test command/outcome, PR URL/number, merge commit, and current Bead state. A package with a competing seam or stale API is rejected or reimplemented rather than massaged into a nominal merge. + +**Falsification:** Remove the production dependency or the claimed behavior; the anti-vacuity test must fail. Apply the package to a fresh current worktree; stale patches must be visible before review. + +## D8. Pace generation by reconciliation and integration capacity + +**Decision:** Use a small capture-and-ledger pilot before broad fan-out. After prerequisites freeze, external drafting may fan out, but active integration worktrees remain bounded. Do not launch a new wave while prior terminal/acquisition gaps or an untriaged queue exceed the available integration lanes. + +**Owner:** `check-dispatch.py`, workload coordinator, `polylogue-yyvg.6`. + +**Acceptance:** First launch 3–5 mixed attempts to prove action, terminal, output, and ledger events. Then expand to 8–12 dependency-disjoint implementation attempts only if every pilot attempt is terminally reconciled and triaged. Cap simultaneously active integration worktrees at the available 3–4 local lanes; the next wave waits until the triage queue is at or below that cap. + +**Falsification:** Seed one unresolved terminal-output gap or an over-cap integration queue; dispatch must report the exact blocked gate. + +## D9. Rebuild current delivery projection from immutable receipts + +**Decision:** `INTEGRATION-LEDGER.json`-style package adjudication remains immutable evidence, but current delivery state must be rebuilt from result receipts, Git/PR history, and Beads. A package marked pending can later project as delivered without rewriting its original adjudication. + +**Owner:** `polylogue-yyvg.6`; results index builder. + +**Acceptance:** Backfilling PR #2953 changes the current CaptureJobs projection to delivered/closed while preserving package 24’s original “preserved draft awaiting reconciliation” decision. Contradictions are rendered, not overwritten. + +**Falsification:** Rebuild after changing a Bead from open to closed; only the projection changes. The immutable package receipt hash and original decision remain byte-identical. + +## D10. Preserve the generic product boundary + +**Decision:** External campaign cadence, prompt fragments, package naming, mission state, triage, and integration stay under `.agent/handoffs/external-agent-campaigns/`. Browser product code exposes only provider-neutral BrowserAction and canonical capture behavior. + +**Owner:** `polylogue-ptx`, `polylogue-3v1`, `polylogue-yyvg.6`. + +**Acceptance:** No campaign/job/package-specific field, route, queue, or UI is added to browser-extension or receiver product contracts. External orchestration can submit a generic action and discover its conversation, turns, and files through canonical capture. + +**Falsification:** Search product schemas/routes/UI for campaign/job/package-revision concepts. Any result outside generic evidence references fails the boundary. + +## Explicitly rejected shortcuts + +- Do not report “28 conversations → 28 packages → 4 merges”. The entity mapping is false. +- Do not treat `processing_state: processed` as delivered code. +- Do not infer completion or acquisition time from browser filenames. +- Do not use package size, patch count, or self-reported tests as a merge score. +- Do not revive the Sol-specific launch/work-package model removed by PR #2928. +- Do not close an owning Bead merely because one package slice merged. +- Do not claim historical output recovery until live replay produces evidence. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..8fac0fba2 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/EVIDENCE.md @@ -0,0 +1,131 @@ +# Evidence and reconstruction record + +## Authority and method + +The analysis followed this order when sources disagreed: current tracked source at snapshot commit; repository instructions and current Beads records; canonical campaign ledgers and corpus README; Git/PR history; older notes. Prose that had been explicitly corrected was not treated as current fact. + +The all-refs Git bundle from the project-state archive was verified and cloned. The inspected tracked snapshot is `master` at `f654480cadb7cc4c194704e24dfd483199547b35`. The snapshot metadata says it was generated at `2026-07-17T043202Z` and was dirty. The reduced working-tree tar omitted the full tracked `.agent` campaign corpus, so the all-refs bundle was necessary to recover the canonical package tree and history. + +For the package corpus, all 28 paths named by `.agent/handoffs/polylogue-sol-pro-2026-07-15/INTEGRATION-LEDGER.json` were reopened as ZIPs, hashed, and compared to the ledger. Member counts, uncompressed sizes, content categories, and largest members were recomputed from the actual archives. Bead states were joined from `.beads/issues.jsonl`. Direct package PRs and later repair PRs were matched against all-ref Git history and the archived PR records. + +No package code or tests were executed. Verification claims in this report are attributed to PR/Bead receipts. No live browser, daemon, provider, archive database, deployment, or remote service was queried. + +## Primary evidence inventory + +| Evidence | Role | +| --- | --- | +| `polylogue-overview.json` / `polylogue-snapshot-audit.json` | Snapshot time, branch, commit, dirty flag, repository/Beads/PR census. | +| `polylogue-all-refs.bundle` | Complete tracked source and all-ref Git history. | +| `.agent/handoffs/polylogue-sol-pro-2026-07-15/README.md` | Acquisition history, capture incident summary, session correlation, corpus status. | +| `.agent/handoffs/polylogue-sol-pro-2026-07-15/INTEGRATION-LEDGER.json` | Canonical 28-package identity and adjudication. | +| `.agent/handoffs/polylogue-sol-pro-2026-07-15/SHA256SUMS` and `raw/*.zip` | Package byte custody and validation. | +| `.beads/issues.jsonl` | Current owners, corrections, acceptance criteria, closure state. | +| PRs #2913, #2918, #2919, #2922–#2930, #2953, #2957 and their merge commits | Implementation, review, verification, chronology, architectural correction. | +| `.agent/handoffs/external-agent-campaigns/README.md` | Current external campaign model: stable campaign/workload/job/attempt/revision identities, immutable `result.json`, rebuildable index. | +| `.agent/handoffs/external-agent-campaigns/schemas/{campaign,result}.schema.json` | Existing schema authority that recommendations must extend. | +| `.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/{check-dispatch.py,triage-package.py}` | Current dispatch and intake mechanics. | +| `.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/*/results/index.json` | Prepared next-wave indexes; all are empty in the snapshot. | + +## Observed facts + +1. The corpus README records 45 manual browser downloads deduplicated to 28 unique ZIPs, with 17 exact duplicate re-downloads. +2. All 28 canonical packages are present, hash-correct, ZIP-valid, and marked `processing_state: processed`; none is unknown. +3. Ledger dispositions are: 4 merged, 4 research-incorporated, 12 incorporated pending delivery, 2 already subsumed, 4 superseded/duplicate, and 2 rejected. +4. The 28 package revisions map to 26 unique conversation IDs. Package pairs 6/7 and 23/26 share conversation IDs. +5. Every ledger `iteration` field is null. All ledger `pr` fields and all direct `merge_commit` fields are null except one subsumed package’s historical merge reference; direct PR mapping lives in prose/current-master evidence rather than normalized fields. +6. The corpus contains 65,938,477 compressed bytes and 68,696,641 uncompressed bytes. Ten packages are at least 1 MiB and none directly merged. +7. The four direct-merge package ZIPs total 192,209 bytes. Their PRs are #2922, #2923, #2924, and #2925. +8. Current owner state at the snapshot shows two of the four direct merge owners closed: `polylogue-lkrc.4` and `polylogue-303r.2.1`; `polylogue-866e` is open and `polylogue-1xc.13` is in progress. +9. The CaptureJobs package is preserved on `feature/integration/capture-job-authority` at `ba340c71a` and `8ecc34ecc`, but its own Bead states that the fallback `paired:` is unsafe for exact account recovery and conflicts with generic BrowserAction. PR #2953 later delivered a reconciled current-source implementation and closed `polylogue-06zm.1`. +10. PR #2957 later repaired parser-drift replay under `polylogue-lkrc.4`, after the direct raw-authority package PR #2923. +11. `polylogue-3v1` reports a 27-conversation production census with 2,551 messages in current extension files versus 205 exposed by the index, 24/27 projection mismatches, and 22/27 cursor paths permanently excluded after transient failures. PR #2930 addresses replacement/reingest and browser snapshot membership; live replay/deployed parity remained required. +12. The corpus README records zero `*launch-handoff*` output attachment refs when the live archive was checked, despite all source sessions and 29 input attachments being present. + +## Source-supported inferences + +- Generation was not the principal loss point: 28 valid package revisions survived manually. The evidence loss occurred in canonical output-attachment custody and campaign event recording. +- Integration capacity and architecture stability constrained direct yield. Twelve packages were retained but gated, while the launch/capture architecture changed repeatedly during the campaign. +- The direct-merge missions shared current-source specificity and bounded claims. This association is stronger than the raw ZIP-size association, because large packages were dominated by heterogeneous fixtures and documents. +- Same-chat repair/revision behavior existed, but the campaign model failed to record it. The duplicate conversation IDs and null `iteration` values are direct evidence of that gap. +- The broad 28-session temporal set likely includes the later `6a587a8c` proof conversation outside the authoritative 27-conversation campaign census. This remains an inference until the raw roster is available. + +## Contradictions and adjudication + +| Earlier or superficial claim | Contrary evidence | Adjudication | +| --- | --- | --- | +| 28 files were 28 iterations of one mission. | Manifests, transcript references, patch digests, and Bead correlation identify a coordinated multi-mission batch. | Superseded. Treat as 28 package revisions across many missions. | +| 28 sessions, 28 deliverables, one-to-one. | 28 packages map to 26 unique conversation IDs; `polylogue-3v1` reports 27 campaign conversations; README lists 28 sessions in a broader time envelope. | Do not use a one-to-one funnel. Preserve all four entity counts. | +| Assistant output acquisition capability did not exist. | `launch_jobs.py`, work-package code, PR #2913, and later corrections show a mechanism existed. | Root cause was stale payload/tab-lifetime/reingest/exclusion defects, not total capability absence. | +| PR #2918/#2919 fixed the historical campaign outputs. | The audit still found zero output package refs, and `polylogue-3v1` requires live replay/deployed parity. | Future-path fixes are not retroactive recovery evidence. | +| Ledger `incorporated_pending_delivery` is current delivery truth. | Current Beads/PRs show CaptureJobs delivered later by #2953 and `polylogue-06zm.1` closed. | The ledger is immutable package adjudication; current delivery must be a rebuilt projection from Beads/Git/PR evidence. | +| Canonical download timestamp marks package completion or integration start. | Three direct PRs were created before the canonical timestamp; #2924 merged before it. | Timestamp is custody/dedup evidence only. | +| Package self-verification establishes product correctness. | Direct PRs narrowed claims, rejected competing seams, corrected design errors, and ran independent production-route tests. | Package receipts are triage input, never the publication authority. | +| Large packages contain proportionally more implementation value. | Large packages are dominated by snapshots, fixtures, replacement archives, or oversized documents; none directly merged. | Record content composition and declared necessity; do not infer quality from total bytes. | + +## Direct merge evidence + +### PR #2922 — lineage order independence + +Package 2 maps to `polylogue-866e`. The PR merged `b55f3fd9697083d44466613091604a21c7324ae6`, using canonical sibling-variant ordering and a missing-cut guard. It explicitly refused the separate continuity package because that package covered only two of seven routes and introduced a competing scenario seam. Focused lineage/storage tests and quick gates are recorded; the Bead remains open for semantic cut witnesses, crash/rollback proof, exhaustive examples, and nested-ancestor completeness. + +### PR #2923 — complete live bundle admission + +Package 12 maps to the raw-authority path and merged `81142d1dce7d8e896ef340783ab943cdf59143f6`. The PR claims only the reproduced complete-bundle admission defect and real taxonomy/parser/storage/retry path. It reports 54 passes and 10 unrelated failures in the broader file, and explicitly does not claim the full immutable census/conservation program. PR #2957 later merged `a62d2f972ea43dbaa4cb34e897eed07e674e0e30` for parser-drift membership replay and closed `polylogue-lkrc.4`. + +### PR #2924 — exact-source freshness + +Package 23 maps to `polylogue-1xc.13` and merged `b6c78adfcd666358307daf64ac97e8d695a8b854`. It adds bounded, read-only exact-source freshness across cursor/raw/authority/parse/index/FTS/insight stages and records 189 focused tests plus affected and quick verification. It intentionally does not run remediation. The owning Bead remains in progress pending live receipts and broader work. + +### PR #2925 — Sinex publication convergence + +Package 25 maps to `polylogue-303r.2.1` and merged `36001d023b2cfe793cb19fdd7c42a87597356f48`. It adds durable publication obligations and mode-aware convergence but leaves real transport/deployment to `polylogue-303r.2.2`. Bead notes record a coordinator correction: source and index are separate SQLite tiers, so the enforceable invariant is ordered durability, not a single cross-database transaction. The owner is closed. + +## Later repair and reimplementation evidence + +### CaptureJobs + +Package 24 was preserved rather than merged. Its local branch had receiver registry semantics and recorded 2 focused tests plus a 16-gate quick run, but failed the exact-account and current-transport constraints. PR #2953 merged `e6698a74ea55109d20cd21a98e51e422e6f784ed` with receiver-authoritative IDs, opaque HMAC-reduced account scope, CAS revisions/checkpoints, replaceable leases, idempotent receipts, new-profile recovery, and typed legacy orphans. Its recorded verification includes receiver, daemon security, extension, lint, manifest, and quick gates, plus multiple adversarial passes. This is local reimplementation influenced by packages 4 and 24, not a direct package patch merge. + +### Parser-drift replay + +PR #2957 is a post-package repair on the raw-authority line. It validates replay against persisted accepted index/raw witnesses while preserving divergent/quarantined rejection. It records focused tests and a 16/16 quick gate. Its existence demonstrates why initial merge and Bead closure must be separate funnel stages. + +## Capture architecture evidence + +- #2913 introduced the authenticated Sol Pro launch queue and a live submission smoke. +- #2918 removed duplicate handoff-byte transport and made canonical capture authoritative; its broad fresh-worktree run stopped at 22% after unrelated failures. +- #2919 removed launch-only capture metadata and reconciled by provider-native conversation ID. +- #2926 recorded a live closed-tab canonical output ZIP proof and hardened pacing, transport ownership, and output validation. +- #2928 removed campaign-specific launch/work-package semantics from product code, retaining generic BrowserAction plus automatic freshness convergence. +- #2930 repaired changed-file reingest and compatible browser snapshot ordering after the 27-conversation incident census. + +These commits show that the campaign ran across a moving control plane. Prompt, cadence, and model-output comparisons are therefore confounded by transport and ingestion changes. + +## Timing evidence + +Canonical package timestamps and direct PR timestamps yield the following impossible-as-latency relationships: + +| Package / PR | Canonical package timestamp | PR created | PR merged | Interpretation | +| --- | --- | --- | --- | --- | +| 2 / #2922 | 02:44:07 | 03:43:21 | 04:17:18 | Creation after custody; plausible but not proof of acquisition latency. | +| 12 / #2923 | 03:55:02 | 03:43:22 | 04:13:04 | PR created 11m40s before canonical custody timestamp. | +| 23 / #2924 | 06:14:18 | 04:42:00 | 05:52:25 | PR merged 21m53s before canonical custody timestamp. | +| 25 / #2925 | 06:14:30 | 05:22:21 | 07:03:34 | PR created 52m09s before canonical custody timestamp. | + +The direct PR open-to-merge durations are valid GitHub chronology. The package timestamps are not valid stage chronology. + +## Missing evidence + +The supplied project-state archive lacks: + +- raw canonical exports or a complete roster for the 27/28 campaign sessions; +- provider action/run receipts and Retry-After/error telemetry; +- exact first-turn, terminal-turn, and artifact-offered events; +- historical canonical output attachment refs/blobs for the 28 packages; +- live replay results after #2930 and current `ops.db` capture-gap events; +- per-attempt immutable `result.json` records for this prior campaign; +- local repair start/end times, line retention, and human review effort; +- detailed machine-readable verification runs for each package integration; +- exact uncommitted snapshot delta despite the dirty flag. + +Because these are absent, the report marks terminal-turn conversion, acquisition latency, repair cost, and live capture recall as unresolved rather than estimating them. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/FUNNEL.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/FUNNEL.json new file mode 100644 index 000000000..56e2ed803 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/FUNNEL.json @@ -0,0 +1,181 @@ +{ + "entities": { + "campaign_conversations_authoritative_census": { + "confidence": "source_supported", + "count": 27, + "evidence": "polylogue-3v1 production incident note" + }, + "canonical_unique_assets": { + "confidence": "observed", + "count": 28 + }, + "captured_sessions_broad_temporal_envelope": { + "confidence": "source_supported", + "count": 28, + "evidence": ".agent/handoffs/polylogue-sol-pro-2026-07-15/README.md" + }, + "exact_duplicate_redownloads": { + "confidence": "source_supported", + "count": 17 + }, + "package_bearing_unique_conversation_ids": { + "confidence": "observed", + "count": 26 + }, + "package_revisions": { + "confidence": "observed", + "count": 28 + }, + "raw_download_observations": { + "confidence": "source_supported", + "count": 45 + } + }, + "job_id": "analysis-02", + "package_dispositions": { + "already_subsumed": 2, + "incorporated_pending_delivery": 12, + "merged": 4, + "rejected": 2, + "research_incorporated": 4, + "superseded_or_duplicate": 4 + }, + "package_size": { + "causal_interpretation": "not_supported", + "compressed_bytes_total": 65938477, + "direct_merge_bytes_share": 0.0029149748181171975, + "direct_merge_compressed_bytes_total": 192209, + "direct_merge_median_compressed_bytes": 54413.0, + "ge_1_mib_compressed_bytes": 64865984, + "ge_1_mib_direct_merges": 0, + "non_direct_merge_median_compressed_bytes": 76849.5, + "packages_ge_1_mib": 10, + "uncompressed_bytes_total": 68696641 + }, + "schema_version": 1, + "scope_note": "Counts use distinct entities. Rates are present only when numerator and denominator share an entity and the denominator is evidenced.", + "snapshot": { + "branch": "master", + "commit": "f654480cadb7cc4c194704e24dfd483199547b35", + "dirty": true, + "generated_at": "2026-07-17T043202Z" + }, + "stages": [ + { + "confidence": "source_supported", + "count": 27, + "entity": "provider_conversation", + "limitation": "No provider action receipts or raw roster; broad temporal set contains 28 sessions.", + "rate": null, + "stage": "submitted_conversation" + }, + { + "confidence": "unresolved", + "count": null, + "entity": "provider_turn", + "limitation": "Raw canonical session/turn exports are absent. Package existence is not a terminal-turn count.", + "rate": null, + "stage": "terminal_turn_observed" + }, + { + "confidence": "source_supported", + "count": 45, + "entity": "download", + "rate": null, + "stage": "asset_download_observation" + }, + { + "confidence": "observed", + "count": 28, + "denominator": 45, + "entity": "package_revision", + "note": "Denominator is download observations, so this is deduplication efficiency, not conversation conversion.", + "rate": 0.6222222222222222, + "stage": "asset_acquired_unique" + }, + { + "confidence": "observed", + "count": 28, + "denominator": 28, + "entity": "package_revision", + "rate": 1.0, + "stage": "asset_hash_and_zip_validated" + }, + { + "confidence": "observed", + "count": 28, + "denominator": 28, + "entity": "package_revision", + "rate": 1.0, + "stage": "triaged" + }, + { + "confidence": "observed", + "count": 20, + "definition": "merged + research_incorporated + incorporated_pending_delivery", + "denominator": 28, + "entity": "package_revision", + "rate": 0.7142857142857143, + "stage": "new_work_retained" + }, + { + "confidence": "observed", + "count": 22, + "denominator": 28, + "entity": "package_revision", + "rate": 0.7857142857142857, + "stage": "routed_or_already_subsumed" + }, + { + "confidence": "source_supported", + "count": 4, + "denominator": 28, + "entity": "package_revision", + "rate": 0.14285714285714285, + "refs": [ + "PR#2922", + "PR#2923", + "PR#2924", + "PR#2925" + ], + "stage": "direct_package_pull_request" + }, + { + "confidence": "source_supported", + "count": 4, + "denominator": 28, + "entity": "package_revision", + "rate": 0.14285714285714285, + "stage": "direct_package_merge" + }, + { + "confidence": "lower_bound", + "count": 2, + "entity": "delivery_track", + "limitation": "In-PR repair events and effort were not normalized.", + "rate": null, + "refs": [ + "PR#2953 CaptureJobs reimplementation", + "PR#2957 parser-drift replay repair" + ], + "stage": "material_post_package_repair_or_reimplementation" + }, + { + "confidence": "observed_at_snapshot", + "count": 2, + "denominator": 4, + "entity": "direct_delivery_track", + "rate": 0.5, + "stage": "direct_merge_owner_closed" + }, + { + "confidence": "derived", + "count": 3, + "definition": "four direct merges plus one CaptureJobs local reimplementation", + "denominator": 5, + "entity": "delivery_track", + "rate": 0.6, + "stage": "code_delivery_track_owner_closed" + } + ] +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/MANIFEST.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/MANIFEST.json new file mode 100644 index 000000000..381260e90 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/MANIFEST.json @@ -0,0 +1,44 @@ +{ + "generated_at": "2026-07-17T05:31:47.373365+00:00", + "job_id": "analysis-02", + "members": [ + { + "name": "DECISIONS.md", + "sha256": "09f9b65ad9df4a7afe7bf97fe9867da897803ef7e5cfee88c20cef0accb6bd77", + "size_bytes": 10283 + }, + { + "name": "EVIDENCE.md", + "sha256": "f958c7769c2bfaccab4c121f641afb9ec48bf1a0057477232c1de0733f622b6e", + "size_bytes": 14232 + }, + { + "name": "FUNNEL.json", + "sha256": "731f1e83fd5af3a83e80989127105ef476427a1a7c23c89a908e1bfd1ac08bf6", + "size_bytes": 5191 + }, + { + "name": "NEXT-ACTIONS.md", + "sha256": "e8f4f6714425182379e8b10275f03a483cfc2187ed8236ec16a79c361f9322c0", + "size_bytes": 10872 + }, + { + "name": "PACKAGES.csv", + "sha256": "59318781ecef842bf52af7b8dc499f79d3aafde64fb0584b8961aac812ee2692", + "size_bytes": 14504 + }, + { + "name": "REPORT.md", + "sha256": "3b58ad0c55de896c93e758f0fcb00b27d9caf5776f2e2c98e3dbf7ac0042a673", + "size_bytes": 14377 + }, + { + "name": "RESULT-TELEMETRY-V2.schema.json", + "sha256": "73978bce131206452315d58a3044861080a56b52fa4d8713ec2dd321fee1b2d2", + "size_bytes": 16125 + } + ], + "result_revision": "r01", + "schema_version": 1, + "source_snapshot_commit": "f654480cadb7cc4c194704e24dfd483199547b35" +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/NEXT-ACTIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/NEXT-ACTIONS.md new file mode 100644 index 000000000..287b17de1 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/NEXT-ACTIONS.md @@ -0,0 +1,115 @@ +# Ordered next actions + +## P0 — before the next broad campaign wave + +### 1. Close the historical capture-evidence gap or mark it irrecoverable + +**Owner:** `polylogue-3v1`, with generic action correlation under `polylogue-ptx`. + +Deploy/replay the #2930 replacement/reingest path against the 27-conversation campaign census. Produce one immutable receipt per conversation containing current provider/native identity, observed terminal turn, offered output attachments, captured attachment refs/blobs, and replay outcome. For each of the 28 package revisions, either match the exact SHA-256 to canonical capture or record that historical recovery is impossible and preserve the manual-download receipt as degraded custody. + +**Acceptance:** No campaign conversation is silently absent; all 27 have an explicit terminal/capture outcome. Every offered output is acquired or has a typed failure. The index/raw projection matches the current source files after replay, and unchanged poison observations remain excluded while changed replacements revive. `polylogue-3v1` is not closed until its remaining Gemini and durable capture-gap criteria are independently adjudicated. + +**Local verification:** run the production watcher/membership/replacement tests from #2930; run a closed-tab live output fixture through ordinary `/v1/browser-captures`; query the durable capture-gap projection; compare canonical artifact hashes to manual custody. + +### 2. Land result schema v2 as an extension of the existing campaign model + +**Owner:** `polylogue-yyvg.6`. + +Replace `.agent/handoffs/external-agent-campaigns/schemas/result.schema.json` with the reviewed version derived from `RESULT-TELEMETRY-V2.schema.json`. Keep existing stable IDs and fields; add action/run identity, prompt shape, snapshot details, package composition, immutable stage events, structured triage, repair, verification, PR, and Bead outcomes. + +**Acceptance:** Existing v1 fixtures have an explicit migration path. New results validate with `additionalProperties: false`. Event timestamps require source and evidence refs. Unknown historical events are omitted or represented by a typed uncertainty record, never fabricated. + +**Local verification:** JSON Schema validation for a normal implementation, same-chat repair, analysis-only response, manual-download degraded acquisition, rejection, and later Bead closure. Mutation fixtures must fail when terminal, artifact, merge, or closure events are deleted. + +### 3. Backfill this campaign into immutable attempt receipts + +**Owner:** `polylogue-yyvg.6`. + +Create a one-time importer from `INTEGRATION-LEDGER.json`, `SHA256SUMS`, package ZIPs, Git/PR history, and Beads. Represent 26 package-bearing conversations, 28 package revisions, 45 download observations, four direct merges, the CaptureJobs reimplementation, and the parser-drift repair. Record the 27/28-session discrepancy as uncertainty rather than choosing one count. + +**Acceptance:** Rebuilding the projection reproduces `FUNNEL.json`. Package 24 retains its original preserved-draft decision while its current owner projects closed through #2953. Direct PRs and merge commits are normalized. All imported timestamps state their source kind; canonical download timestamps are not labeled terminal or acquisition-start time. + +### 4. Upgrade package triage without executing unreviewed tests + +**Owner:** `.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/triage-package.py` and then the shared campaign tool. + +Retain the current required-member, copied-input, placeholder, and detached-worktree `git apply --check` behavior. Add result-v2 receipt writing, content composition, largest-member declarations, snapshot/prompt hash checks, duplicate hash detection, expected filename/revision checks, and route to one of implementation, repair, analysis/research, duplicate, rejected, or blocked. + +**Acceptance:** Package 13’s synthetic SQL bulk and package 24’s oversized rollout-plan document are visible as dominant content; package 27’s 432-byte patch is not obscured by its fixture. The tool does not run `TESTS.md`. It creates `result.json` under `results///`, stores raw evidence immutably, and records a deterministic extraction receipt. + +### 5. Make dispatch gating depend on campaign evidence state + +**Owner:** `check-dispatch.py`, then the shared campaign orchestrator. + +Add gates for: authoritative snapshot hash; prompt hash; owning Bead/dependency status; no duplicate active mission footprint; prior-wave terminal/acquisition reconciliation; integration WIP; and result-schema compatibility. Keep the current foundation and context-manifest checks. + +**Acceptance:** The command names every unmet gate. A missing terminal artifact, stale snapshot, closed/superseded owner, overlapping write set, or over-cap integration queue blocks only affected jobs. Analysis/research jobs are not blocked for lacking a patch lane. + +## P1 — prompt, routing, and integration changes + +### 6. Add explicit mission shape and integration contract to rendered prompts + +**Owner:** shared contracts under `.agent/handoffs/external-agent-campaigns/contracts/` and `render_prompts.py`. + +For implementation prompts, require: current snapshot commit/archive hash; one primary Bead and any dependency owners; named production route; allowed and forbidden paths; existing interfaces that must be reused; exact residual scope; one anti-vacuity mutation; expected result filename/revision; and a machine-readable result receipt. State that a coherent smaller slice is preferred to a parallel framework. + +For repair prompts, include the prior attempt/package hash, exact failed review/test receipt, current source delta, and `supersedes_attempt_id`. For analysis/research prompts, require decision ownership, falsification evidence, and no placeholder patch. + +**Acceptance:** A prompt renderer check proves every job has stable title, result identity, prompt hash, owner, shape, and snapshot. Same-chat continuation renders a new attempt/revision rather than overwriting the old result. + +### 7. Route before generation + +**Owner:** workload coordinator and owning Beads. + +Use implementation only when dependencies are landed and the production route is current. Route cross-cutting identity, provider-policy, architecture reconciliation, and dependency-premature kernels to analysis/research. Route known review failures to same-chat repair rather than fresh duplicate chats. Reject missions that ask an external package to certify live deployment, daemon state, provider policy, performance, or destructive-route completeness without the required local evidence. + +**Acceptance:** Every job has one disposition hypothesis before launch and a named local owner. The hypothesis can be disproved at triage but cannot be absent. Competing seams and duplicate mission footprints are detected before dispatch. + +### 8. Standardize the local integration receipt + +**Owner:** integration coordinator; `results///integration/`. + +Record: fresh base commit; worktree/branch; package patch apply status; current-source reconciliation; retained, rewritten, and discarded paths/lines; repair reason; focused production-route tests; anti-vacuity mutation; broader gate outcome and inherited failures; PR; merge; deployment if in scope; and Bead outcome. + +**Acceptance:** Package-to-final retention and local repair effort become measurable. A reimplementation such as #2953 is visibly different from a direct patch merge. A later correction such as #2957 links to the original delivery track without inflating package merge count. + +### 9. Synchronize Bead closure with delivered scope, not PR existence + +**Owner:** owning Bead maintainers and projection builder. + +At merge, record which acceptance criteria are satisfied, which residual Bead owns the rest, and whether closure follows. Keep partial slices open. Rebuild current campaign outcomes from Bead state rather than rewriting immutable package decisions. + +**Acceptance:** #2922 projects as merged with owner open; #2924 as merged with owner in progress; #2925 as merged with owner closed; #2953 as local reimplementation with owner closed. No report treats those as equivalent closure outcomes. + +## P2 — campaign cadence and reporting + +### 10. Run a 3–5 attempt evidence pilot, then expand conditionally + +Launch a mixed pilot after P0: two narrow implementation jobs, one same-chat repair, and one or two analysis/research jobs. Do not expand until every attempt is terminally reconciled, every expected asset has a typed outcome, every package is validated/triaged, and the integration queue is at or below the 3–4 active-worktree cap. + +After that gate, 8–12 dependency-disjoint implementation attempts are reasonable. Generation can be parallel; shared architecture decisions, fixtures, local execution, mutation proof, and publication remain serialized or lane-bounded. A second wave launches only when prior results have current owners and no unresolved capture gap. + +### 11. Publish a campaign effectiveness report from receipts, not prose + +**Owner:** `polylogue-yyvg.6`. + +Generate counts and duration distributions for submission success, first/terminal turn, acquisition, validation, triage, repair, verification, PR, merge, and Bead closure. Stratify by prompt shape, dependency readiness, current-source apply result, package content composition, repair/fresh attempt, and owning area. Keep descriptive association separate from causal claims. + +**Acceptance:** The report can reproduce this campaign’s known counts while marking missing terminal evidence. It must reject impossible chronology, such as a PR supposedly starting after an acquisition timestamp that occurs later, unless an alternate evidence source is explicitly recorded. + +## Stop conditions + +Pause dispatch when any of the following occurs: + +- terminal output exists without a canonical acquisition or typed failure; +- snapshot or prompt hashes drift after launch; +- duplicate mission footprints or same owner/write set are active without declared coordination; +- the untriaged result queue exceeds available integration lanes; +- package validation finds copied inputs, undeclared bulk, placeholders, unsafe paths, or a patch that cannot apply to the claimed snapshot; +- current Beads/source invalidate the mission; +- provider rate/safety circuits lack typed receipts or cooldown evidence. + +## Expected value + +The P0 work has high value because it converts the principal campaign uncertainty—terminal/output custody and stage timing—into measurable evidence and prevents recurrence. Prompt wording refinements alone have moderate value; the current contracts are already strong. Scaling generation before capture replay, immutable receipts, and integration gating would mostly increase unmeasured queue depth rather than trustworthy delivered yield. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/PACKAGES.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/PACKAGES.csv new file mode 100644 index 000000000..5a455c6bd --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/PACKAGES.csv @@ -0,0 +1,29 @@ +package_no,sha256,download_timestamp,conversation_id,conversation_title,mission,zip_bytes,uncompressed_bytes,member_count,dominant_content,largest_member,patch_count,touched_path_count,disposition,delivery_state,delivery_constraint_kind,primary_bead,final_owner,owner_state,direct_pr,direct_merge_commit,preserved_branch,current_master_evidence +1,8f37aa16b083c357c32b426d44379c96ef49acd692f7b569b2d5f4d8fc8470fd,20260715-230840,6a57e0b9-b3fc-83ed-9e7a-9fcf172698a3,Implementation-grade Handoff,Implementation-grade Handoff,8388608,8384388,32,patch,PATCHES/REPLACEMENTS.tar (8054930 bytes),5,19,already_subsumed,already_subsumed,,polylogue-yyvg.5,polylogue-yyvg.5 / polylogue-yyvg.6,polylogue-yyvg.5=closed; polylogue-yyvg.6=in_progress,,,,"PRs #2913, #2918, #2919, #2926, and #2928; current master removed campaign-specific product concepts." +2,5e2363a19a6493b93446a5feff75ff4047bebd5e2ca7f8eb0de62526117ed77e,20260716-024407,6a57f420-1bb0-83ed-8205-6cf85427736f,Lineage order independence,Lineage order independence,40844,163175,21,design_docs_metadata,DESIGN/production-path.md (89725 bytes),3,4,merged,merged,,polylogue-866e,polylogue-866e,polylogue-866e=open,2922,b55f3fd9697083d44466613091604a21c7324ae6,,"PR #2922, merge b55f3fd9697083d44466613091604a21c7324ae6." +3,4ddd843c064bd1ddc06cd39500a0ca4898e4f01bbbf7aa350ac86207402ef2ed,20260716-024440,6a57fc1e-0cec-83eb-b0e9-bcaea3e722b8,Definition closure graph,Definition closure graph,47850,226639,23,patch,TESTS/definition-closure-report.json (87426 bytes),3,6,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-9e5.31.1,polylogue-9e5.31.1,polylogue-9e5.31.1=open,,,,Current polylogue-9e5.31.1 is open and explicitly awaits the DeclarationSpec kernel polylogue-o21.1. +4,733092b30e6427c26096211d0603e824f0709e2e6632c488ad07728307b9703d,20260716-024453,6a57f377-2000-83ed-9567-bc875a00a61a,Provider-native identity,Provider-native identity,8388608,8352893,23,fixture_snapshot_archive,DESIGN/SNAPSHOT-AUDIT-APPENDIX.md (8080559 bytes),4,20,research_incorporated,research_incorporated,,polylogue-yyvg.4,polylogue-06zm.1 / polylogue-jlme.5,polylogue-06zm.1=closed; polylogue-jlme.5=open,,,,Current generic BrowserAction transport in PR #2928 and CaptureJob bead design own the executable form. +5,2d66993b4890e71119d5609707572d23fccba13989a0ab228676651d1e9e7f8c,20260716-024506,6a57fba2-0ffc-83ed-8d59-59e703412080,Continuity oracles,Continuity oracles,1048576,1045374,24,fixture_snapshot_archive,TESTS/archive-integrity-sentinel.bin (853673 bytes),3,14,rejected,rejected,,polylogue-t8t,polylogue-t8t,polylogue-t8t=open,,,,PR #2922 records that the patch covered two of seven routes and introduced a competing scenario seam. +6,ca9526ba04461c369bb82127a5e730770ed55bcce5d09fb4169d1babacbd5966,20260716-024516,6a57f4d4-2000-83eb-a721-0e6363e958c0,Bounded query transaction A,Bounded query transaction A,4194304,4188698,15,fixture_snapshot_archive,TESTS/fixtures/cancellation-load.sqlite3 (4063232 bytes),1,9,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-z9gh.1,polylogue-z9gh.1,polylogue-z9gh.1=open,,,,Current polylogue-z9gh.1 owns a shared execution-control layer; no current-master executor exists. +7,0a91ceaa6451a7d301278d6eda35af1fb252f98d51a7f562f119df8726330cec,20260716-024523,6a57f4d4-2000-83eb-a721-0e6363e958c0,Bounded query transaction B,Bounded query transaction B,4194304,4192372,15,fixture_snapshot_archive,TESTS/fixtures/serialization-budget-boundary.jsonl (4034424 bytes),1,8,superseded_or_duplicate,superseded_or_duplicate,,polylogue-z9gh.1,polylogue-z9gh.1,polylogue-z9gh.1=open,,,,Same conversation and semantic target; canonical shared transaction is polylogue-z9gh.1/.9. +8,0a73157c0c53f17e1ebe8b5a60a51adede11e7e563d8fd1651403e2d5777973e,20260716-024540,6a580996-4d8c-83eb-b1d4-8226a72bf21d,Stable receiver pairing,Stable receiver pairing,3000000,2976647,14,fixture_snapshot_archive,TESTS/archive-integrity-corpus.bin (2958497 bytes),1,4,research_incorporated,research_incorporated,,polylogue-jlme.5,polylogue-jlme.5,polylogue-jlme.5=open,,,,PRs #2926/#2928 landed identity/pairing diagnostics; two-profile proof remains open in polylogue-jlme.5. +9,542278830b909d48bd1418a2b93987f2e381a1cf35019070722baa563b5fd566,20260716-024553,6a580989-2a80-83ed-8fdf-1f24543905d7,Destructive-operation authorization (early),Authorize and receipt every destructive operation with domain-owned plans and actuators.,57982,266398,22,design_docs_metadata,DESIGN/census.json (124202 bytes),3,3,superseded_or_duplicate,superseded_or_duplicate,,polylogue-kwsb.2,polylogue-kwsb.2,polylogue-kwsb.2=open,,,,Later MutationTransaction package 76a1279fe519 is the canonical iteration. +10,073651c8d9b3f00a0da1956269abf6cd0d37be9f9afd2ca92e24524d00d69437,20260716-024606,6a58079b-0f70-83eb-af7e-7ab72440c8eb,Query discovery A,Generate Polylogue agent query discovery from executable query declarations and eliminate parser-invalid product curriculum.,65443,228491,13,patch,PATCHES/0001-feat-query-declare-executable-discovery-contracts.patch (104689 bytes),3,17,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-z9gh.3,polylogue-z9gh.3,polylogue-z9gh.3=open,,,,Current polylogue-z9gh.3 remains open and depends on the unified query lane. +11,e426074411b86da54e10b00bccd3c364cecf10cb35dbc14c55a30a00c7943514,20260716-024616,6a580976-03d0-83eb-af6a-eb745db5ac0c,Query discovery B,Make agent queries self-discovering and executable,65040,214385,16,patch,PATCHES/0001-feat-query-declare-executable-agent-discovery-regist.patch (80786 bytes),3,14,superseded_or_duplicate,superseded_or_duplicate,,polylogue-z9gh.3,polylogue-z9gh.3,polylogue-z9gh.3=open,,,,Semantic duplicate/alternative of 073651c8d9b3; same owner and no separate registry allowed. +12,ee58f7411a931d3920e5f57024fc0cca4d74bf7e2419918c87b3d6576157ab41,20260716-035502,6a581170-10cc-83eb-ba33-c7199fbe3d2f,Raw-authority repair,Raw-authority repair,15136,34022,9,design_docs_metadata,PATCHES/0001-fix-live-multi-session-authority.patch (8925 bytes),1,2,merged,merged,,polylogue-lkrc.4,polylogue-lkrc.4,polylogue-lkrc.4=closed,2923,81142d1dce7d8e896ef340783ab943cdf59143f6,,"PR #2923, merge 81142d1dce7d8e896ef340783ab943cdf59143f6." +13,2df5dc1c22e38166b08ff2fd9c7e609483ed61f19641dcc92c44e23c5d09ed80,20260716-035623,6a580da1-1854-83ed-acd3-aaf72bc35dc9,Selective action/delegation,Selective action/delegation,16777216,16774424,21,fixture_snapshot_archive,TESTS/synthetic-archive-fixture.sql (16745651 bytes),4,0,rejected,rejected,,polylogue-z9gh.1,polylogue-z9gh.1,polylogue-z9gh.1=open,,,,Patch was placeholder-heavy and did not establish a production execution route. +14,18dd421a9b9fb5e18aa9e48b39c588089d23f60407087ef89192a6ff3ac737ad,20260716-061242,6a582e7d,Query execution control,polylogue-z9gh.1 Make archive queries interruptible and resource-bounded,42915,151908,12,patch,PATCHES/0001-make-archive-queries-interruptible.patch (99353 bytes),1,7,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-z9gh.1,polylogue-z9gh.1,polylogue-z9gh.1=open,,,,Current polylogue-z9gh.1 design names these as the authoritative execution primitives; master still has synchronous per-surface execution. +15,0d45bbbc8ddb44a4e1fdff499e0fe39f8c8cc8413e7cd308fca8a70d9b8ce78a,20260716-061307,6a583f80,Objective posture/resumability,Derive session resumability from evidence-bearing open obligations,75537,278346,30,patch,PATCHES/0004-test-objective-cover-projection-and-shared-consumers.patch (45957 bytes),4,17,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-37t.23,polylogue-37t.23,polylogue-37t.23=open,,,,Current polylogue-37t.23 is open; insight/storage authority is not yet reconciled. +16,c33e83027957ab5819a5fd2585e9df1bfa049a913c3f4cebb1cc06c721f72e1a,20260716-061317,6a583fbd,ArchiveLocation identity,"polylogue-ovme.1: Land ArchiveLocation identity, resolution, and split-tier canaries",38811,128661,13,patch,PATCHES/0001-feat-storage-resolve-archive-tiers-through-typed-loc.patch (58345 bytes),2,8,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-ovme.1,polylogue-ovme.1,polylogue-ovme.1=open,,,,Current polylogue-ovme.1 is open; deployment/index activation is expressly outside this mission. +17,1d287d6cd7c6e0f7496a03bf24f411e204f8efa873e773ff52bce237f0596fde,20260716-061322,6a58008f,Workload/resource receipts,Workload/resource receipts,74086,263846,31,patch,PATCHES/0002-feat-devtools-collect-bounded-Linux-workload-receipt.patch (109299 bytes),3,14,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-1xc.14,polylogue-1xc.14,polylogue-1xc.14=open,,,,Current polylogue-1xc.14 is open and now has schema-derived workload-profile child polylogue-1xc.14.1. +18,cd7ab67ea3a1db513afbd22b061a166243dbd7168d2a9166b8a42f9395a688f5,20260716-061330,6a5837da,DeclarationSpec kernel,DeclarationSpec kernel,42603,125027,19,patch,PATCHES/0001-feat-declarations-add-typed-declaration-kernel.patch (36521 bytes),4,11,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-o21.1,polylogue-o21.1,polylogue-o21.1=open,,,,Current polylogue-o21.1 is open and named prerequisite for downstream work. +19,6c79bd86766a8f1c9103890b7e833d162f49cf10be393a01aede20c150ad3725,20260716-061337,6a581929,Handoff incorporation workspaces,"Turn validated Sol Pro handoffs into durable receiver-authoritative intake, explicit cluster workspaces, independent verification, and normal publication work.",78162,253881,26,patch,PATCHES/0001-feat-capture-plan-durable-Sol-Pro-handoff-intake.patch (78324 bytes),3,11,research_incorporated,research_incorporated,,polylogue-yyvg.6,polylogue-yyvg.6,polylogue-yyvg.6=in_progress,,,,Current polylogue-yyvg.6 design explicitly keeps campaign orchestration outside the browser. +20,251332b72bd838ac5678e308fe96804935168255ff14ee71f0a9be835a7f7362,20260716-061344,6a5837a1,Durable schema change train,Durable schema change train,74760,274618,26,patch,PATCHES/0001-feat-storage-add-durable-change-train-lifecycle.patch (104932 bytes),6,5,incorporated_pending_delivery,awaiting_deployment,deployment_gate,polylogue-60i5.1,polylogue-60i5.1,polylogue-60i5.1=open,,,,Current polylogue-60i5.1 is open; PR #2931 deployment work is coordinator-owned and excluded. +21,cdc06754e7ce511fe85a12fe4a83c24f807dfb6429c9b3fcb4df334c487567f8,20260716-061355,6a58385b,OriginSpec separation,OriginSpec separation,62623,164624,23,design_docs_metadata,TESTS/verify_snapshot.py (23653 bytes),3,3,incorporated_pending_delivery,awaiting_dependency,dependency_wait,polylogue-2qx.1,polylogue-2qx.1,polylogue-2qx.1=open,,,,Current polylogue-2qx.1 is open and blocked by polylogue-o21.1. +22,7fa320242b2c6aa6a92e3eada4299e8355a8628eefc41cb4846327f1c6205080,20260716-061408,6a5830bc-0d94-83ed-8d4f-6136a748bc19,Extension mission control,Extension mission control,81240,287207,19,patch,PATCHES/0002-ambient-mission-control-phase.patch (206044 bytes),2,17,already_subsumed,already_subsumed,,polylogue-3v1,polylogue-3v1 / polylogue-ptx,polylogue-3v1=in_progress; polylogue-ptx=in_progress,,,,PR #2926 merge 54e8911b9e5cb8e7d53597f04417cf040d28fb4d and PR #2928 merge 165e6a03416a3db2cb75001d370dfd738bdd992b. +23,17d8a28e9c6a2ae7dbbf380b31f41e8b9c84d115b6bd690d6a4aa65b8c61384e,20260716-061418,6a5823b5-15f4-83ed-8432-768e9452b460,Named-source freshness implementation,Expose named-source freshness and excluded cursor degradation.,68247,223157,23,patch,PATCHES/0002-feat-archive-add-bounded-named-source-freshness-proj.patch (68084 bytes),4,9,merged,merged,,polylogue-1xc.13,polylogue-1xc.13,polylogue-1xc.13=in_progress,2924,b6c78adfcd666358307daf64ac97e8d695a8b854,,PR #2924 merge b6c78adfcd666358307daf64ac97e8d695a8b854. +24,3ca08cd43d04d66114ba5f44df64b73eab9ab4f31826ed87548a6d8b7de4393a,20260716-061424,6a57f545-56a0-83eb-b961-e81c7d030e70,Durable CaptureJobs,Durable CaptureJobs,8388608,8472997,26,design_docs_metadata,DESIGN/10-rollout-plan.md (8334818 bytes),3,9,incorporated_pending_delivery,preserved_draft_awaiting_reconciliation,preserved_draft_reconciliation,polylogue-06zm.1,polylogue-06zm.1,polylogue-06zm.1=closed,,,feature/integration/capture-job-authority,"Published preservation branch feature/integration/capture-job-authority (ba340c71a, 8ecc34ecc); current master only has opaque per-extension backfill mirrors. Fresh-master comparison found unsafe fallback account scope paired: in browser adapter, so it cannot prove exact-scope profile-loss adoption." +25,6b6f67183ddad2866a2ee6e349f55f5a603172140be1c494dd7976f5533614ee,20260716-061430,6a57f60c-1c74-83ed-81e0-94e1072a7eb0,Sinex publication convergence,Sinex publication convergence,67982,265112,17,patch,PATCHES/0003-supervised-publication-convergence.patch (76206 bytes),4,20,merged,merged,,polylogue-303r.2.1,polylogue-303r.2.1,polylogue-303r.2.1=closed,2925,36001d023b2cfe793cb19fdd7c42a87597356f48,,PR #2925 merge 36001d023b2cfe793cb19fdd7c42a87597356f48; deployment transport remains polylogue-303r.2.2/coordinator scope. +26,8fa6ec8272818731e5b62a5a6c9e936b0500834e179c7fd27c1eb0493a4b2a2b,20260716-061438,6a5823b5-15f4-83ed-8432-768e9452b460,Named-source freshness design,Named-source freshness design,2097152,2107998,18,fixture_snapshot_archive,TESTS/fixtures/excluded-growing.jsonl (2083481 bytes),2,0,superseded_or_duplicate,superseded_or_duplicate,,polylogue-1xc.13,polylogue-1xc.13,polylogue-1xc.13=in_progress,,,,Same conversation; canonical implementation merged as PR #2924. +27,85acc7ce2cffcf9bbd734511ec739376a909ae407986b974369e8344a44fa168,20260716-061444,6a582757-0bcc-83ed-99aa-b36f86a9ba50,Receiver pairing runtime identity,Receiver pairing runtime identity,8388608,8384417,12,fixture_snapshot_archive,TESTS/fixtures/receiver-pairing-preserved-drain.jsonl (8335266 bytes),1,0,research_incorporated,research_incorporated,,polylogue-jlme.5,polylogue-jlme.5,polylogue-jlme.5=open,,,,PR #2926/#2928 pairing work and polylogue-jlme.5 retain residual multi-profile proof. +28,76a1279fe519f73891a7266a3863b6675d86c41305255ca2dc0e952a6941baf7,20260716-070230,6a582014-0f04-83eb-a310-4034d9173e88,MutationTransaction,MutationTransaction,73232,266936,24,patch,PATCHES/0001-security-add-plan-bound-mutation-transaction-kernel.patch (86647 bytes),3,11,incorporated_pending_delivery,incorporated_design_awaiting_admission,admission_prerequisite,polylogue-kwsb.2,polylogue-kwsb.2,polylogue-kwsb.2=open,,,,Current polylogue-kwsb.2 remains open; no route census/current-master proof supports merging its stale patch. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/REPORT.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/REPORT.md new file mode 100644 index 000000000..2dd6b2b83 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/REPORT.md @@ -0,0 +1,110 @@ +# External-agent campaign effectiveness postmortem + +Job: `analysis-02` +Result revision: `r01` +Evidence snapshot: Polylogue `master` at `f654480cadb7cc4c194704e24dfd483199547b35`, snapshot generated `2026-07-17T043202Z`, marked dirty +Primary campaign corpus: `.agent/handoffs/polylogue-sol-pro-2026-07-15/` + +## Executive assessment + +The campaign generated substantial usable engineering input, but generation throughput outran trustworthy capture and integration throughput. The strongest honest package-level result is **4 direct merges from 28 canonical package revisions (14.3%)**. A broader **20/28 (71.4%)** supplied new work retained as merged code, research, or an explicit downstream delivery input; two more were already subsumed by current work. The remaining six were superseded alternatives or rejected. All 28 packages were hash-valid and adjudicated, so low direct-merge yield was not an intake-completeness problem. + +The direct merges were compact, current-source-anchored missions with a coherent production contract, an owning Bead, bounded residual scope, and independent local verification. Broad architecture packages, dependency-premature kernels, competing seams, and large fixture-heavy packages more often became research, pending input, superseded work, or rejection. This is an association, not evidence that small size or narrow scope alone causes merge success. + +The most serious campaign failure was evidence custody. The contemporaneous audit states that Polylogue held all source sessions and 29 input attachments but no `*launch-handoff*` output attachment references, leaving 45 manual browser downloads as the durable acquisition path. The incident was first misdiagnosed as missing output capability, then corrected to stale completion payload, tab-lifetime, snapshot replacement, and cursor exclusion defects. PRs #2918, #2919, #2928, and #2930 changed the architecture and recovery path, but the supplied snapshot contains no live replay receipt proving that the historical outputs were recovered. + +## The honest funnel + +The campaign does not have one denominator. Conversations, package revisions, downloads, integration tracks, and Beads are different entities. Treating them as a single 28-item funnel creates false precision. + +| Stage | Observed result | Honest interpretation | +| --- | ---: | --- | +| Submitted conversations | 27 campaign conversations in the authoritative `polylogue-3v1` census | The broad session-correlation note lists 28 sessions in the temporal envelope. Raw action receipts are absent, so submission success and retries cannot be independently recounted. | +| Package-bearing conversations | 26 unique provider conversation IDs | Two conversations each produced two canonical package revisions. | +| Terminal turn | Not independently countable | The archive lacks the raw canonical session exports and per-turn terminal receipts. The existence of 28 downloadable packages proves output availability, not a one-to-one terminal-turn count. | +| Raw asset downloads | 45 | Manual browser acquisitions; timing is bursty and not a reliable completion timestamp. | +| Unique canonical assets | 28 | 17 exact duplicate re-downloads were removed: 37.8% duplicate-download rate and 62.2% unique acquisition efficiency. | +| Hash/ZIP validation | 28/28 | Every canonical ZIP matched its ledger hash and reopened successfully. | +| Triage/adjudication | 28/28 | Four merged, four research-incorporated, twelve pending delivery, two already subsumed, four superseded/duplicate, two rejected. | +| New work retained | 20/28 (71.4%) | Merged + research-incorporated + explicit pending-delivery inputs. | +| Routed or proven redundant | 22/28 (78.6%) | Adds the two already-subsumed packages. This is not equivalent to code delivery. | +| Direct package PR/merge | 4/28 (14.3%) | PRs #2922–#2925. | +| Material post-package repair | At least 2 identifiable tracks | CaptureJobs was locally reimplemented as PR #2953; the raw-authority path received a later parser-drift repair in PR #2957. In-PR coordinator repairs were not normalized as events. | +| Independent verification | 4/4 direct merges; both later repair PRs also verified | Focused production-route tests and quick gates are recorded. Full-suite evidence was not uniformly available or green. | +| Direct-merge owner closure | 2/4 | `polylogue-lkrc.4` and `polylogue-303r.2.1` closed; `polylogue-866e` remained open and `polylogue-1xc.13` remained in progress. | +| Code-delivery track closure | 3/5 | Counting four direct merge tracks plus the locally reimplemented CaptureJob track, `polylogue-06zm.1` also closed. This is a secondary attribution view, not a package conversion rate. | + +`FUNNEL.json` carries the same counts with explicit entity types, confidence, and denominator notes. + +## Reconciled campaign identity + +Four superficially conflicting counts refer to different scopes: + +- The package ledger contains 28 canonical package revisions. +- Those revisions map to 26 unique conversation IDs because packages 6/7 and 23/26 are same-chat alternatives or revisions. +- `polylogue-3v1` reports a production audit of 27 Sol Pro campaign conversations. +- The corpus README lists 28 captured sessions from 2026-07-15 21:34 through 2026-07-16 08:30, including the later `6a587a8c` “Live Action Proof Request”. + +The most plausible reconciliation is 27 campaign conversations plus one later proof session in the broad temporal set, with two package-bearing conversations producing two revisions each. That reconciliation is source-supported but not independently provable without the raw session roster. The earlier `polylogue-s2x7` one-session/one-deliverable framing is superseded. + +## Direct delivery outcomes + +| Package | Mission | PR / merge | Current owner state | Boundary that made the result honest | +| --- | --- | --- | --- | --- | +| 2, `5e2363a1…`, 40,844 B | Lineage order independence | #2922 / `b55f3fd…` | `polylogue-866e` open | Canonical sibling ordering and missing-cut behavior merged; persisted semantic cuts, crash proof, and complete nested propagation remained open. The competing continuity package was not applied. | +| 12, `ee58f741…`, 15,136 B | Raw-authority repair | #2923 / `81142d1…` | `polylogue-lkrc.4` closed later | A narrow complete-bundle admission defect merged; immutable census/conservation was explicitly not claimed. PR #2957 later repaired parser-drift membership replay. | +| 23, `17d8a28e…`, 68,247 B | Named-source freshness | #2924 / `b6c78ad…` | `polylogue-1xc.13` in progress | Bounded read-only exact-source projection merged; two live receipts and remediation authority remained outside the PR. | +| 25, `6b6f6718…`, 67,982 B | Sinex publication convergence | #2925 / `36001d0…` | `polylogue-303r.2.1` closed | Durable local publication obligations merged; real transport/deployment remained `polylogue-303r.2.2`. Coordinator review corrected an invalid cross-database atomicity claim to ordered durability. | + +These four packages total 192,209 compressed bytes, **0.29% of the 65,938,477-byte corpus**. Their median compressed size is 54,413 bytes. The 24 non-direct-merge packages have a median size of 76,850 bytes, but that comparison is heavily confounded by mission type and large embedded fixtures. + +## Package shape, content, and yield + +All 28 packages contained something classifiable as patch, tests, design, and report material. Therefore formal package completeness did not discriminate mergeability. + +Ten packages were at least 1 MiB. They account for 64,865,984 bytes, 98.4% of compressed corpus size, and none merged directly. The large members are mostly evidence or payload bulk rather than proportional implementation substance: an 8.05 MB replacements tar in package 1, an 8.08 MB snapshot appendix in package 4, a 16.75 MB synthetic SQL fixture in package 13, an 8.33 MB rollout-plan Markdown file in package 24, a 2.08 MB freshness fixture in package 26, and an 8.34 MB pairing fixture in package 27. Package size should therefore be measured by content composition and declared necessity, not used as a standalone quality cutoff. + +Mission shape was more informative: + +- Direct merges targeted a concrete current production route and admitted residual scope. +- Nine of the twelve pending packages were blocked by declared dependencies; one by deployment, one by preserved-draft reconciliation, and one by an admission prerequisite. +- Broad provider identity, pairing, and incorporation designs supplied useful constraints without a safe current patch. +- Duplicate alternatives appeared both within a chat and across missions. Same-chat lineage was not recorded because every ledger `iteration` field is null. +- Rejected work either covered too little of the claimed surface, introduced a competing seam, or depended on synthetic evidence that did not establish current product behavior. + +`PACKAGES.csv` records the 28 package revisions, dominant content category, disposition, ownership, and direct PR mapping. + +## Iteration and local repair + +The campaign’s most valuable implementation work often happened after package generation: + +1. The CaptureJobs package was preserved on `feature/integration/capture-job-authority` at `ba340c71a` / `8ecc34ecc`, with focused tests and a green quick gate, but was not merged because `paired:` could not prove exact account scope and the adapter conflicted with current generic BrowserAction transport. PR #2953 then implemented receiver-authoritative identity, HMAC-reduced provider account scope, leases, CAS checkpoints, receipts, recovery, and typed legacy orphans against current source, closing `polylogue-06zm.1`. +2. PR #2923 fixed the initial complete-bundle admission defect, while PR #2957 later fixed parser-drift replay against the persisted CAS witness and deployed that correction. The second PR is a real downstream repair, not a second package result. +3. PR #2925’s integration review corrected the package-era claim that source and index writes could be one cross-database transaction. The merged contract is ordered source durability before rebuildable index projection. +4. PR #2922 explicitly refused the continuity package’s competing scenario seam and incomplete route coverage while retaining a smaller current-source lineage fix. + +The ledger cannot quantify repair effort, changed lines, elapsed integrator time, or patch-retention percentage because it records no repair events and leaves PR/merge fields null even for direct merges. This missing telemetry prevents a trustworthy “package code retained” metric. + +## Timing and cadence + +The broad session window spans about 10 hours 56 minutes; canonical download timestamps span 23:08 through 07:02 and occur in bursts. The four direct package PRs were open for approximately 30, 34, 70, and 101 minutes before merge, with a median of about 52 minutes. + +Three direct PRs were created before the canonical package download timestamp, and PR #2924 merged before that timestamp. Therefore the filename timestamp is a custody or deduplication observation, not a generation-completion or integration-start timestamp. It cannot support acquisition-latency or time-to-PR analysis. The next campaign must emit stage events from provider submission through closure rather than derive timing from filenames and Git history. + +The campaign infrastructure itself evolved during execution: #2913 introduced the Sol-specific queue; #2918 made launch orchestration advisory and canonical capture authoritative; #2919 reconciled ordinary capture; #2926 operationalized canonical missions; #2928 removed campaign concepts from product code in favor of generic BrowserAction plus external orchestration; #2930 repaired replacement/reingest failures. This moving architecture is a major confounder for any prompt-level effectiveness claim. + +## Decisions + +The next campaign should preserve the current external campaign workspace and generic product boundary, extend the existing `schemas/result.schema.json` rather than invent a new product protocol, and make every funnel transition an evidence-backed immutable event. Dispatch should be dependency-aware and integration-capacity-aware. Implementation jobs should require one current owner, a named production route, bounded write/avoid sets, explicit residual scope, and anti-vacuity tests. Broad or dependency-premature missions should be routed deliberately to analysis/research rather than forced to emit pseudo-implementation packages. + +The detailed decisions, owners, acceptance criteria, and falsification tests are in `DECISIONS.md`. The proposed additive replacement for the existing external-agent result schema is `RESULT-TELEMETRY-V2.schema.json`. + +## Limitations and missing evidence + +This report did not access a live browser, provider account, daemon, deployed archive, or current `ops.db`. The project-state archive does not include the raw canonical session exports for the 27/28-session campaign set, provider action receipts, per-turn terminal markers, historical output attachment blobs, or operator interaction logs. It therefore cannot independently count terminal turns, time-to-first/terminal output, asset-offer events, acquisition retries, exact manual effort, or live replay success. + +The snapshot is marked dirty, but its branch-delta patch and changed-file list are empty, so the exact uncommitted state is unavailable. The Git bundle supplies tracked history and refs; it does not recover omitted live state. PR verification is based on the recorded PR/Bead evidence, not rerun tests. The 325-unit older corpus was used only as context for intake breadth, not re-adjudicated package by package. + +## Value of another iteration + +A second pass over the same archive would add little beyond editorial refinement. A materially higher-value iteration requires one or more of: the canonical 27-session roster and raw turn/attachment exports; BrowserAction submission receipts; live replay results after #2930; the campaign’s `ops.db`/capture-health events; and timestamped integration/worktree/verification receipts. With those inputs, the funnel can be made genuinely time-based, output-capture recall can be measured, same-chat repair yield can be separated from fresh-chat yield, and prompt/mission associations can be tested without relying on custody filenames or prose reconstruction. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/RESULT-TELEMETRY-V2.schema.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/RESULT-TELEMETRY-V2.schema.json new file mode 100644 index 000000000..b3f3fa486 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-02/a01/extracted/RESULT-TELEMETRY-V2.schema.json @@ -0,0 +1,781 @@ +{ + "$defs": { + "evidence_event": { + "additionalProperties": false, + "properties": { + "actor": { + "type": [ + "string", + "null" + ] + }, + "details": { + "type": "object" + }, + "event_id": { + "minLength": 1, + "type": "string" + }, + "event_type": { + "enum": [ + "dispatch_prepared", + "submission_requested", + "submission_receipt", + "conversation_bound", + "first_turn_observed", + "terminal_turn_observed", + "artifact_offered", + "artifact_acquisition_started", + "artifact_acquired", + "artifact_hash_validated", + "package_extracted", + "package_contract_validated", + "triage_decided", + "integration_worktree_created", + "patch_apply_checked", + "local_repair_started", + "local_repair_completed", + "verification_completed", + "pull_request_opened", + "merged", + "bead_state_observed", + "attempt_superseded", + "blocked", + "rejected" + ] + }, + "evidence_ref": { + "minLength": 1, + "type": "string" + }, + "occurred_at": { + "format": "date-time", + "type": "string" + }, + "outcome": { + "enum": [ + "success", + "failure", + "partial", + "unknown", + "not_applicable" + ] + }, + "reason_code": { + "type": [ + "string", + "null" + ] + }, + "recorded_at": { + "format": "date-time", + "type": "string" + }, + "source_kind": { + "enum": [ + "provider_receipt", + "canonical_capture", + "manual_download", + "filesystem", + "git", + "github", + "beads", + "local_verification", + "operator_observation", + "historical_import" + ] + } + }, + "required": [ + "event_id", + "event_type", + "occurred_at", + "recorded_at", + "source_kind", + "evidence_ref", + "outcome" + ], + "type": "object" + }, + "verification_run": { + "additionalProperties": false, + "properties": { + "command": { + "type": "string" + }, + "completed_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "evidence_ref": { + "type": "string" + }, + "failed": { + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "limitations": { + "type": [ + "string", + "null" + ] + }, + "outcome": { + "enum": [ + "passed", + "failed", + "stopped", + "not_run" + ] + }, + "passed": { + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "run_id": { + "type": "string" + }, + "scope": { + "enum": [ + "package_self_report", + "focused_local", + "affected_local", + "quick_gate", + "broad_local", + "live", + "deployment" + ] + }, + "skipped": { + "minimum": 0, + "type": [ + "integer", + "null" + ] + }, + "started_at": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "run_id", + "command", + "started_at", + "outcome", + "evidence_ref" + ], + "type": "object" + } + }, + "$id": "polylogue.external-agent-result.v2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Proposed replacement for .agent/handoffs/external-agent-campaigns/schemas/result.schema.json. This remains repository-side orchestration evidence, not a browser/product protocol.", + "properties": { + "action_id": { + "type": [ + "string", + "null" + ] + }, + "agent_handle": { + "type": [ + "string", + "null" + ] + }, + "artifacts": { + "items": { + "additionalProperties": false, + "properties": { + "custody_kind": { + "enum": [ + "canonical_capture", + "manual_download", + "provider_export", + "local_generated", + "external_reference" + ] + }, + "custody_path": { + "type": "string" + }, + "expected": { + "type": "boolean" + }, + "filename": { + "type": "string" + }, + "polylogue_attachment_ref": { + "type": [ + "string", + "null" + ] + }, + "provider_attachment_id": { + "type": [ + "string", + "null" + ] + }, + "sha256": { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + "size_bytes": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "filename", + "sha256", + "size_bytes", + "custody_path", + "custody_kind" + ], + "type": "object" + }, + "type": "array" + }, + "attempt_id": { + "pattern": "^a[0-9]{2}$", + "type": "string" + }, + "bead_outcomes": { + "items": { + "additionalProperties": false, + "properties": { + "bead_id": { + "type": "string" + }, + "evidence_ref": { + "type": "string" + }, + "observed_at": { + "format": "date-time", + "type": "string" + }, + "observed_status": { + "enum": [ + "open", + "in_progress", + "blocked", + "closed", + "unknown" + ] + }, + "residual_owner": { + "type": [ + "string", + "null" + ] + }, + "satisfied_scope": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "bead_id", + "observed_status", + "observed_at", + "evidence_ref" + ], + "type": "object" + }, + "type": "array" + }, + "beads": { + "items": { + "type": "string" + }, + "type": "array" + }, + "campaign_id": { + "minLength": 1, + "type": "string" + }, + "commits": { + "items": { + "type": "string" + }, + "type": "array" + }, + "events": { + "items": { + "$ref": "#/$defs/evidence_event" + }, + "minItems": 1, + "type": "array" + }, + "job_id": { + "minLength": 1, + "type": "string" + }, + "local_repair": { + "additionalProperties": false, + "properties": { + "completed_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + }, + "discarded_paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "notes": { + "type": "string" + }, + "reason_codes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "required": { + "type": "boolean" + }, + "retained_paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "rewritten_paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "started_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + } + }, + "type": [ + "object", + "null" + ] + }, + "mission": { + "additionalProperties": false, + "properties": { + "allowed_paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "anti_vacuity_mutation": { + "type": [ + "string", + "null" + ] + }, + "avoid_paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends_on": { + "items": { + "type": "string" + }, + "type": "array" + }, + "owning_beads": { + "items": { + "type": "string" + }, + "type": "array" + }, + "production_route": { + "type": [ + "string", + "null" + ] + }, + "residual_scope": { + "type": [ + "string", + "null" + ] + }, + "title": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "title", + "owning_beads", + "depends_on", + "allowed_paths", + "avoid_paths" + ], + "type": "object" + }, + "notes": { + "type": "string" + }, + "package_metrics": { + "additionalProperties": false, + "properties": { + "compressed_bytes": { + "minimum": 0, + "type": "integer" + }, + "copied_input_detected": { + "type": "boolean" + }, + "design_metadata_bytes": { + "minimum": 0, + "type": "integer" + }, + "fixture_snapshot_archive_bytes": { + "minimum": 0, + "type": "integer" + }, + "largest_members": { + "items": { + "additionalProperties": false, + "properties": { + "declared_purpose": { + "type": [ + "string", + "null" + ] + }, + "path": { + "type": "string" + }, + "size_bytes": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "path", + "size_bytes", + "declared_purpose" + ], + "type": "object" + }, + "type": "array" + }, + "member_count": { + "minimum": 0, + "type": "integer" + }, + "patch_bytes": { + "minimum": 0, + "type": "integer" + }, + "tests_verification_bytes": { + "minimum": 0, + "type": "integer" + }, + "uncompressed_bytes": { + "minimum": 0, + "type": "integer" + } + }, + "type": [ + "object", + "null" + ] + }, + "package_revision": { + "pattern": "^r[0-9]{2}$", + "type": [ + "string", + "null" + ] + }, + "polylogue_session_ref": { + "type": [ + "string", + "null" + ] + }, + "prompt_contract_sha256": { + "pattern": "^[0-9a-f]{64}$", + "type": [ + "string", + "null" + ] + }, + "prompt_profile": { + "type": [ + "string", + "null" + ] + }, + "prompt_sha256": { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + "prompt_shape": { + "enum": [ + "implementation", + "repair", + "analysis", + "research", + "review" + ] + }, + "provider": { + "type": [ + "string", + "null" + ] + }, + "provider_conversation_id": { + "type": [ + "string", + "null" + ] + }, + "provider_turn_id": { + "type": [ + "string", + "null" + ] + }, + "pull_request": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": [ + "string", + "null" + ] + }, + "schema_version": { + "const": 2 + }, + "snapshot": { + "additionalProperties": false, + "properties": { + "archive_sha256": { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + "branch": { + "type": [ + "string", + "null" + ] + }, + "commit": { + "pattern": "^[0-9a-f]{7,40}$", + "type": "string" + }, + "dirty": { + "type": "boolean" + }, + "generated_at": { + "format": "date-time", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "commit", + "dirty", + "archive_sha256" + ], + "type": "object" + }, + "snapshot_identity": { + "type": [ + "string", + "null" + ] + }, + "state": { + "enum": [ + "prepared", + "submitted", + "running", + "terminal", + "acquired", + "validated", + "triaged", + "integrating", + "verified", + "published", + "rejected", + "blocked" + ] + }, + "supersedes_attempt_id": { + "pattern": "^a[0-9]{2}$", + "type": [ + "string", + "null" + ] + }, + "supersedes_package_revision": { + "pattern": "^r[0-9]{2}$", + "type": [ + "string", + "null" + ] + }, + "terminal_turn_ref": { + "type": [ + "string", + "null" + ] + }, + "triage": { + "type": [ + "string", + "null" + ] + }, + "triage_decision": { + "additionalProperties": false, + "properties": { + "current_source_reconciled": { + "type": "boolean" + }, + "disposition": { + "enum": [ + "implementation", + "repair", + "analysis_or_research", + "duplicate", + "subsumed", + "rejected", + "blocked" + ] + }, + "next_prerequisite": { + "type": [ + "string", + "null" + ] + }, + "owner_beads": { + "items": { + "type": "string" + }, + "type": "array" + }, + "patch_apply_check": { + "enum": [ + "pass", + "fail", + "not_applicable", + "not_run" + ] + }, + "reason_codes": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": [ + "object", + "null" + ] + }, + "uncertainties": { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "needed_evidence": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "code", + "detail" + ], + "type": "object" + }, + "type": "array" + }, + "verification_receipts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "verification_runs": { + "items": { + "$ref": "#/$defs/verification_run" + }, + "type": "array" + }, + "workload_id": { + "minLength": 1, + "type": "string" + }, + "worktree": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "schema_version", + "campaign_id", + "workload_id", + "job_id", + "attempt_id", + "state", + "prompt_sha256", + "prompt_shape", + "mission", + "snapshot_identity", + "snapshot", + "action_id", + "run_id", + "artifacts", + "events" + ], + "title": "External-agent immutable attempt result with evidence events", + "type": "object" +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/DECISIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/DECISIONS.md new file mode 100644 index 000000000..ad0750285 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/DECISIONS.md @@ -0,0 +1,138 @@ +# Decisions + +## Decision register + +### D01 — Use bundled remote master as the integration authority + +**Decision:** compare every ref to `0d081e5bc0b5e5bedcb3256d1779da8c0f091c65`, not checked-out `f654480c`. + +**Observed basis:** the bundle contains five newer merged commits, including corrections to complete raw-state counts, stream-safe replay, schema journal transaction bounds, and scale-proof metering. + +**Falsifier:** a newer attached authoritative snapshot that shows a later remote master or proves these refs were deliberately rebased elsewhere. + +### D02 — Merge none of the substantial visible refs wholesale + +**Decision:** every large visible ref is either already landed, superseded, archival, false-green, or structurally broken. + +**Observed basis:** exact patch/tree/ancestry results plus source/AC review. + +**Falsifier:** a ref-local commit or untracked worktree file absent from the archive that is not in current master and independently satisfies an open AC. Preserve tags/ref names before deletion so such evidence can be recovered. + +### D03 — Retire exact-landed and ancestor refs + +**Refs:** workload profiles, raw ledger, raw unification, raw proof runner, raw scale predecessor, schema regen, fable program, dead-protocol cleanup. + +**Action:** remove worktrees and branches only after checking for worktree-local files; do not make merge commits. + +**Reason:** retaining equivalent pre-squash histories creates false active work and encourages conflict resolution against code already integrated. + +### D04 — Retire the CaptureJob checkpoint as superseded + +**Decision:** keep #2953/current master; delete `feature/integration/capture-job-authority` after preserving any review comments. + +**Reason:** the stale branch's fallback scope model is weaker and its nine touched files all conflict with current receiver/extension routes. Its parent residuals belong to `.2`/`.3`, not to this branch. + +### D05 — Reopen `polylogue-pf1` + +**Decision:** change tracker status from closed to open and replace its close evidence. + +**Observed basis:** current test does not regenerate any source-derived write-path/SQL/table diff; companion test explicitly contains a placeholder. + +**Required closure:** one generator enumerates the defined lane universe, normalizes write operations, emits a stable committed artifact, and provides the test oracle. Adding/changing a write path must fail without updating classification through the generator. + +**Not accepted:** a count of ten, docstring numbering, filename/method substring assertions, or an independently hand-authored second model. + +### D06 — Quarantine `polylogue-48h`; do not repair the branch in place + +**Decision:** retain the ref only as forensic evidence; create a new branch from current master when scheduled. + +**Observed basis:** 22 compile failures, 226 unresolved private-helper calls, 17 merge conflicts, missing Bead artifacts/tests, and semantic changes. + +**Reason:** conflict resolution would obscure whether failures came from stale base, automated edits, or API design. A mechanical re-run without a semantic inventory would reproduce the defect. + +### D07 — Keep `polylogue-hjpx.2` open + +**Decision:** runner landing is a prerequisite, not closure. + +**Required closure:** successful admitted-host July-15-shape receipt, exact distribution/bytes, pass/resource/health data, interruption invariants, two matching quiescent digests, and fairness/conservation mutation failures. No live archive apply. + +### D08 — Keep `polylogue-lkrc` open while accepting the merged unification code + +**Decision:** retire the unification branch, retain the P0 Bead. + +**Reason:** source unification and state-count truth landed; stopped-daemon live postflight and separate `yla8` authorization boundary remain absent. + +### D09 — Refresh Testsuite Diet's baseline, but preserve its non-duplication rule + +**Decision:** change “assumed realized” to a versioned prerequisite matrix tied to current merged symbols/receipts. Block clusters that require C-03, full correlated variants, or shared full receipts until those are landed. + +**Not accepted:** Diet-owned workload identities, profile schemas, tier taxonomies, query/storage shadow oracles, or source-spelling gates. + +### D10 — Transpose, do not cherry-pick, old `lkrc-v35` compatibility intent + +**Decision:** add a current unified-reconciler source-schema-v7 fixture if current Bead ownership still requires the case. + +**Reason:** the old branch's repair commands and incident-specific receipt lifecycle were intentionally removed by #2962. Cherry-picking would reintroduce parallel authority. + +### D11 — Treat ignored extension lockfile as disposable state + +**Decision:** do not include `browser-extension/package-lock.json` in a repair or merge unless repository policy changes explicitly. + +**Reason:** `.gitignore` excludes it and no history indicates it is a committed authority surface. + +### D12 — Keep recovery refs archival + +**Decision:** do not merge `bundle/ap7` or `recovered/demo-packet-v2`. + +**Reason:** later current-source PRs selectively assimilated valid pieces and Beads explicitly rejected stale/self-referential proof shapes. + +### D13 — Refresh automation PRs only after product-state normalization + +**Decision:** #2701 and #2858–#2863 are last in order. + +**Reason:** release/changelog and dependency lock/type/snapshot outcomes should be generated from the settled product baseline. A clean textual merge is not enough for tree-sitter or mypy major/minor changes. + +## Ordered integration plan + +| Order | Change | Entry gate | Exit gate | Unsafe alternative rejected | +| ---: | --- | --- | --- | --- | +| 0 | fast-forward/normalize local master to `0d081e5b`; tag stale refs | bundle integrity; no worktree-local value hidden | one current baseline, clean worktree inventory | evaluating against `f654480c` | +| 1 | retire exact-landed/superseded refs and ancestor worktrees | patch/tree/ancestry evidence recorded | no duplicate active branches | merging equivalent histories | +| 2 | tracker correction for `pf1` and stale assumptions | source contradiction attached | Bead statuses match evidence | leaving false closure as downstream authority | +| 3 | fresh `pf1` generated inventory | current master; exact lane scope approved | generated artifact + mutation-sensitive test | merging either old twins test branch | +| 4 | schema `.1` replay/memory closure | #2968 current master | full receipt/query plan/cleanup | claiming small 1x/10x proof is production closure | +| 5 | schema `.2` privacy regeneration and `.3` family semantics | `.1` stable replay substrate | reviewed current packages and defensible catalog roles | promoting content-shaped keys or rare-latest default | +| 6 | parent workload canaries/receipts/promotion | children stable | C-03/related route mutations, shared receipts, no parallel scenarios | letting Diet fill missing upstream contracts | +| 7 | full `hjpx.2` proof | admitted host; merged #2970 runner | durable full-shape receipt plus mutations | using tiny unit run as scale proof | +| 8 | `lkrc` stopped-daemon/`yla8` boundary | full fixed-point proof, backup, quiescence, authorization | zero unreported gaps and retained postflight | live mutation before authorization | +| 9 | CaptureJob `.2`, then `.3` | #2953 core stable | events/timeline first, retention/quota/migration second | lifecycle policy on cache-local state | +| 10 | fresh `48h` semantic-cohort refactor | P0/P1 storage/schema churn settled | compile/type/parity/layering/public-contract proof | resolving 17 conflicts in the broken branch | +| 11 | release/dependency automation | settled master | owning workflow/type/snapshot gates | merging stale release/lock outputs earlier | + +## Merge-order hazards + +1. Merging schema/raw pre-squash branches before current master would overwrite later transaction, count, stream, and metering corrections. +2. Merging old capture code would reintroduce weaker scope/adoption semantics into a completed receiver-authoritative route. +3. Merging `48h` before current raw/schema proof completion would touch the same readiness, repair, archive, FTS, and source surfaces while lacking behavior parity. +4. Closing or consuming `pf1` before a real source-derived oracle makes the planned storage-twin collapse depend on a circular proof. +5. Running Diet implementation against assumed C-03/tiers/receipts before they exist invites precisely the parallel product model the planning documents prohibit. +6. Running live `lkrc` repair before `hjpx.2` and `yla8` gates confuses synthetic fixed-point evidence with live authorization. + +## Residual disputes + +- Current Bead/PR records say `pf1` is independently verified. This report disputes closure based on direct source. A new test run cannot resolve the dispute unless the test's oracle is replaced. +- `hjpx.1` is closed, yet #2965 corrected a status-truth gap after #2961. This report accepts current-master satisfaction while rejecting the idea that the stale branch/initial close record alone was complete. +- Testsuite Diet labels its future substrate a “realized baseline” while explicitly saying those changes are still landing. This report treats it as a planned consumer contract, not current source truth. +- The old `48h` design permits a grep lint; the newer adjudicated test policy forbids source-spelling gates. Current policy and behavior-focused acceptance criteria control. + +## Conditions for another adversarial pass + +A second pass has high value after at least one of these artifacts exists: + +- a fresh `pf1` generator/artifact/test; +- a full `hjpx.2` result receipt and mutation results; +- the schema `.1` production-scale replay receipt/query plan; +- a current source-v7 unified-reconciler fixture; +- a fresh staged `48h` semantic-cohort branch. + +At that point, the next review should actively seek omitted dynamic SQL, decorator/generated methods, views/attached schemas, retry/starvation edge cases, receipt self-certification, and current call sites outside the declared ownership map. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..4229ad764 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/EVIDENCE.md @@ -0,0 +1,217 @@ +# Evidence and per-acceptance-criterion matrix + +## Evidence labels + +Every conclusion below uses one of four labels: + +- **Observed:** directly present in source, Git objects, current Beads/PR snapshot, or output of a command run during this review. +- **Source-supported inference:** the source and history support the conclusion, but the complete runtime behavior was not executed by this review. +- **Unresolved:** evidence required by the acceptance criterion is absent, contradictory, or outside the snapshot. +- **Recommendation:** an integration or repair action, not a claim about current behavior. + +A passing test record is not promoted to “Observed runtime behavior” unless this audit ran it. Bead/PR-reported commands remain provenance-tagged evidence. + +## Commands and checks actually performed + +| Check | Result | Use | +| --- | --- | --- | +| Bundle/ref enumeration and ancestry | completed | identify local heads, remote heads, pseudo-worktree heads, recovery refs, ancestors, and current bundled master | +| `git rev-list --left-right --count`, merge-base diff stats | completed | stale-base and size inventory | +| Stable patch-id comparison | completed | exact equivalence of three feature branches to merged squash commits | +| Git tree comparison | completed | raw-proof runner tree equals current master | +| `git merge-tree --write-tree --messages` | completed | conflict messages/path classes without modifying a checkout | +| Working-tree file hashing | completed | distinguish real untracked state from omitted archive content | +| Static source/Bead/PR inspection | completed | production route and test trace; AC adjudication | +| `_table_exists`/`_column_exists`/`_index_exists` call/definition census on `48h` | completed | prove removed-private-helper calls remain unresolved | +| `python -m compileall -q polylogue` on isolated `48h` checkout | **failed: 22 files** | falsify branch verification claim and merge readiness | +| pytest / JavaScript tests / mypy / `devtools verify` | **not run** | no independent green claim | +| live daemon/browser/archive/deployment/full-scale proof | **not accessed** | no live claim | + +## Baseline and repository state + +**Observed.** `polylogue-overview.md` identifies checked-out `master @ f654480c`, dirty state, and snapshot generation at 2026-07-17T04:32:02Z. The bundled all-ref repository has `refs/remotes/origin/master @ 0d081e5b`, five commits ahead. The remote tip contains the latest source in the attached authority and is used throughout. + +**Observed.** Repository instructions require Bead AC honesty, fresh branch/squash integration, targeted verification, topology regeneration for new modules, and rejection of old/parallel devloop mechanisms. Those rules exclude “merge then discover intent” and source-spelling gates as substitutes for behavior. + +**Observed.** The extracted working-tree source matched local `f654480c` except ignored `browser-extension/package-lock.json`. Tracked `.agent`, `.beads`, and lockfile omissions in the tar explain the false mass-deletion status produced by naively overlaying it onto a clone. + +## Patch, tree, and ancestry proof + +| Ref | Proof | Adjudication | +| --- | --- | --- | +| `feature/feat/schema-workload-profiles` | stable patch id `81739ae638f54f6902834da110321ca44aef0167`, equal to squash `c20286459` | all branch code landed; residual Bead ACs are new work | +| `feature/storage/raw-authority-ledger` | stable patch id `31aeb2456c68023932eac0385d3ff49027585f32`, equal to squash `593ef3c62` | branch fully landed | +| `feature/storage/raw-authority-unification` | stable patch id `daa985a58b834d6cceb82aeb83842d7892f3bb0a`, equal to squash `d17ded51b` | branch fully landed | +| `origin/feature/test/raw-authority-proof-runner` | branch tree id equals `origin/master` tree id | no unmerged tree value | +| `polylogue-schema-regen` | head `067c87e49` is in current master | no active delta | +| `polylogue-fable-program` | head `fd7b35492` is in current master | no active delta | +| `feature/chore/prune-dead-protocols` | ancestor of current master | no active delta | +| `feature/integration/capture-job-authority` | no patch equality to #2953; older 9-file checkpoint versus merged 16-file implementation | superseded, not additive | + +## Conflict simulation + +| Ref | Conflict messages | Important conflict classes/paths | +| --- | ---: | --- | +| workload profiles | 7 | add/add in `polylogue/scenarios/workload.py`, `observation_journal.py`, and receipt/journal tests; content in generated topology and `cluster_collection.py` | +| capture checkpoint | 9 | add/add in current CaptureJob production/tests; content in background, contracts, server, topology | +| raw ledger | 10 | add/add in `raw_authority.py`; content across maintenance, backfill, readiness, repair, and tests | +| raw unification | 3 | add/add in `raw_reconciler.py`; readiness/test content | +| old `lkrc-v35` | 9 | modify/delete old monolithic maintenance CLI; add/add current repair tests; content in live source, backfill, repair, archive store | +| `pf1` companion | 0 | merge-clean but semantically inadequate | +| `48h` | 17 | generated topology plus readiness, live source, blob, embedding, FTS, raw retention, search, source sessions, and archive write paths | +| semantic recovery packet | 3 | current archive session repository, facade contracts, semantic-card test | +| demo recovery packet | 9 | current demo tooling, registry, docs, tour, fixtures, and tests | + +Conflict-free does not mean acceptable: `pf1` is the strongest counterexample in this set. + +# Per-AC matrices + +## `polylogue-1xc.14.1` — derive archive-scale workload profiles + +The visible branch is exactly landed as #2934, but the owning Bead remains in progress. The matrix evaluates current master, not whether the old branch should merge. + +| AC | Required outcome | Current source/routes and tests | Status | Falsification / missing proof | +| ---: | --- | --- | --- | --- | +| 1 | deterministic, versioned, privacy-classified `WorkloadProfile`; provenance; staging does not mutate committed packages | `polylogue/schemas/generation/workload_profiles.py`, `archive_workload_profile.py`, provider package/generation modules, `promotion_audit.py`; tests in `test_schema_workload_profiles.py`, `test_schema_promotion_audit.py`, schema generation/laws | **Partially satisfied; source-supported.** Core artifacts/provenance/staging landed. Live reviewed promotion remains open. | Demonstrate staged live generation leaves committed packages byte-identical until explicit promotion; then show reviewed artifact replacement. | +| 2 | bounded streaming rates/distributions/joints/relationships/lineage/growing/convergence/mix/sizes/selectivity; memory independent of corpus size | bounded sketch modules and `ObservationJournal`; #2968 bounds transactions/selective replay; memory probe and journal tests exist | **Partial.** Data structures and small scaling machinery exist; production-scale replay economics and residual retention sites are unresolved in child `.1`. | A committed representative `EXPLAIN QUERY PLAN`, 1x/10x RSS receipt, journal bytes, and cleanup across success/cancel/abrupt death; source audit must find no corpus-proportional list/set. | +| 3 | joint profile consumption; deterministic provider-native wire; real acquire→parse→materialize→index→query; parser/pushdown mutation fails | `polylogue/schemas/synthetic/*`, provider wire builders, scenario/workload modules, schema and source parser tests | **Partial.** Deterministic wire generation and some real ingest/convergence wiring landed; correlated joint variant selection and query/pushdown sensitivity are not closed. | Remove a production parser and selective query pushdown in reversible mutations; the same profile canary must fail for the intended reason. | +| 4 | named scale tiers preserving tails/selectivity; C-03 mixed archive + exact-session action query; related canaries | `polylogue/schemas/workload_tiers.py` and scenario structures exist | **Not demonstrated as the complete AC.** Presence of a tier module is not a C-03 production-route receipt. Bead notes keep named tiers/canaries open. | Retain a machine-readable named-tier definition and show C-03 ranking/selectivity failure when either ranking leg loses its bound; add tool/lineage/growing/partial-convergence canaries from the same profile. | +| 5 | shared `polylogue-1xc.14` receipts with identities, timings, resources, cancel/progress/cleanup; no parallel envelope | `polylogue/scenarios/workload.py`, `tests/unit/scenarios/test_workload_receipts.py`, infra workload artifacts | **Partial.** Shared receipt types/routes exist, but all performance lanes have not been shown to consume them. | Inventory every active perf/scale route; fail any route that invents a separate corpus identity/resource envelope. Retain cancellation/cleanup receipts from real routes. | +| 6 | promotion review for structural/distribution/privacy changes; automatic hard rejects and operator-review inventory | `polylogue/schemas/promotion_audit.py` and tests landed | **Partial.** Audit mechanism exists. Child `.2` proves current committed provider artifacts still require live regeneration/review for content-shaped property keys. | Stage live Claude Code artifacts; hard-fail seeded secrets/content-shaped keys; separately enumerate review-only values; promote only reviewed replacements. | +| 7 | supersede vague perf family; focused inference/generator/route/privacy/determinism/memory/receipt tests and quick gate | broad focused tests added by #2934; PR/Bead reports targeted and quick results | **Partial; not rerun.** Existing tests do not prove all residual ACs or full-scale closure. | Current master must remove/redirect parallel perf scenarios, run the complete targeted set, and attach exact quick-gate receipt after `.1`–`.3` completion. | + +**Branch decision:** retire `d0cf780128`; do not equate exact landing of its patch with closure of the seven-AC Bead. + +### Children that control residual closure + +| Bead | Open residual | Current evidence | Required next proof | +| --- | --- | --- | --- | +| `polylogue-1xc.14.1.1` | full-corpus replay/memory, safe journal lifecycle, exact mergeable accumulators | #2968 commits bounded transactions and selective replay; Bead notes describe a 41.7 GiB WAL/live replay incident and repair | full production-scale receipt, selective query plan, cancellation/abrupt cleanup, no proportional retention | +| `polylogue-1xc.14.1.2` | prevent observed content becoming property names; scan/promote reviewed current packages | classifier/audit design and tests are in progress | live Claude Code staging, blocker/review inventory, reviewed replacement of committed schemas | +| `polylogue-1xc.14.1.3` | separate latest/recommended/default/evidence family semantics | Bead records 55 live families and rare-latest default failure | retain all families, deterministic coverage-first recommendation rationale, live regenerated catalog and resolution mutations | + +## `polylogue-06zm.1` — receiver-authoritative CaptureJob identity + +The stale checkpoint branch is not the implementation under review for closure; current master #2953 is. + +| AC | Required outcome | Current production routes/tests | Status | Residual / falsification | +| ---: | --- | --- | --- | --- | +| 1 | receiver create/get/list/adopt/update with stable id/scope/intent/revision/checkpoint/lease/retry/hold/receipts/client policy | `polylogue/browser_capture/capture_jobs.py`, `route_contracts.py`, `server.py`; extension `backfill/capture_jobs.js`, coordinator/storage/background; Python and JS CaptureJob tests | **Satisfied in current source; PR-reported verification, not rerun.** | Remove CAS/revision enforcement or receipt idempotence; production-route fixtures must fail. | +| 2 | whole-profile wipe and new extension id discovers/adopts only exact compatible scope, with no credential/cross-account leak/replay | receiver scope model and extension recovery/adoption routes in #2953 | **Satisfied in current source; not rerun.** The old branch's `paired:` fallback is weaker and must not return. | Use two accounts under one provider and a wiped profile; only exact account scope may be discoverable/adoptable. | +| 3 | concurrent adoption, expired leases, incompatible clients, duplicate reconnects, checkpoint conflict are visible/idempotent; removing CAS/lease breaks fixture | receiver CAS/lease operations and Python/JS tests | **Satisfied in current source; not rerun.** | Reversible mutation of lease expiry/CAS should produce duplicate ownership or acknowledged-page replay and fail. | +| 4 | IndexedDB/chrome.storage are caches and rehydrate from receiver | extension storage/coordinator/background routes and tests | **Satisfied in current source; not rerun.** | Delete both caches between acknowledged pages and recover solely from receiver state. | +| 5 | old mirrored checkpoints migrate or surface as typed orphans; focused/quick gates | typed legacy orphan paths and tests merged in #2953; close reason reports receiver 7, daemon-auth 19, extension 313, lint/manifest, quick 16/16 | **Satisfied by source plus recorded verification; not independently reproduced.** | A malformed or cross-scope legacy checkpoint must become a typed orphan rather than be silently adopted. | + +**Branch decision:** retire `8ecc34ecce`. **Parent decision:** keep `polylogue-06zm` open; event/timeline projection (`.2`) and lifecycle quota/retention/migration (`.3`) are separate residual work. + +## `polylogue-hjpx.1` — immutable raw replay-plan conservation + +The branch patch is exactly landed as #2961. The matrix evaluates current master, including later #2965/#2966 corrections. + +| AC | Required outcome | Current source/routes/tests | Status | Adversarial note | +| ---: | --- | --- | --- | --- | +| 1 | moved-path singleton is fully censused before immutable multi-raw plan; preview/apply same id/inputs | `polylogue/storage/raw_authority.py`, `revision_backfill.py`, `repair.py`; raw ledger/backfill/repair tests | **Source-supported satisfied; not rerun.** | A pre-census plan or post-id widening must fail the moved-path fixture. | +| 2 | executable conservation algebra over every before plan id | durable authority ledger/migration and plan state accounting; `test_raw_authority_ledger.py` | **Source-supported satisfied; not rerun.** | Drop carried-forward/unselected inventory or duplicate terminalization; digest/algebra must fail. | +| 3 | logical keys, witnesses, raw ids, source/index preconditions, exact application/membership receipts | raw authority plan/receipt records and repair integration | **Source-supported satisfied.** | `parsed_at_ms`-only execution evidence must not satisfy postflight. | +| 4 | interrupted census resumes without duplicate/partial plans; incomplete census cannot mutate | ledger and restart paths/tests | **Source-supported satisfied.** | Kill/restart between census and plan publication; no partial component may apply. | +| 5 | rejected-stale writes durable blocker before event; automation halts until explicit repair | source-tier authority ledger, readiness/repair/daemon projection | **Source-supported satisfied.** | Suppress durable blocker while emitting event; readiness must fail closed. | +| 6 | two consecutive matching quiescent dry-run digests | fixed-point fields and current runner/ledger tests | **Source-supported satisfied at mechanism level.** | One empty pass or candidate count alone cannot mark fixed point. | +| 7 | bounded CLI/MCP/daemon inventory plus query handles | maintenance CLI, daemon CLI, MCP resources, readiness; #2965 adds complete frontier counts | **Satisfied only in current master after follow-up #2965.** | Omit `proven_current` or derive postflight blocking from preflight counts; contract tests must fail. | +| 8 | mutations to closure/slicing/carry-forward/application receipt/second census fail | focused raw-authority tests recorded in merged change | **Source-supported; not rerun.** | Keep exact mutation list in the targeted gate; a test that only reads constants is insufficient. | + +**Adversarial correction:** the #2961 close reason is not sufficient evidence for current AC7 by itself. #2965 exists because complete frontier state counts were missing. Current master repairs that gap; the stale branch does not. + +## `polylogue-lkrc` — one proof-driven raw authority reconciler + +The unification branch is exactly landed as #2962, but the P0 Bead remains open. + +| AC | Required outcome | Current source/routes/tests | Status | Residual / falsification | +| ---: | --- | --- | --- | --- | +| 1 | one typed census over origin mismatch, duplicate, quarantine, superseded, missing/replaced bytes, competing authority | `polylogue/storage/raw_reconciler.py`, `raw_authority.py`, `repair.py`; browser/duplicate/quarantine tests | **Source-supported satisfied for implemented state space.** | Add a known frontier case that falls through to an untyped/healthy state; census must fail. | +| 2 | one plan-authorize-apply-receipt-postflight lifecycle; no incident-specific parallel proof lifecycle | #2962 removes old incident routes and routes actuators through shared reconciler; CLI/readiness/daemon use shared contract | **Source-supported satisfied.** | Global consumer trace must find no separate browser-origin/duplicate repair digest or mutator. | +| 3 | equivalent/rekeyable/duplicate converge idempotently/restartably with CAS | shared planner/apply paths and focused tests | **Source-supported satisfied.** | Replay the same plan after restart and mutate a witness between plan/apply; stale write must be rejected. | +| 4 | conflicting content cannot auto-win; durable judgment blocker; assertion resumes same plan | current conflict disposition/judgment handling and browser origin repair tests | **Source-supported satisfied.** | Remove assertion or change competing head after assertion; plan must remain blocked/revalidate. | +| 5 | missing bytes create durable reacquisition obligation; promote only after origin/identity/hash proof; receiver artifact retained | shared reconciler typed state and repair routes | **Partially source-supported.** Mechanism exists; full current fixture/live evidence is not reproduced here. | Delete/replace bytes while retaining receiver source; promotion without exact hash/origin proof must fail and source must remain recoverable. | +| 6 | all known fixtures use one reconciler; stopped-daemon live postflight has zero unreported gaps | known fixture suites were merged/reported; current Bead explicitly says live stopped-daemon postflight remains | **Partial.** Fixture convergence is source-supported; live postflight is unresolved. | Run on reviewed stopped-daemon copy after proof/backup gates; retain complete pre/post census and zero unreported frontier states. | +| 7 | readiness/status expose complete state counts/remediation; sidecar/index alone cannot report healthy | `archive_readiness.py`, daemon status/CLI; #2965 adds `frontier_state_counts` including `proven_current` | **Satisfied in current master; not rerun.** | Omit a state or use preflight residuals for postflight blocking; contract must fail. | +| 8 | `OriginSpec` authority rules and `yla8` replay-order protections; mutations fail if bypassed | current authority/replay code and related tests preserve mechanisms; live authorized `yla8` gate is separate | **Partial.** Static integration is present; complete mutation/live gate evidence is absent from snapshot. | Bypass OriginSpec or replay ordering in a reversible mutation; then run separately authorized live gate only with verified backup/quiescence. | + +**Old branch decision:** do not merge `adda2b96ac`. Its useful residual is a compatibility question, not its incident-specific architecture. Build a current source-schema-v7 fixture through `raw_reconciler.py` and public repair/readiness surfaces. + +## `polylogue-hjpx.2` — July-15 raw replay convergence proof + +| AC | Required outcome | Current mechanism/evidence | Status | Exact missing evidence | +| ---: | --- | --- | --- | --- | +| 1 | sanitized corpus matches candidate/component/byte/skew distributions and is private-free | runner creates deterministic synthetic components/raws and content-addresses corpus; Bead notes record a 10,163/15,264 scheduler-shape execution | **Not satisfied at full requested shape.** | Receipt matching ~21,398 expanded candidates, 10,163 components, 4.788 GiB payload, skew/conflict cohorts, with documented tolerances/privacy scan. | +| 2 | each bounded pass within RSS/PSS/swap/temp/write/wall envelopes; daemon health responsive | #2970 records process resources and rejects contended hosts | **Partial mechanism only.** No successful full-shape receipt; runner does not itself prove live daemon responsiveness in the snapshot. | Admitted-host full run with declared envelopes, health probe latency, and retained containment receipt. | +| 3 | backlog monotonically decreases modulo retry injection; finite retries resolve once; no starvation | bounded pass loop drains candidates on small/default shapes | **Partial.** No full-skew fairness receipt and no explicit retry-injection proof in retained artifact. | Per-pass backlog/state ledger at full shape, deterministic retry injection, bounded fairness/wait evidence. | +| 4 | cursor/source/index/FTS/raw authority never regress across interruption/resume | real repair route and ledger censuses are used | **Partial.** Snapshot lacks interruption/resume invariant artifact at required scale. | Kill/restart points with before/after invariant digests and FTS/readiness state. | +| 5 | two final equal quiescent digests, zero executable plans, identical residual debt | runner enforces two quiescent census digests; unit test exercises 3 components/5 raws | **Satisfied only for small mechanism, not Bead scale.** | Same invariant in successful July-15-shaped receipt. | +| 6 | removing fair rotation causes starvation; removing conservation causes mismatch; harness fails both | no such mutation variants were found in runner/test source | **Not satisfied.** | Two reversible mutation executions with exact expected failures and retained results. | +| 7 | exact commands, containment receipts, seed, result artifacts durable/reviewable | command catalog/docs and JSON receipt path exist; content-addressed seed/corpus id exists | **Partial.** No successful full result package in snapshot. | Commit or otherwise durably reference command line, environment/admission, seed, corpus digest, per-pass data, resource receipt, result SHA. | +| 8 | no live archive apply; `yla8` owns separate gate | runner constructs a generated workdir/archive and Bead notes explicitly reject live apply | **Satisfied.** | Keep this boundary; any live apply would invalidate this Bead's safety contract. | + +The tests are not vacuous: they reach `ArchiveStore`, source raw writes, and `repair_raw_materialization`. They are insufficient because their scale and mutation coverage do not match the AC, not because they are mere mocks. + +## `polylogue-pf1` — generated storage-twin divergence inventory + +| AC clause | Required | Current master evidence | Status | Why the close claim fails | +| --- | --- | --- | --- | --- | +| classify every write-path method | AST + SQL-string extraction of method, SQL, tables written, lane classification | narrative `docs/plans/STORAGE_TWINS_DIVERGENCES.md`; hand-written `DOCUMENTED_DIVERGENCES` in test | **Contradicted.** | No extractor, source-derived inventory, SQL/table association, or proof that every write method is included. | +| zero unexplained divergences | every same-table/intent semantic difference fixed or rationale row | ten narrative rows derived from existing docstring | **Not demonstrated.** | The universe being classified is not generated, so “zero unexplained” is circular. | +| test regenerates diff and fails on new divergence | same generator produces committed artifact and test oracle; mutation/new route fails | test counts ten constants and checks strings/files; companion test says SQL analysis is a placeholder | **Contradicted / false-green.** | Adding a new write method or changing SQL can pass without touching any asserted string. | + +**Observed contradiction:** the Bead close reason says the test “regenerates the diff” and reports nine passes. Source shows no regeneration function. Passing tests do not cure the missing oracle. + +**Decision:** reopen `polylogue-pf1`; delete both stale test approaches after extracting only their rationale text as seed material. + +## `polylogue-48h` — consolidate SQLite introspection helpers + +| AC clause | Required | Branch evidence | Status | Falsification | +| --- | --- | --- | --- | --- | +| before/after ownership map | complete old helper definitions, call semantics, owner and final API mapping | no committed map; only broad mass edits/new module | **Missing.** | Static census finds 226 private-helper calls still present after definitions were removed. | +| public behavior preserved through parity tests | sync/async/schema/view/virtual/error semantics tested before and after | no new tests in 41-file branch | **Contradicted by source and compile failure.** | 22 files do not parse; old error/view behavior differs from replacement defaults. | +| old path deleted/redirected with compatibility notes | call sites use new API or aliases; notes where public/internal contract changes | old definitions removed, calls remain; no aliases/notes | **Contradicted.** | `_table_exists` 186 calls/0 defs; `_column_exists` 34/0; `_index_exists` 6/0. | +| no evidence semantics change without migration/release note | same error and object-existence meaning | replacement propagates errors and changes view inclusion; identifier/schema semantics differ | **Contradicted.** | `operations/archive_debt.py` previously returned `False` on `sqlite3.Error` and treated views as existing. | +| layering/import graph, parity, public-model suite | reviewable verification artifacts | generated topology changed, but no parity/public suite and code cannot parse | **Missing/invalid.** | A generated topology file cannot validate an unparsable tree. | + +### Compile failures observed on `48h` + +`python -m compileall -q polylogue` failed in: + +`polylogue/cli/commands/status.py`, `tutorial.py`, `daemon/convergence_stages.py`, `embedding_backlog.py`, `fts_automerge.py`, `metrics.py`, `hooks/__init__.py`, `insights/readiness.py`, `operations/archive_debt.py`, `sources/live/hook_paste_enrichment.py`, `storage/blob_integrity.py`, `blob_repair.py`, `embeddings/materialization.py`, `embeddings/preflight.py`, `embeddings/status_payload.py`, `fts/fts_lifecycle.py`, `fts/session_repair.py`, `raw_retention.py`, `session_replacement.py`, `sqlite/archive_tiers/archive.py`, `sqlite/archive_tiers/write.py`, and `usage.py`. + +The branch commit's “mypy --strict” claim is unsupported by the branch tree: Python cannot parse these files. This does not prove which command the author attempted; it proves the recorded verification cannot apply to the visible final tree. + +## Recovery and archival refs + +### `refs/remotes/bundle/ap7` + +**Observed.** This is a recovery/assimilation ref, not an open worktree or PR. Current Bead `polylogue-ap7` is an open semantic-renderer invariant epic. Current master already contains selectively adapted core via #2700 and #2736. Bead/history notes reject the proof-only packet's self-corrupting snapshot as independent evidence and keep residual normalized-family/Origin coverage and bounded lineage parity open. + +**Decision.** Do not merge or use recovery packet tests to close current ACs. Continue from current registry/document routes and current `polylogue-ap7.1` ownership. + +### `refs/remotes/recovered/demo-packet-v2` + +**Observed.** This is also archival recovery history. Current-relevant schema/validator/receipt behavior was selectively ported via #2704 and #2709, while the stale flagship subsystem was rejected. + +**Decision.** Do not merge. Any missing current demo invariant must be reproduced against current `devtools/demo_packet.py`, CLI/tour routes, and current fixtures rather than resurrecting the old packet tree. + +## Missing generated surfaces and invented parallel mechanisms + +| Area | Finding | Decision | +| --- | --- | --- | +| topology docs | stale branches contain generated topology conflicts, but generated files do not confer correctness | regenerate once on the final fresh implementation after code/tests parse and targeted checks pass | +| `pf1` diff artifact | committed narrative is not generated from current source | one source-derived function must emit both test comparison and stable artifact; never maintain two hand-authored lists | +| Testsuite Diet workload baseline | assumes not-yet-realized C-03/tiers/correlated generation/receipts | block consumers or refresh to available subset; do not add a Diet-specific profile/receipt/canary model | +| raw scale proof | runner exists; successful full result surface absent | retain exact full-shape JSON receipt and command/environment metadata | +| `48h` grep lint | Bead design suggests a source-spelling tripwire; Diet adjudication rejects it | use semantic parity, import ownership/layering, compile/type checks; do not require/forbid private helper spellings | +| old `lkrc` incident tools | old branch contains independent repair/receipt lifecycle | transpose only compatibility fixture into current unified reconciler; no parallel command or plan schema | + +## Evidence limitations + +- The snapshot is self-contained but time-bounded; no network verification was performed. +- PR bodies and Bead close reasons can report commands, but this audit did not rerun them. +- Static comparison cannot prove all runtime invariants, resource envelopes, browser behavior, or live archive safety. +- `git merge-tree` predicts textual merge conflicts, not semantic compatibility; merge-clean refs were still rejected where AC evidence failed. +- Recovery namespaces do not expose whether an external operator still informally calls them “active”; repository ancestry/worktree/PR evidence classifies them as archival here. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/NEXT-ACTIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/NEXT-ACTIONS.md new file mode 100644 index 000000000..cb2b7977a --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/NEXT-ACTIONS.md @@ -0,0 +1,191 @@ +# Next actions and repair prompts + +## 1. Normalize and retire stale repository state + +**Owner:** repository integrator +**Starting point:** bundled `origin/master @ 0d081e5b` + +1. Record `git show-ref`, worktree paths, and any untracked/ignored files for each stale worktree. +2. Tag or archive ref names locally if historical review trace is required. +3. Fast-forward local master; do not merge old feature heads. +4. Remove worktrees/branches whose patch/tree/ancestor proof is recorded in `EVIDENCE.md`. +5. Delete ignored `browser-extension/package-lock.json` unless a separate policy decision makes npm lock authority explicit. +6. Leave recovery namespaces read-only or prune them under an explicit archival policy; never present them as pending product branches. + +**Acceptance:** one clean current-master checkout; no substantial “active” ref that is patch-equivalent, tree-equivalent, ancestor-only, or explicitly superseded. + +**Falsification:** any worktree-local file not represented by its Git ref and not ignored/generated must halt deletion and be classified against an owning Bead. + +## 2. Correct `polylogue-pf1` and implement a real generated inventory + +**Tracker action:** reopen `polylogue-pf1`; replace the close reason with the source contradiction and link downstream `polylogue-hiu`/`polylogue-a7xr` dependencies. + +### Implementer prompt + +> Create a fresh branch from current master for `polylogue-pf1`. Do not copy either existing twins test as the oracle. Define the authoritative async and sync storage lane roots from current architecture. Build one deterministic inventory function that parses Python AST and recoverable SQL string expressions, associates each write-capable method with normalized operation intent, tables/schema objects written, pragmas/transaction/error policy, and source locations, and marks unsupported/dynamic constructs explicitly rather than dropping them. Compare lanes by stable semantic operation and table intent, not by method spelling alone. Classify every row as equivalent, lane-only with rationale, divergent-to-fix, or unresolved-parser coverage. Generate `docs/plans/STORAGE_TWINS_DIVERGENCES.md` (or a compact JSON/CSV plus readable rendering) from this same function. The test must regenerate in a temporary directory and compare semantic content to the committed artifact. Adding a new write method, changing same-table SQL semantics, or making SQL unparsable without an explicit unresolved row must fail. Do not create a second hand-authored storage model or require source spellings merely to exist. + +### Required owning paths + +- async lane: current `polylogue/storage/sqlite/async_sqlite*.py` and delegated write mixins actually used by it; +- sync lane: current `polylogue/storage/sqlite/archive_tiers/` write paths and shared query/write modules reached by it; +- generated artifact: `docs/plans/STORAGE_TWINS_DIVERGENCES.md` and, only if useful, one compact machine-readable inventory; +- tests: `tests/unit/storage/test_storage_twins.py` rewritten around the generator, plus known-answer parser tests in a dedicated test module if needed. + +### Closure gates + +- every in-scope write method appears exactly once or has an explicit parser-coverage blocker; +- every normalized write table/intent is classified; +- no unexplained divergence remains; +- artifact is byte/content deterministic under shuffled file discovery; +- mutation: add a new same-table write with incompatible conflict/update semantics; test fails; +- mutation: hide SQL behind an unsupported construct; test fails as unresolved rather than silently omitting it; +- mutation: alter transaction/error handling where the lanes claim equivalence; test fails; +- targeted storage-twin command and repository quick gate are run by the local implementer and exact receipts are attached. + +**Do not accept:** a fixed row count, docstring-number check, filename existence check, method-name grep, or test that only validates its own constant table. + +## 3. Finish `polylogue-1xc.14.1.1` production-scale replay/memory proof + +### Implementer prompt + +> Continue from current master with #2968 present. Enumerate every remaining full-corpus retention site in schema inference/replay and prove no list/set/map grows with samples, memberships, scopes, paths, tool IDs, or distinct values. For the live representative archive, capture `EXPLAIN QUERY PLAN` and phase receipts proving membership replay is selective and does not globally scan samples per package. Define safe journal checkpoint/transaction boundaries that bound WAL and replay while preserving deterministic indexed passes. Run 1x/10x generated scaling and the representative production-scale generation under the shared `polylogue-1xc.14` receipt: peak RSS/PSS, journal/WAL bytes, I/O, timings, cancellation/progress, and cleanup. Exercise success, exception, cancellation, and abrupt worker death; remove DB/WAL/SHM and recover stale runs. Do not claim closure from small scaling alone. + +### Closure gates + +- source audit has no corpus-proportional retention path; +- 1x/10x counts scale by ten while peak Python memory stays within declared fixed overhead/buffers; +- query plan and timings demonstrate selective membership replay; +- exact/additive counts and approximation/loss metadata survive merge/order changes; +- cleanup proves scratch-root restrictions and stale abrupt-death recovery; +- known-answer/shuffled input output is deterministic; +- targeted and quick-gate receipts are retained. + +## 4. Finish workload-profile privacy and catalog semantics + +### `polylogue-1xc.14.1.2` + +> Use one shared dynamic-key classifier across field-stat traversal, fingerprints, schema collapse, generation, and validation. Seed sentence/question, path/XML, control-character, overlength, secret/token, and review-only values. Stage a live Claude Code regeneration without mutating committed packages. The promotion scanner must hard-block secrets and content-shaped property keys, while separately inventorying enums, dates, domains, email/account-like values, paths, IDs, and rare strings with artifact/path/frequency. Replace current committed packages only after the review artifact is accepted. + +**Falsifier:** any previously observed content-shaped key remains in a committed property name, or any subsystem classifies the same key differently. + +### `polylogue-1xc.14.1.3` + +> Represent `latest`, `recommended`, `default`, evidence family, and promoted release as distinct semantics. Retain all positive-value families and exact/profile/scope resolution. Select recommended/default by deterministic support/compatibility rules, not observation recency. Regenerate the live 55-family corpus, emit coverage/novelty/time-window/default rationale, and prove a new rare family cannot become default merely by being newer. + +**Falsifier:** remove the dominant family, reorder input, or add a newer rare family; resolution must remain deterministic and every retained family reachable. + +## 5. Complete parent workload canaries and receipts + +**Owner:** `polylogue-1xc.14.1` after children stabilize. + +- Implement correlated joint variant generation rather than independent marginals. +- Make provider-native wire artifacts reach production acquire, parser, materializer, index, and query routes. +- Define named scale/selectivity tiers from the profile mechanism. +- Land C-03: mixed archive plus exact-session action query, with planted independent facts and an explicit selective work bound. +- Add tool pairing, lineage replay, active-growing, and partial-convergence canaries from the same profile/receipt identity. +- Inventory performance tests and remove/redirect any independent corpus identity or resource envelope. +- Apply parser and query-pushdown mutations; the intended canary must fail. + +**Testsuite Diet handoff:** publish a versioned availability manifest listing which workload identities, tiers, canaries, and receipt fields are actually merged. Diet workers consume this manifest and block on absent prerequisites. + +## 6. Execute `polylogue-hjpx.2` at the required shape + +### Implementer prompt + +> Use current `devtools/raw_authority_scale_proof.py` with #2970's host-admission and resource metering. Extend the generated corpus, if necessary, to reproduce the July-15 direct/expanded candidate counts, authority component distribution, 4.788 GiB retained payload, revision/path/bundle skew, broken seeds, cursor-ahead sources, incomparable heads, missing blobs, and blocked cohorts within documented tolerances. Keep all content synthetic/private-free and bind the receipt to corpus bytes. Run on an admitted contained host. Record each bounded pass's complete state counts, executable backlog, carried/deferred/terminal states, retry identity, fairness/wait age, cursor/source/index/FTS/raw-authority invariants, RSS/PSS/swap, I/O, temp/database growth, wall/CPU time, and daemon health latency. Interrupt/resume at defined boundaries. Require two final identical quiescent census digests. Add two reversible variants: remove fair rotation and remove conservation/carry-forward accounting; retain the expected starvation/census-mismatch failures. Do not touch a live archive. + +### Durable result surface + +Retain: + +- exact command and commit/tree id; +- admission/containment environment; +- corpus seed, byte digest, and shape tolerances; +- machine-readable per-pass ledger; +- shared workload receipt and resource envelopes; +- interruption/resume points; +- mutation commands/results; +- final result SHA-256 and artifact location. + +**Closure prohibition:** the default 16/24 run, the unit 3/5 run, or the scheduler-cardinality-only 10,163/15,264 run cannot close the Bead. + +## 7. Close current `polylogue-lkrc` residuals without reviving old mechanisms + +### Source-v7 compatibility fixture + +> Construct a genuine current source-schema-v7 archive/fixture through supported migration/setup code. Feed it to the current shared `raw_reconciler.py` census/plan/apply/postflight route. Verify legacy native-id/capture-mode shape is classified and converges or produces an explicit typed blocker. Do not restore old incident CLI commands, proof digests, or receipt types. Mutate legacy identity/bytes and assert fail-closed CAS/judgment/reacquisition behavior. + +### Live gate + +After `hjpx.2` succeeds and `yla8` prerequisites are met: + +1. verified backup and restore proof; +2. daemon stopped and quiescent census; +3. explicit operator authorization; +4. apply through the single reconciler only; +5. complete postflight state inventory including `proven_current` and remediation refs; +6. two matching quiescent digests and zero unreported frontier gaps; +7. retained rollback/receipt evidence. + +**Do not accept:** sidecar/index health alone, preflight counts reused as postflight, manual SQL, raw deletion, force replay, or a separate browser-origin repair lifecycle. + +## 8. Continue CaptureJob parent in order + +### `polylogue-06zm.2` + +Land the durable receiver event/timeline projection over the #2953 job identity and receipts. Events must be monotonic/idempotent, queryable by stable job/scope, and reconstructible without extension-local caches. + +### `polylogue-06zm.3` + +Only after `.2`, implement retention/quota/migration/terminal lifecycle policy over durable jobs/events. Prove profile wipe, lease expiry, old-client reconnect, orphan migration, quota pressure, and terminal cleanup without acknowledged-page replay or cross-account disclosure. + +**Do not reopen:** the stale `paired:` checkpoint model. + +## 9. Rebuild `polylogue-48h` from current master + +**Scheduling:** after current P0/P1 raw/schema changes; this is a P4 consolidation. + +### Implementer prompt + +> Start a fresh branch. First commit a before/after ownership and semantics map for every SQLite introspection helper and call site: sync/async connection type; main/attached schema; table/view/virtual-table/trigger/index meaning; quoting/identifier source; error policy; row-factory assumptions; transaction context; and whether absence versus query failure is distinguishable. Group call sites into semantic cohorts. Design a small shared module with validated schema/object identifier handling and explicit policy parameters or adapters; do not force unlike semantics into one default. Add known-answer behavior tests before migration for each cohort, including attached schemas, views, virtual tables, malformed/unavailable DB, and async cursor lifecycle. Migrate one cohort at a time; after each cohort run compile, strict typing for touched modules, parity tests, and public contract tests. Delete old helpers only when the call census reaches zero. Regenerate topology once at the end and retain an import/layering diff. Do not add a grep/source-spelling denylist. + +### Mandatory gates + +- ownership map covers all definitions and calls on current master; +- no interpolated schema/table identifier without validation/quoting policy; +- explicit error semantics preserve old behavior or carry a migration/release note; +- attached schema, view, virtual table, trigger/index, and async cases have parity fixtures; +- `rg` shows zero unresolved removed helper calls, but this census is review navigation rather than the test oracle; +- `python -m compileall -q polylogue` passes; +- targeted strict typing and tests pass; +- import/layering and public-model compatibility evidence is retained; +- generated topology is current. + +**Do not reuse:** the mass import edits or replacement API from `b4217917ce` without independently re-deriving semantics. + +## 10. Refresh automation PRs + +After product integration: + +- regenerate #2701 release metadata/changelog from current master; +- refresh #2858–#2863 instead of resolving stale lock/workflow output manually; +- for tree-sitter, run parser/provider/schema route tests, not just installation; +- for mypy 2.3, classify new diagnostics and preserve strictness rather than blanket-ignore; +- for syrupy, inspect intentional snapshot serialization diffs; +- for coverage, verify configured branch/source semantics and report generation; +- retain exact workflow receipts. + +## Local verification checklist for the next archive + +The next project-state export should include: + +- current remote and local heads plus all worktrees; +- clean/ignored/untracked classification; +- current Beads with corrected `pf1` status; +- fresh branch diffs and PR bodies/reviews; +- generated artifacts and their producing command/version; +- targeted command outputs/receipts, not only prose summaries; +- full `hjpx.2` result or explicit failed/blocked receipt; +- schema journal full-scale result/query plan; +- any `48h` ownership map and parity matrix; +- exact archive timestamp so later changes are not silently treated as present. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/REPORT.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/REPORT.md new file mode 100644 index 000000000..ed18a431e --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-03/a01/extracted/REPORT.md @@ -0,0 +1,167 @@ +# Adversarial acceptance-criteria review of active large diffs + +**Job:** `analysis-03` +**Snapshot generated:** 2026-07-17T04:32:02Z +**Review baseline:** bundled `refs/remotes/origin/master` at `0d081e5bc0b5e5bedcb3256d1779da8c0f091c65` +**Checked-out snapshot head:** local `master` at `f654480cadb7cc4c194704e24dfd483199547b35` + +## Executive judgment + +No currently visible substantial branch, worktree head, recovery ref, or open PR should be merged wholesale. + +The apparent active-diff set is mostly repository hygiene debt rather than unintegrated product value: + +- Three large feature branches are patch-equivalent to squash commits already in current master: workload profiles to `c20286459` (#2934), raw-authority plan conservation to `593ef3c62` (#2961), and raw-authority unification to `d17ded51b` (#2962). +- The raw-authority proof-runner branch has the exact same tree as current master. Its predecessor scale-proof branch is superseded by the merged runner and later metering/host-admission work. +- The capture-job checkpoint branch is stale and strictly superseded by the larger receiver-authoritative implementation merged as `e6698a74e` (#2953). +- Two worktree heads are direct ancestors of current master and carry no active delta: schema journal regeneration at `067c87e49` (#2968) and fable/query execution control at `fd7b35492` (#2964). +- The only clearly unmerged broad production ref, `origin/worktree-wf_54d4fb2e-841-7` for `polylogue-48h`, is not salvageable as a merge candidate: a compilation check found 22 parse/indentation failures, 226 remaining calls to removed private helpers, no replacement aliases, no parity tests, and behavior changes that violate the owning Bead's no-semantics-change requirement. +- The unmerged `polylogue-pf1` companion test branch is merge-clean but does not repair the merged false-green contract. The closed Bead requires a generated AST/SQL inventory of every write path and a test that regenerates it. Current master instead validates a hand-written ten-row list and source/documentation strings. The companion test explicitly calls its SQL-semantic check a placeholder. `polylogue-pf1` should be reopened. +- `polylogue-hjpx.2` remains open correctly. Current master contains a real production-route scale-proof runner, but defaults are 16 components/24 raws, tests use 3/5, and the source says only an explicit July-15-sized invocation is production-shaped evidence. The snapshot contains no successful full-shape receipt. +- `polylogue-lkrc` also remains open correctly. The shared reconciler and status-truth corrections are in current master, but the stopped-daemon live postflight and the separately authorized `yla8` gate are not evidenced here. +- Testsuite Diet planning treats the complete `polylogue-1xc.14.1` outcome as a realized prerequisite even though that Bead and children `.1`, `.2`, and `.3` remain in progress. Those consumers must refresh against merged source and block on absent correlated generation, named C-03 canaries, full-scale receipts, privacy regeneration, and family/default semantics rather than invent parallel substitutes. + +The integration strategy is therefore: normalize to current master; retire landed/superseded refs; reopen and freshly implement the false-green `pf1` contract; complete current P0/P1 proof programs from fresh branches; defer the P4 `48h` refactor until the hot storage/schema programs stabilize; refresh routine automation PRs last. + +## Authority and scope + +The review used this order of authority: + +1. source at bundled current remote master; +2. repository instructions in `CLAUDE.md`; +3. current Beads export and Bead history/notes; +4. merged/open PR records and all-ref Git history; +5. older proposals, recovery packets, and Testsuite Diet planning only where consistent with 1–4. + +The bundle's checked-out local master is five commits behind the bundled remote master. Those five commits are material follow-ups: + +| Commit | Purpose | +| --- | --- | +| `0dc5773a9` | preserve complete raw frontier counts (#2965) | +| `805d49286` | stream safe raw-authority replay (#2966) | +| `9ddfc8d81` | add raw-authority scale-proof runner (#2967) | +| `067c87e49` | bound schema journal replay transactions (#2968) | +| `0d081e5bc` | guard and meter raw-authority proofs (#2970) | + +Using local `f654480c` as the merge baseline would erase the exact follow-up evidence needed to adjudicate the active raw/schema branches. All branch comparisons in this package therefore use `0d081e5b`. + +## Active-ref disposition + +`Behind/ahead` and diff sizes are measured against `0d081e5b` through each ref's merge base. Conflict counts come from `git merge-tree --write-tree --messages` and count conflict messages, not textual conflict-marker occurrences. + +| Ref / worktree | Owning area / Bead | Behind / ahead | Delta | Conflict messages | Adversarial disposition | +| --- | --- | ---: | ---: | ---: | --- | +| `feature/feat/schema-workload-profiles` (`d0cf780128`) and `polylogue-test-harness` | `polylogue-1xc.14.1` | 12 / 31 | 88 files, +10,543/-947 | 7 | **Retire.** Cumulative patch equals squash `c20286459` (#2934). The Bead remains open because #2934 was only a first slice; continue residual ACs from current master, not this ref. | +| `feature/integration/capture-job-authority` (`8ecc34ecce`) | `polylogue-06zm.1` | 53 / 2 | 9 files, +875/-44 | 9 | **Retire as superseded checkpoint.** #2953 is a larger, receiver-authoritative implementation covering exact-account profile-loss recovery and cache rehydration. Do not merge the older fallback model. | +| `feature/storage/raw-authority-ledger` (`34ed698553`) | `polylogue-hjpx.1` | 16 / 13 | 21 files, +4,281/-477 | 10 | **Retire.** Cumulative patch equals squash `593ef3c62` (#2961); current master also includes correctness/scale follow-ups #2965–#2970. | +| `feature/storage/raw-authority-unification` (`a294128820`) | `polylogue-lkrc` | 10 / 12 | 20 files, +2,634/-5,050 | 3 | **Retire.** Cumulative patch equals squash `d17ded51b` (#2962). The parent Bead remains open for live proof, not branch integration. | +| `origin/feature/test/raw-authority-proof-runner` (`fa13be13c9`) | `polylogue-hjpx.2` | 1 / 3 | 2 files, +312/-37 | 0 | **Retire.** Ref tree equals current master exactly. | +| `origin/feature/test/raw-authority-scale-proof` (`329081fd7d`) | `polylogue-hjpx.2` | 3 / 5 | 6 files, +548/-0 | 0 | **Retire as superseded.** The merged runner plus #2970 contains the current mechanism. Absence of a full-shape receipt is work to execute, not a reason to revive this ref. | +| `origin/feature/fix/lkrc-v35-legacy-native-id` (`adda2b96ac`) | historical `polylogue-lkrc.1` / current `polylogue-lkrc` | 195 / 20 | 17 files, +7,459/-86 | 9 | **Do not merge.** Most behavior landed or was superseded through later repairs and the unified reconciler. Transpose only the still-relevant source-v7 compatibility fixture into the current reconciler model. | +| `origin/worktree-wf_54d4fb2e-841-20` (`4eca0b90fd`) | `polylogue-pf1` | 98 / 2 | 1 file, +213/-0 | 0 | **Delete after preserving findings.** It is a second false-green test, not the required generated inventory. Reopen the Bead and implement freshly. | +| `origin/worktree-wf_54d4fb2e-841-7` (`b4217917ce`) | `polylogue-48h` | 98 / 1 | 41 files, +462/-461 | 17 | **Quarantine and rebuild.** It does not compile, leaves removed helper names live, lacks Bead evidence, and changes error/view/schema behavior. Do not repair by conflict resolution. | +| `polylogue-schema-regen` (`067c87e49`) | `polylogue-1xc.14.1.1` | ancestor | no active delta | n/a | **Remove worktree.** Head is already merged as #2968. | +| `polylogue-fable-program` (`fd7b35492`) | query execution control | ancestor | no active delta | n/a | **Remove worktree.** Head is already merged as #2964. | +| `feature/chore/prune-dead-protocols` (`3796f5452`) | cleanup | ancestor | no active delta | n/a | **Retire ref.** Already in current history. | +| `refs/remotes/bundle/ap7` (`d9db53a672`) | recovery packet for `polylogue-ap7` | 352 / 3 | 91 files, +18,304/-36 | 3 | **Archive only.** Current production slices were selectively adapted via #2700/#2736; proof-only/self-referential material was rejected. | +| `refs/remotes/recovered/demo-packet-v2` (`55c7833cc8`) | recovery packet for demo packet work | 352 / 8 | 82 files, +5,906/-98 | 9 | **Archive only.** Current-relevant pieces were selectively ported via #2704/#2709. Do not restore the stale subsystem. | + +### Routine open PRs + +The snapshot records seven open PRs, all release/dependency automation rather than substantial product implementation: + +| PR | Title | Decision | +| ---: | --- | --- | +| #2701 | `chore(release): 0.3.0` | Regenerate/refresh after product ref cleanup and current changelog state. | +| #2858 | setup-uv 8.3.0 → 8.3.2 | Refresh/rebase last; rerun workflow gate. | +| #2859 | Python patch group | Refresh/rebase last; inspect lockfile regeneration. | +| #2860 | coverage 7.14.1 → 7.15.1 | Refresh/rebase last; rerun coverage workflow. | +| #2861 | syrupy 5.1.0 → 5.5.3 | Refresh/rebase last; review snapshot format drift. | +| #2862 | tree-sitter 0.25.2 → 0.26.0 | Refresh/rebase last; this is the highest semantic-risk dependency update of the group. | +| #2863 | mypy 1.17.1 → 2.3.0 | Refresh/rebase last; expect typing-policy and plugin diagnostics rather than treating lock resolution as proof. | + +None has an owning Bead in the snapshot, and none should be inserted into the product-branch integration sequence merely because it merges cleanly. + +## Important falsifications + +### `polylogue-pf1` is closed on unsupported evidence + +The Bead requires three concrete outcomes: a committed artifact classifying every write-path method, zero unexplained divergences, and a test that regenerates the diff and fails on a new divergence. The close reason asserts that PR #2897 did this and reports nine passing tests. + +Current source does not implement the claimed oracle: + +- `tests/unit/storage/test_storage_twins.py` defines ten `StorageTwinDivergence` objects by hand. +- Its strongest assertions count those ten rows, validate field/status strings, search comments/docstrings, and check that expected modules/method-name substrings exist. +- It does not extract method inventories, SQL statements, tables written, pragma behavior, or error semantics from either lane. +- It cannot notice an eleventh write path unless a human also edits a string the test happens to read. +- `docs/plans/STORAGE_TWINS_DIVERGENCES.md` is a narrative ten-row classification, not output regenerated by a source-derived function. +- The unmerged `test_storage_twins_divergence.py` parses class names and documentation structure but explicitly labels same-table SQL consistency “a placeholder” and only asserts that two files exist. + +Passing these tests, whether nine or any other count, does not satisfy the Bead's anti-vacuity criterion. The status should return to open before the downstream twin-collapse program treats it as a proven prerequisite. + +### `polylogue-48h` is structurally broken, not merely stale + +The branch commit claims strict typing verification, but a direct `python -m compileall -q polylogue` check failed in 22 production files. Typical failures are imports inserted at invalid indentation inside `try`/function bodies. Static source counts also show: + +- 186 calls to `_table_exists(` and zero definitions; +- 34 calls to `_column_exists(` and zero definitions; +- 6 calls to `_index_exists(` and zero definitions. + +The replacement `polylogue/storage/sqlite/introspection.py` is not a semantics-preserving normalization: + +- `table_exists()` propagates `sqlite3.Error`, while at least `operations/archive_debt.py::_table_exists` previously returned `False` on SQLite errors. +- The new default excludes views unless `include_views=True`; some old helpers intentionally treated tables and views alike. +- `schema` and PRAGMA table identifiers are interpolated without a validated identifier policy. +- `column_exists()` has no schema parameter despite schema-sensitive old call sites. +- No new parity tests, ownership map, compatibility notes, public-model checks, or import/layering artifact are present. +- Topology files were regenerated even though the production tree cannot parse; generated-file presence is therefore not closure evidence. + +Testsuite Diet's adjudicated policy also rejects the Bead design's proposed source-spelling/grep tripwire. The fresh implementation should enforce behavior and import ownership, not memorialize private helper names. + +### “Landed” is not the same as “proved at required scale” + +`devtools/raw_authority_scale_proof.py` is a real route: it builds an archive, writes source raws, calls `repair_raw_materialization`, records census receipts and resource counters, and requires two matching quiescent digests. That is valuable production wiring. + +It is not the requested July-15 proof by existence alone. Defaults are 16 components and 24 raws; unit tests use 3 components and 5 raws; the receipt notes state that only an explicit July-15-sized invocation is production-shaped evidence. Current Bead notes record an admitted 10,163-component/15,264-direct-raw scheduler-shape run and a terminated medium run, but explicitly do not claim the 4.788 GiB / 21,398-expanded-candidate result. No successful full result artifact is included in the snapshot. + +### Testsuite Diet has a stale realized-baseline assumption + +The Diet archive correctly says not to create a second workload identity, profile schema, or shadow oracle. That architectural constraint remains sound. Its execution baseline is nevertheless ahead of the source authority: it says to assume correlated variants, named scale/selectivity tiers, C-03, shared receipts, and the complete `polylogue-1xc.14.1` outcome are realized. Current Beads explicitly keep the parent and three children in progress, and current notes identify live full-corpus replay economics, content-shaped property-key regeneration, 55-family default semantics, production-route canaries, and live promotion as residual. + +Diet workers must consume only the subset actually present in current master and wait for the remaining upstream contracts. They must not fill the gap with Diet-owned equivalents. + +## Uncommitted and stranded value + +The extracted working tree is source-clean relative to checked-out local master except for `browser-extension/package-lock.json`. That file is explicitly ignored by `.gitignore`, is generated package-manager state, has no repository history, and is not stranded product value. The apparent mass deletion from reconstructing a Git worktree is an extraction artifact: the working-tree tar omits tracked `.agent`, `.beads`, and lock content. + +Actual stranded value is narrower: + +1. The old `lkrc-v35` branch contains intent for a legacy source-schema-v7 fixture. Its incident-specific mechanism is obsolete, but the compatibility question is still useful. Re-express it as a genuine current unified-reconciler fixture rather than cherry-picking the old command/repair stack. +2. The stale `pf1` branches expose the precise false-green failure and can seed a fresh test design, but no code should be carried over as the oracle. +3. The `48h` branch's call-site census is useful as navigation only. The replacement module and mass import edits are unsafe. +4. Recovery refs retain historical reasoning, but current Beads and selectively merged PRs already adjudicated which production pieces survived. + +## Integration order + +1. **Normalize repository state.** Fast-forward local master from `f654480c` to bundled `0d081e5b`; record or tag stale heads before deleting worktrees; do not merge them. +2. **Retire already-landed/superseded refs.** Remove workload-profile, raw-ledger, raw-unification, raw-proof, schema-regen, fable, capture checkpoint, and dead-protocol refs/worktrees after confirming no unpushed worktree-only files. +3. **Correct tracker truth.** Reopen `polylogue-pf1`, attach the source contradiction, and remove “regenerates the diff” from closure evidence. Keep `polylogue-hjpx.2`, `polylogue-lkrc`, `polylogue-1xc.14.1`, and children open. +4. **Repair `pf1` fresh from current master.** This is a prerequisite for any twin-collapse migration and is independent enough to land early. +5. **Finish the current P1 schema proof chain.** Close `.1` production-scale journal/replay economics and cleanup; then `.2` live privacy regeneration/promotion; then `.3` family/default semantics. Only then claim parent canaries/receipts/promotion complete. +6. **Run and retain the full `hjpx.2` proof.** Use the merged guarded runner on an admitted host, add fairness/conservation mutation variants, and retain exact receipts. Do not alter live archives. +7. **Close `lkrc`/`yla8` live boundary.** Only after fixed-point proof and the separate backup/quiescence/operator authorization gate; retain a stopped-daemon postflight showing no unreported frontier gaps. +8. **Continue CaptureJob parent slices.** Land `polylogue-06zm.2` event/timeline projection before `.3` retention/quota/migration so lifecycle policy consumes the durable event model rather than inventing one. +9. **Rebuild `polylogue-48h` last among these product refs.** It is P4 and touches hot storage/schema routes. Start from current master after P0/P1 churn, migrate semantic cohorts in small batches, and prove parity per cohort. +10. **Refresh routine automation PRs.** Recreate/rebase release and dependency updates on the resulting master, then run their owning workflow/type/snapshot gates. + +There is no safe sequence in which the stale `48h`, `pf1`, capture, or raw-authority branch tips are merged first and cleaned up afterward. + +## Limits and missing evidence + +This review is bounded to the attached snapshot. It did not contact GitHub, read a live daemon, inspect a live archive, run a browser extension, execute deployment, or verify changes that may have occurred after the snapshot generation time. + +Only one production-code execution check was run: `python -m compileall -q polylogue` on the isolated `polylogue-48h` branch, which failed with 22 errors. No pytest, JavaScript, mypy, `devtools verify`, live scale, daemon, or browser command was run by this audit. Test counts and green-gate statements elsewhere in the package are explicitly attributed to PR/Bead records, not independently reproduced. + +Static evidence included bundle/ref enumeration, Git ancestry, patch-id comparison, tree comparison, diff statistics, merge-tree simulation, source/path inspection, current Beads, PR records, and working-tree hash comparison. + +The highest-value next iteration would consume a newer project-state archive after tracker corrections and proof execution. It should specifically attempt to falsify: the new `pf1` inventory's completeness against all SQL construction forms; `hjpx.2` fairness/conservation mutations at full shape; the current reconciler's source-v7 and stopped-daemon fixtures; and the schema journal's live replay query plan/resource receipt. Another pass before those changes exist would mostly reconfirm stale-ref hygiene rather than reduce product uncertainty. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/DECISIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/DECISIONS.md new file mode 100644 index 000000000..2b19e8c2c --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/DECISIONS.md @@ -0,0 +1,73 @@ +# Decisions + +## D1 — Facts are owned by the current domain/storage substrate, not by whichever adapter is named “repository” + +Normalized sessions, messages, blocks, refs, queryable action relations, and derived archive facts are owned by the current split-tier storage/domain code. `SessionRepository`, `Polylogue`, CLI, daemon, and MCP are access/projection boundaries. The async repository/backend is a legacy twin to collapse under `polylogue-hiu`, not a second semantic authority. + +**Consequence:** parity work compares adapters against canonical domain/query outputs. It does not copy semantics into a “surface model” or choose the repository API by name. + +## D2 — `SessionQuerySpec` owns selection; a bounded query transaction must own execution and paging + +`SessionQuerySpec` plus expression lowering remains the canonical selection intent. `Query × Projection × Render` from `polylogue-4p1` remains the product algebra. The missing owner is narrower: canonical plan execution against one declared snapshot/epoch, cancellation/deadline/admission, count/exactness, page boundaries, cursor/result refs, and query receipt. + +**Consequence:** implement `polylogue-z9gh.9/.1` beneath existing adapters. Do not create one omniscient surface abstraction or move rendering/auth into the query engine. + +## D3 — Logical result facts must be invariant; wire presentation may differ + +For identical normalized requests and the same archive snapshot, all read surfaces must agree on selected identities, order, logical total/exactness, page membership, continuation meaning, target/result refs, absence/degraded state, and evidence provenance. CLI text versus JSON, field naming compatibility, MCP wrappers, and browser redaction may differ only through declared projections. + +**Consequence:** introduce an explicit `page_count` when consumers need emitted-row count. Do not overload `total`. A budget may shrink a page but may not erase the logical result or drop replay state. + +## D4 — Delete the daemon’s manual query mirror + +The split-archive fast path must build the same `SessionQuerySpec` as the non-split path and use canonical execution/envelope assembly. ArchiveStore-specific optimization may remain below the spec/plan boundary. + +**Consequence:** `polylogue-4p1.1` lands first. A generated field-inventory test compares every spec field and fails whenever a new field is not handled by the fast path. + +## D5 — Public normalized source vocabulary is `Origin`; provider/native coordinates remain explicit provenance + +Public archive queries and rows use `origin`. Parser/raw acquisition, browser capture, and ImportExplain may use `provider`, material source, capture mode, parser binding, and native ids because they describe pre-normalization/provenance coordinates. Archive ids remain product identity. + +**Consequence:** no global rename from provider to origin. Generate declaration/drift tests under `polylogue-2qx/.1`, including the non-injective Gemini/Drive bridge. + +## D6 — Absence and provenance are independent semantic axes + +Known zero, unknown, unavailable, skipped, redacted, stale, degraded, sampled, capped, and estimated are not interchangeable. Authority/provenance/coverage/freshness must survive all projections for fact families that declare those axes. + +**Consequence:** continue `polylogue-cuxz.2/.3` selectively by fact family. Do not wrap every scalar in a universal container; do not let adapters synthesize zero or certainty from absence. + +## D7 — Operation declarations become executable only for real landed mutations + +`OperationSpec` currently describes effects and guards but does not authorize or dispatch runtime writes. Existing concrete mutation implementations and `MaintenanceTargetCatalog` remain the mechanics. Add stable handler bindings, capability requirements, preview/confirmation rules, idempotency/conflict behavior, and receipt projection for operations that actually exist. + +**Consequence:** implement `polylogue-t46.9`, `polylogue-kwsb.2`, and `polylogue-71ey` incrementally. Do not resurrect a generic polymorphic base for unimplemented operation kinds. HTTP CORS/bearer, MCP process role, and OS authority stay at their edges; the mutation transaction consumes the resulting actor/capability context. + +## D8 — The ingest follow-up string must be executable, not illustrative + +Every accepted/pending operation’s `status_endpoint` must be followed through the same surface and resolve the same operation id. The current `/api/operations/{id}` ingest URL fails this contract because no route is registered. + +**Consequence:** add a route-follow test before choosing the final URL. Register a generic route only if there is a generic registry; otherwise emit the actual ingest-status route. Do not assume maintenance registry ownership. + +## D9 — One status fact assembly, many disclosure-aware projections + +Daemon, CLI, MCP, web, and browser status should consume one snapshot of shared facts. Each projection may omit, redact, compact, or relabel fields according to audience and privacy policy. + +**Consequence:** `polylogue-703` owns convergence. Browser spool-path redaction remains intentional; status assembly must explicitly represent unknown/stale/unavailable data rather than recomputing it differently at each edge. + +## D10 — Remove or quarantine duplicate MCP verbs after usage evidence + +Canonical typed `search`/`list_sessions` and query-unit tools are the forward path. Legacy `archive_list_sessions`/`archive_search_sessions` should be removed or placed behind an explicit compatibility mode after source/telemetry evidence confirms callers and a migration window is defined. + +**Consequence:** `polylogue-t46.8.2.1` follows the shared query transaction so all surviving tools inherit physical paging, exactness, cursor, and response-budget behavior. + +## D11 — Browser capture remains a separate acquisition/auth boundary, with a stable receiver protocol + +The receiver keeps provider-native identity and exact-origin/token policy. Daemon public status keeps its privacy-safe projection. Capture-job errors should use the receiver’s stable error wrapper or declare a separately versioned protocol with explicit consumer tests. + +**Consequence:** browser recovery/pairing/action work remains under `polylogue-06zm`, `polylogue-ptx`, `polylogue-gnie`, and `polylogue-jlme.5`; archive query refactors do not absorb the receiver. + +## D12 — Sequence consolidation after semantic proof + +Land differential parity tests and shared execution first; collapse storage twins second; generate the public operation matrix third; decompose the Python facade last. + +**Consequence:** order is `4p1.1` → `z9gh.9.1`/`yeq.3` → `hiu`/`s1kr` → `1fp`. This prevents a structural refactor from preserving or hiding divergent semantics. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..9694c657c --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/EVIDENCE.md @@ -0,0 +1,233 @@ +# Evidence register + +## Authority and snapshot + +This analysis used the task brief as the question and applied the authority order requested there: current source, repository instructions, current Beads records, then older plans/history where they still agree. + +| Input | SHA-256 | Size | +| --- | --- | ---: | +| `00-polylogue-all.tar(11).gz` | `3d0c5a2e5024632c05438dbc3a08aae1dcd31529472b1185ad5b80c0cd933e30` | 92,457,778 bytes | +| `00-slightly-stale-context-testsuite-diet.tar(11).gz` | `d4fa7fc31c70ff30db076e526836ffc81b8124d3f54c7add5e58d3f57c9dcbff` | 934,006 bytes | +| `04-surface-semantic-matrix.md` | `2d55c9e7b05884f4445881026be348e8f4f6dd395b31e1ddc3aa46bfc40fa530` | 3,278 bytes | + +The project-state snapshot reports generation at `2026-07-17T043202Z`, branch `master`, commit `f654480cadb7cc4c194704e24dfd483199547b35`, dirty state true, and 976 Beads. The extracted branch-delta file list, log, and patch are empty. Analysis was performed in a clone at the recorded commit. The only local tracked change created during analysis was an index rewrite of `uv.lock` while enabling dev dependencies; it was reverted before packaging. + +`AGENTS.md:17-39` is the central repository doctrine: substrate owns meaning, surfaces are leaf adapters, and new semantics land in storage/insights or the product layer first. + +## Source evidence by concept + +### Identity, origin, provider, and provenance + +- `AGENTS.md:47-53` defines generated identity: session `origin:native_id`, message beneath session using native id or position/variant, and block beneath message. +- `polylogue/core/identity_law.py:33-70` implements those deterministic laws. +- `polylogue/storage/sqlite/archive_tiers/write.py:216-220` maps provider to origin before generating the normalized session id. +- `docs/provider-origin-identity.md:15-43` separates provider-wire family, public origin, material source, capture mode, and archive refs. +- `docs/provider-origin-identity.md:47-63` provides the classification table; `:65-92` records current invariants. +- `polylogue/core/sources.py:214-277` implements `Provider`→`Origin`; Gemini and Drive both map to `Origin.AISTUDIO_DRIVE`, so reverse mapping is not one-to-one. +- `polylogue/browser_capture/models.py:112-181` models provider-native sessions and capture ids before normalization. +- `polylogue/browser_capture/models.py:209-228` preserves provider/native/raw coordinates while separately reporting indexed archive session identity. +- `polylogue/surfaces/payloads.py` ImportExplain payloads keep material source, provider hint, capture mode, origin, and produced refs as separate fields. + +**Observed conclusion:** public archive identity/vocabulary is coherent. Browser/raw/provider use is not a public-origin leak when it occurs before normalization or as explicit provenance. + +### Canonical query intent and response + +- `polylogue/archive/query/spec.py:199-243` lists recognized public parameter names. +- `polylogue/archive/query/spec.py:439-495` defines the full immutable `SessionQuerySpec`, including structured filters, ranking/similarity controls, pagination, boolean predicate, and attached-unit projection. +- `polylogue/api/search_envelope_builder.py:42-98` builds a search envelope from an already normalized spec and centralizes cursor/fetch/count/diagnostics assembly for callers that use it. +- `polylogue/surfaces/payloads.py:1249-1318` declares `SearchEnvelope`. Its documentation says `total` is the full matching-session count and that every read surface computes it through the shared spec count. It also defines offset, next offset, opaque cursor, lane, ranking policy, diagnostics, route state, provenance refs, and exactness. +- `polylogue/surfaces/payloads.py:3009-3065` is the canonical envelope builder; its own comment says all four surfaces should call it. + +**Observed conclusion:** query intent and wire shape have canonical models. Execution, counting, and paging are not yet singular. + +### Daemon canonical versus split-archive path + +Canonical path: + +```text +GET /api/sessions + -> DaemonAPIHandler._handle_list_sessions + -> _build_query_spec_params + -> _do_list + -> SessionQuerySpec.from_params + compile_expression_into + -> list/count or build_search_envelope_for_spec +``` + +Source: `polylogue/daemon/http.py:2267-2408`. + +Split-archive path: + +```text +GET /api/sessions + -> _handle_list_sessions + -> _web_reader_archive_root() is present + -> _do_archive_list_sessions(archive_root, raw params, limit, offset) + -> compile only query expression + manually merge selected HTTP fields + -> ArchiveStore.list_summaries/search_summaries/count_sessions + -> manually assembled JSON +``` + +Source: `polylogue/daemon/http.py:2282-2285`, `:2410-2734`. + +Load-bearing observations: + +- `:2476-2479` explicitly says the fast path does not construct `SessionQuerySpec` and must mirror parameters. +- `_filter_kw` at `:2512-2539` contains only a subset of the spec. +- `:2610-2678` hardcodes `retrieval_lane="dialogue"` and ranking metadata, sets `total=len(hits)`, and emits no cursor or next offset. +- List mode computes a filtered count (`:2704-2734`), but unsupported behavioral fields remain ignored. +- Explicit zero bounds can be erased by `or ... or None` at `:2504-2507`, unlike `optional_int()` in `query/spec.py:189-196`, which deliberately preserves zero. + +`daemon-query-field-risk.csv` turns this call-path comparison into a field inventory. + +### CLI paths + +```text +CLI common read + -> _try_emit_daemon_session_page + -> support discriminator + -> daemon /api/sessions + -> normalize daemon row / rebuild CLI envelope + +CLI unsupported read or mutation + -> local ArchiveStore + -> local list/search/mutation implementation + -> local envelope +``` + +- `polylogue/cli/archive_query.py:894-980` is daemon-first read dispatch. +- `:1071-1101` excludes unsupported query/projection controls from daemon mode. +- `:1335-1354` normalizes the legitimate `word_count`/`words` presentation difference. +- `:1357-1416` derives daemon list continuation but discards daemon search continuation. +- `:1880-1946` directly implements delete preview/confirmation/execute. +- `:1971-2050` sets direct list/search `total` to emitted row count. + +**Observed conclusion:** CLI presentation compatibility is intentional; totals, cursor loss, and direct write policy are semantic forks. + +### Python API and repository twins + +- `polylogue/api/archive.py:3661-3790` exposes overlapping list/search methods with direct ArchiveStore access. +- `polylogue/api/archive.py:3792-3901` builds `search_envelope()` manually with the shared payload builder rather than delegating to `build_search_envelope_for_spec()`. +- `polylogue/api/archive.py:3903-4085` exposes a second broad archive list/count/search family. +- `polylogue/api/archive.py:4541-4570` exposes another spec-based query method. +- `polylogue/api/archive.py:4820-4850` resolves and deletes directly through `ArchiveStore`. +- `polylogue/storage/repository/__init__.py:44-93` exposes the async repository/backend boundary, separate from current split-tier sync ArchiveStore paths. + +**Observed conclusion:** names do not determine authority. The storage/domain implementation that actually owns normalized rows is authoritative; repository/facade methods are adapters or legacy twins. `polylogue-hiu` contains the already-ratified consolidation direction. + +### MCP canonical and legacy paths + +- `polylogue/mcp/server_tools.py:209-266` canonical search lowers a typed request to `SessionQuerySpec`, opens ArchiveStore, attaches response context, and emits `archive_search_payload()`. +- Canonical list uses the corresponding typed request and `archive_session_list_payload()` in the same module. +- `polylogue/mcp/archive_support.py:277-469` separately orchestrates list/search counts, rows, and envelopes. Similar-session totals may be inferred from page state rather than counted exactly. +- `polylogue/mcp/server_tools.py:451-652` retains legacy `archive_list_sessions` and `archive_search_sessions`, which call facade methods and build their own totals/paging. +- `polylogue/mcp/server_tools.py:268-373` shows the desired execution direction: `query_units()` creates `QueryExecutionContext` and calls `execute_archive_read()`. +- `polylogue/mcp/server_support.py:47-48` defines process roles and a 25,000-byte budget. +- `polylogue/mcp/server_support.py:193-277` enforces that budget after serialization and may replace the entire payload with a metadata-only envelope and narrower replay call. + +**Observed conclusion:** MCP has typed canonical request/payload code, duplicate legacy tools, and a transport budget that is not yet physical lossless paging. + +### Mutations and operation authority + +- `polylogue/operations/specs.py:47-101` defines descriptive `OperationSpec`/`OperationCatalog`; `:845-856` builds cached catalogs. +- Production references found by source search are artifact graph, scenario metadata/execution, verification, and tests. No production mutation handler dispatches through `OperationCatalog.resolve()`. +- `polylogue/operations/operation_contract.py:3-12` explicitly records that the generic operation framework was removed because only import landed and nothing dispatches on `.kind`. +- `polylogue/maintenance/targets.py:14-109`, `:158-246` defines the narrower executable maintenance target catalog. +- `polylogue/maintenance/planner.py:423-543` uses that catalog for preview and execute; `polylogue/maintenance/replay.py` uses it for replay. +- `polylogue/daemon/http.py:4445-4499` calls maintenance preview/execute directly. +- CLI maintenance and MCP maintenance modules similarly call concrete planners; CLI/API/MCP/daemon deletes use separate direct implementations/adapters. + +**Observed conclusion:** `OperationSpec` describes required policy but is not current mutation authority. Maintenance target resolution is already a real narrow authority and should be composed, not replaced. + +### Follow-up route defect + +- `polylogue/daemon/http.py:4410-4415` returns an `OperationFollowUp` with `/api/operations/{op_id}`. +- `polylogue/operations/operation_contract.py:65-90` says the emitted status identifier must be accepted by that surface’s lookup tool. +- `polylogue/daemon/route_contracts.py:449-455` registers `/api/maintenance/status/:id`. +- Source search found no `/api/operations/:id` route or dispatch. + +**Observed conclusion:** the advertised URL has no matching route. **Unresolved:** which registry owns ingest ids and therefore what endpoint should be canonical. + +### Authorization and disclosure + +- `polylogue/daemon/route_contracts.py:27-34` defines daemon auth vocabularies. +- `polylogue/daemon/http.py:1709-1733` enforces machine auth, same-origin, and write gating for reset/ingest/maintenance. First-party web cookies are intentionally insufficient for these archive-control operations. +- `polylogue/browser_capture/server.py:197-230` enforces allowed origin and optional token; preflight checks origin only. +- `polylogue/mcp/server_support.py:47-48` defines process-level `read/write/review/admin` roles. +- CLI/Python/repository execution relies on local process/OS authority. + +**Observed conclusion:** transport authorization should remain edge-specific. Shared operation policy must own capability, target resolution, preview, confirmation, idempotency/conflict, and receipt semantics without trying to replace CORS, bearer, MCP process role, or OS policy. + +### Browser errors and status + +- `polylogue/browser_capture/models.py:192-205` defines full local receiver status including spool path. +- `polylogue/daemon/status.py:419-444` strips spool/artifact paths for public daemon status. +- `polylogue/browser_capture/models.py:338-344` defines the stable safe receiver error envelope. +- `polylogue/browser_capture/server.py:193-195` uses it for normal receiver errors. +- `polylogue/browser_capture/server.py:520-525` emits capture-job errors with a different nested object. + +**Observed conclusion:** status redaction is legitimate; the error shape is a real divergence unless made an explicit versioned subprotocol. + +### Status duplication + +- `polylogue/daemon/status.py:349-390` declares `DaemonStatus` as consumed by all surfaces. +- `polylogue/daemon/status.py:1986` and `:2192` build and serialize daemon status. +- `polylogue/cli/commands/status.py:603`, `:755`, `:1140`, `:1319`, `:1528`, `:1924`, and `:1986` independently probe tiers, readiness, workload, compact output, direct component readiness, and claim guards. + +**Observed conclusion:** projection may differ; fact computation should not. Bead `polylogue-703` records observed production disagreement and owns convergence. + +## Beads adjudication + +| Bead | Current relevance to this analysis | +| --- | --- | +| `polylogue-4p1` / `.1` | Existing read algebra and exact daemon fast-path repair; no new surface framework required | +| `polylogue-z9gh.1` | Interruptible, resource-bounded read execution | +| `polylogue-z9gh.9` / `.1` | Sole bounded/resumable query transaction and exact cross-surface totals/order/pages/cursors/refs | +| `polylogue-yeq.3` | Query laws, differential parity, and scale regression ownership | +| `polylogue-t46`, `.8`, `.8.2.1` | Surface contract ownership and MCP duplicate-tool convergence/removal | +| `polylogue-t46.9` | Executable operation declaration/handler inventory | +| `polylogue-kwsb.2` | Mutation transaction semantics | +| `polylogue-71ey` | Maintenance catalog/replay authority | +| `polylogue-703` | One status-fact assembly | +| `polylogue-hiu` | Ratified storage-twin collapse onto sync core plus async adapter | +| `polylogue-s1kr` | Generated, committed surface parity matrix and docs | +| `polylogue-1fp` | Facade decomposition after semantics stabilize | +| `polylogue-2qx/.1` | Origin declaration/drift control | +| `polylogue-cuxz.2/.3` | Independent absence/authority/provenance/coverage/freshness axes | +| `polylogue-06zm`, `ptx`, `gnie`, `jlme.5` | Browser durable recovery, action conduit, secure pairing, receiver identity | + +No new top-level architecture Bead is necessary. The ingest follow-up defect can be an acceptance item/subtask under operation contract execution (`t46.9`) unless tracker policy requires a dedicated bug. + +## Test evidence + +Environment setup and test evidence are separated from product results: + +1. Initial test invocation failed before collection with `ModuleNotFoundError: hypothesis`. +2. `uv sync --extra dev --frozen` could not resolve locked public package URLs because public DNS/network access was unavailable in the analysis environment. +3. `uv sync --extra dev` resolved the dev bundle from the available internal package index. This rewrote `uv.lock`; the tracked rewrite was reverted before packaging. +4. The selected contract/property suite passed: **86 passed in 7.37s**. Files covered canonical search-envelope construction, cursor laws, origin vocabulary, query descriptor parity, browser receiver and capture jobs, operation specs/contracts, maintenance endpoints, and CLI/daemon golden parity. +5. A focused daemon/operation run passed: **11 passed in 3.58s**. It covered route query-parameter construction, bounded SQL helper use, list/search envelope shape and target refs, split-tier search, degraded FTS state, shared list payload, privacy-safe browser status, and OperationSpec structural contracts. +6. An earlier broad invocation of `test_web_reader.py` plus `test_daemon_http_contracts.py` did not finish within the 300-second analysis limit after substantial progress. It is recorded as a non-conclusive harness timeout, not as a passing or failing product result. + +The 97 passing tests establish that current isolated contracts hold. They do **not** establish cross-path equivalence: the seeded search envelope test has only three hits; the bounded helper test searches source text; OperationSpec tests validate catalog structure, not runtime dispatch; no test follows the ingest ack URL; no generated test enumerates every `SessionQuerySpec` field through canonical and split daemon execution. + +## Secondary, explicitly stale evidence + +The smaller archive is dated `2026-07-16` and is treated only as secondary corroboration: + +- `architecture/06-query-cancellation-and-bounds.md` recommends one query execution context and owned read lifecycle. +- `architecture/07-evidence-provenance-and-public-algebra.md` preserves independent EvidenceValue axes and rejects semantic strengthening by projection. +- `architecture/04-destructive-and-authentication-boundaries.md` recommends executable operation declarations, but its proposed universal `OperationExecutor` is broader than current source evidence. This report narrows the recommendation to bindings for real landed mutations and respects `operation_contract.py`’s anti-speculation decision. +- `architecture/09-capture-delivery-and-deployed-status.md` supports independently refreshed component snapshots and disclosure-aware status. +- `areas/status-and-facades.md` supports a shared state-transition matrix and one semantic fact source with surface projections. + +Where these documents conflict with current source or Beads, current source/Beads control. + +## Limitations and missing evidence + +- No live browser extension, receiver, daemon deployment, MCP process, or production archive was available. +- No live operation registry was queried, so ingest-id ownership remains unresolved. +- No live usage telemetry was available to confirm whether legacy MCP tools are externally used; removal must pass a usage/compatibility gate. +- No live-scale archive was used to reproduce response-budget erasure, query cancellation latency, memory pressure, or status disagreement recorded in Beads. +- The broad daemon test files exceeded the local 300-second invocation limit; focused relevant tests passed. +- Static call-path inspection cannot prove that every indirect/decorated mutation path was found. Source search was broad, but runtime plugin registration or external consumers could add paths outside the snapshot. +- The project snapshot was marked dirty even though its branch-delta artifacts were empty. The report treats the captured working tree/source archive as authority and records the commit for orientation, not as a claim that `git show ` alone reproduces every byte. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/NEXT-ACTIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/NEXT-ACTIONS.md new file mode 100644 index 000000000..a4c1e1eee --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/NEXT-ACTIONS.md @@ -0,0 +1,234 @@ +# Next actions + +The order below is designed to remove active semantic forks first, then establish one execution boundary, then consolidate adapters. Each action includes acceptance criteria, falsification evidence, and local verification. + +## 0. Repair the daemon split-archive query path + +**Owner:** `polylogue-4p1.1` under `polylogue-4p1` +**Primary code:** `polylogue/daemon/http.py`, query request lowering, `polylogue/api/search_envelope_builder.py`, ArchiveStore query adapter +**Priority:** immediate, contained + +Implementation: + +1. Build `query_params` once in `_handle_list_sessions()` and pass the normalized mapping/spec into both execution modes. +2. Compile the expression into a base `SessionQuerySpec` exactly once. +3. Replace the manual `_filter_kw` mirror with a spec/plan-to-ArchiveStore adapter owned below the route, or route the fast path through the shared query executor when available. +4. Build ranked pages through the canonical envelope/page builder. Preserve degraded FTS route state as an additive projection. +5. Delete the manual mirror comment/block once no route depends on it. + +Acceptance: + +- A generated test enumerates every `dataclasses.fields(SessionQuerySpec)` member and classifies it as filter, ordering, page, similarity, boolean, or projection. No field can be added without an explicit route/executor decision. +- For a seeded archive with more than two pages, canonical and split modes return identical ids, order, complete total/exactness, page boundaries, next cursor/offset, lane/ranking metadata, target refs, diagnostics, and route state for every supported field and representative combinations. +- Explicit `min_messages=0`, `max_words=0`, and equivalent zero bounds remain bounded rather than becoming `None`. +- Exclusion text, project, typed-only, message type, boolean expressions, `since_session_id`, similarity, latest/sample/sort/reverse, and cursor are either implemented identically or rejected with the same typed valid-values/unsupported response before execution. Silent ignore is forbidden. + +Falsification: + +- Monkeypatch the canonical builder/spec adapter to raise and prove that both daemon modes fail; any surviving response proves a bypass. +- Add a new dummy spec field in a mutation test; the inventory test must fail until classified. +- Seed `limit=2` with five matches; any `total==2` or absent continuation falsifies parity. + +Local verification: + +- Run focused daemon route tests in both `_web_reader_archive_root()` states. +- Inspect generated SQL/plan descriptors for identical pushed predicates. +- Run quick contract/property gates and the new field-inventory differential suite. + +## 1. Land the shared bounded query transaction + +**Owners:** `polylogue-z9gh.1`, `polylogue-z9gh.9`, `polylogue-z9gh.9.1`; semantic algebra `polylogue-4p1` +**Primary code:** archive query execution layer, read-only connection lifecycle, cursor/result page models, surface adapters +**Priority:** P0 + +Implementation: + +- Make one request/execution/page contract own canonical plan digest, archive snapshot/epoch, actor/disclosure context, admission, deadline/cancellation, exactness/count policy, selected order, physical page, continuation/result ref, and query receipt. +- Run SQLite on an owned read-only connection outside server event loops; cancellation/deadline/disconnect must interrupt that exact connection and clean temp/spool state. +- Fetch/serialize a bounded page rather than materializing the full logical result first. +- Make current `query_units()` execution-control use the same transaction as session list/search/detail/messages/tree/topology. +- Feed existing shared payload builders from the page result; do not make the executor render CLI text, HTTP, or MCP. + +Acceptance: + +- Identical normalized requests at the same declared snapshot yield identical identities, stable order/frame, total/exactness, page boundaries, cursor state, and result refs across CLI direct/daemon, Python, daemon canonical/split, and MCP. +- Cursor state preserves expression, all structural filters, material scope, projection/render budget, sort/order, snapshot/epoch, and query-run identity; iterating pages yields every logical row exactly once and terminates. +- Cancellation of an expensive recursive/aggregate read interrupts within the recorded SLO while health and a cheap read remain responsive. +- RSS/temp/spool work stays within declared bounds on the live-scale cases recorded by Beads; no surface serializes a full result only to discard it. +- Zero results, empty archive, degraded index, timeout, cancellation, busy/admission-deferred, stale snapshot, and invalid cursor are distinct typed outcomes. + +Falsification: + +- Remove/bypass the shared executor from any surface and confirm a source/architecture test fails. +- Run a page walk while inserting new rows; the declared epoch behavior must prevent duplicate/skip ambiguity or explicitly reject stale continuation. +- Force a response larger than MCP’s budget; at least one useful page plus lossless continuation must remain, not a metadata-only replacement with incomplete arguments. +- Cancel a query and inspect active SQLite statements/worker resources; any continuing work falsifies ownership. + +Local verification: + +- Run property tests for pagination laws and differential surface tests over seeded and live-scale archives. +- Capture plan descriptors, peak RSS/temp bytes, cancellation latency, event-loop heartbeat, and cleanup receipts. +- Re-run the known Workflow-tool, coordinator-delegation, 129-session, and recursive topology cases named in `z9gh.9/.1`. + +## 2. Build the cross-surface differential parity suite + +**Owner:** `polylogue-yeq.3`; generated documentation/matrix consumer `polylogue-s1kr` +**Primary code:** tests/property, tests/integration or contract harness, generated parity manifest +**Priority:** P1, parallel with Action 1 once page contract exists + +Test design: + +- Generate requests from `SessionQuerySpec` field metadata plus a curated interaction matrix, not from manually copied option names. +- Seed at least three pages, tied ranks, missing optional fields, multiple origins/tags/repos/projects, typed/untyped messages, actions/tools, path refs, zero bounds, timestamps, lineage, and degraded search state. +- Execute through CLI direct, CLI daemon, Python facade, daemon canonical, daemon split, MCP canonical, and any retained compatibility tools. +- Normalize only declared presentation differences, then compare logical facts. + +Acceptance: + +- The comparator checks identity/order, total and exactness, page count, cursor/offset, lane/ranking policy, target/result/query refs, absence/degraded/error categories, and required provenance axes. +- A machine-readable committed matrix maps every semantic operation to each surface and records intentional absence with an owning Bead/reason. +- Mutation tests prove the suite fails for page-total substitution, cursor removal, one omitted filter, provider leakage into normalized rows, zero-as-unknown, and unauthorized destructive execution. + +Falsification: + +- Replace complete total with page length in one adapter; the test must fail. +- Drop one field from daemon/MCP request lowering; generated inventory must fail. +- Change only CLI text formatting; logical parity must continue passing, proving the suite does not overconstrain presentation. + +Local verification: + +- Keep a small deterministic fixture for quick gates and a separate live-scale profile for performance/resource laws. +- Record normalized request/plan/page digests in failures so implementers can identify the owning layer. + +## 3. Converge MCP verbs and response paging + +**Owners:** `polylogue-t46.8`, `polylogue-t46.8.2.1`, `polylogue-z9gh.9.1` +**Primary code:** `polylogue/mcp/server_tools.py`, `archive_support.py`, `server_support.py` +**Priority:** after shared page transaction is callable + +Implementation: + +- Route canonical list/search/query units through the shared transaction. +- Enforce the 25,000-byte transport budget by selecting a smaller physical page/projection before full serialization. +- Emit complete opaque continuation/result refs from the transaction. +- Inventory external/internal callers of legacy archive tools. Remove them when zero-use evidence is sufficient; otherwise place them behind explicit compatibility registration and adapt them to the same transaction. + +Acceptance: + +- No MCP list/search path owns filter mapping, totals, paging, cursor, or query receipt independently. +- Oversized logical results retain useful rows and complete continuation state. +- Canonical and compatibility verbs produce identical logical results for identical requests. +- Role discovery and write-role enforcement remain MCP-specific edge policy. + +Falsification: + +- Grep/source test finds a legacy tool calling a broad facade list/search method directly. +- Force an oversized response; if the full body is first built or all evidence rows disappear, the action fails. +- Usage scan finds a live caller after removal without a migration path. + +## 4. Make mutation declarations executable and repair operation follow-up + +**Owners:** `polylogue-t46.9`, `polylogue-kwsb.2`, maintenance authority `polylogue-71ey` +**Primary code:** operation specs/bindings, mutation transaction, import ack/status registry, maintenance planner/replay, surface adapters +**Priority:** P1 + +Implementation: + +- Add executable bindings for real operations only: stable operation id/version, handler, capability, target resolver, preview requirement, confirmation strength, idempotency/conflict rule, affected tiers, and receipt projection. +- Reuse `MaintenanceTargetCatalog` for maintenance resolution/replay rather than creating a competing target model. +- Convert CLI/API/MCP/daemon mutation handlers into actor/auth adapters that submit the same request and project the same preview/receipt/outcome. +- Preserve defense-in-depth storage guards. +- Add a generic operation-status route only if one registry truly owns all ids; otherwise make each follow-up URL name the actual owning registry. + +Acceptance: + +- Every runtime `OperationSpec` with a mutating effect either has a real executable binding or is explicitly declaration-only and cannot appear as executable surface capability. +- Destructive operations require preview evidence and a confirmation token bound to actor, operation/version, archive, target-set digest, and expiry; stale target sets fail closed. +- Same idempotency key/request cannot execute twice; conflicts and partial failures produce typed durable receipts. +- A test schedules ingest, follows the returned `status_endpoint`, resolves the same operation id, and reaches terminal status. +- CLI, daemon, MCP, and Python delete/tag/maintenance/import outcomes agree on target identity, affected count, no-op/not-found, preview, and receipt refs while retaining edge-specific auth UX. + +Falsification: + +- Patch an operation’s declared capability/preview rule; a surface that still executes directly proves a bypass. +- Change targets after preview; execution must return `preview_stale`. +- Follow the ingest ack URL in the current implementation; it should fail, preserving the regression until repaired. +- Grep/source test finds direct surface calls to registered mutation handlers outside the transaction adapter allowlist. + +Local verification: + +- Run real split-tier fixtures for user overlay writes, session delete, reset, import scheduling, and maintenance preview/execute/replay. +- Inspect ops/user/source/index tier effects and receipt durability across restart. + +## 5. Converge status facts without flattening disclosure + +**Owner:** `polylogue-703` +**Primary code:** `polylogue/daemon/status.py`, `polylogue/cli/commands/status.py`, ops diagnostics, browser/public projections +**Priority:** P2 after read transaction fact snapshots stabilize + +Implementation: + +- Define one status snapshot assembly over archive tiers, daemon lifecycle, convergence, cursor lag, ingest attempts, FTS/insight/embedding readiness, browser receiver, storage/disk, and claim guards. +- Mark collector authority, observed time, freshness, degraded/unavailable reason, and disclosure class. +- Make CLI, daemon, MCP, web, and browser status pure projections/compactors/redactors. + +Acceptance: + +- State-transition fixtures cover empty, ingesting, pending convergence, partially failed, stale, repaired, healthy, and unavailable. +- Every surface agrees on shared fact value/state/authority/time while allowed field sets and path redaction differ. +- A single snapshot prevents mixed-frame claims during rebuild. +- Browser public status never exposes spool/artifact paths; local receiver status may. + +Falsification: + +- Patch a shared fact collector; all projections must change consistently. +- Remove the daemon status builder and prove CLI direct status cannot silently recompute the same semantic fact. +- Seed known zero versus unavailable; renderers must not collapse them. + +## 6. Collapse storage/API twins and generate the governed matrix + +**Owners and order:** `polylogue-hiu` → `polylogue-s1kr` → `polylogue-1fp` +**Priority:** P2/P3 after Actions 1–3 + +Implementation: + +- Execute the ratified sync-core/async-adapter migration mixin by mixin with ingest and interactive-read gates. +- Redirect facade/repository methods to the same core transaction/operations rather than preserving duplicate SQL. +- Generate the committed CLI/MCP/Python/HTTP semantic-operation matrix from declarations and actual bindings. +- Decompose `polylogue/api/archive.py` by capability protocols only after duplicate semantics are removed. + +Acceptance: + +- Async storage twins and documented divergence comments are deleted as specified by `polylogue-hiu`; facade signatures remain compatible where promised. +- Ingest throughput remains within the ratified noise envelope and read/cancellation SLOs hold. +- Generated matrix drift-checks every operation/surface, including intentional absence and compatibility mode. +- Facade decomposition changes ownership/imports, not evidence semantics. + +Falsification: + +- Differential tests pass only because both twins share the same bug; mutation tests against canonical laws must still fail. +- A facade file split introduces a new count/filter/page implementation; source guard fails. +- Generated matrix lists a method by name but cannot resolve its actual handler/call path; generation fails. + +## 7. Close identity/evidence/browser protocol gaps + +**Owners:** `polylogue-2qx/.1`, `polylogue-cuxz.2/.3`, `polylogue-06zm`, `polylogue-ptx`, `polylogue-gnie`, `polylogue-jlme.5` +**Priority:** continue alongside later consolidation where dependencies permit + +Acceptance: + +- Generated origin declarations cover public filters/rows and prove Provider→Origin bridges, including non-injective mappings. +- ImportExplain and capture state preserve material source, provider/native id, capture mode, parser binding, normalized origin, and produced archive refs as distinct coordinates. +- Declared EvidenceValue fact families preserve absence, authority, temporal provenance, enumeration/coverage, freshness, and calibrated confidence across every surface. +- Receiver errors use one versioned envelope; capture-job consumers have contract tests. +- Pairing, receiver identity, durable capture recovery, and browser action conduit retain exact-origin/token and disclosure boundaries. + +Falsification: + +- Replace provider-native id with archive id before normalization or leak provider as the normalized public source filter; identity tests fail. +- Render unknown price/coverage as zero or archive-wide certainty; evidence law tests fail. +- Capture-job error omits receiver/schema identity without an explicit versioned subprotocol; receiver contract fails. + +## Expected value of another iteration + +A second ordinary iteration has high value after Action 0 or a prototype of Action 1 lands. It can run the generated field inventory against the new code, inspect concrete query-page and mutation-binding types, and resolve the ingest status registry from implementation rather than inference. Before code changes, another static pass would mostly refine inventories and discover additional call sites; useful, but lower value than converting the current findings into executable differential tests. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/REPORT.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/REPORT.md new file mode 100644 index 000000000..c848587cd --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/REPORT.md @@ -0,0 +1,166 @@ +# Cross-surface semantic parity matrix + +**Job:** `analysis-04` +**Revision:** `r01` +**Evidence snapshot:** Polylogue `master` at `f654480cadb7cc4c194704e24dfd483199547b35`, generated `2026-07-17T043202Z` +**Surfaces covered:** CLI, Python API, repository/operations, daemon HTTP, MCP, browser capture/status + +## Executive conclusion + +Polylogue already has the right architectural rule: storage and product layers own meaning, while CLI, API, daemon, MCP, and browser code are projections. Current code follows that rule well for normalized identity and source vocabulary, but not yet for read execution, mutation admission, or status assembly. + +The strongest shared semantics are identity and public origin. Stored session identity is deterministically computed as `origin:native_id`; message and block identities are derived beneath it. Public archive reads use `origin`; parser, acquisition, and browser-capture boundaries retain `provider` and provider-native ids as provenance. The Gemini/Drive-to-`aistudio-drive` bridge is intentionally non-injective. This is a sound model, not a naming defect. + +The highest-risk divergence is execution-path-dependent query meaning. `SessionQuerySpec` is the canonical selection intent and `SearchEnvelope` is the declared cross-surface response, but the daemon split-archive path manually re-lowers filters and manually assembles search responses. It omits or weakens fields including exclusion text, retrieval lane, project, typed-only, message type, `since_session_id`, boolean predicates, similarity, sample/latest/sort/reverse, and cursor state. It reports `total=len(hits)` for a search page and emits no continuation. CLI direct mode also reports page length as `total`; CLI daemon search discards continuation; legacy MCP archive tools have their own paging and total rules; Python `search_envelope()` repeats orchestration rather than calling the existing shared spec builder. The result model is shared more than the result transaction. + +Mutation semantics are even less centralized. `OperationSpec` declares names, effects, surfaces, previewability, and safety guards, and its tests validate those declarations. Runtime CLI, API, daemon, and MCP handlers still call concrete write implementations directly. No production dispatcher authorizes or invokes a handler by `OperationSpec`; the source explicitly records that no generic `.kind` dispatch exists. The maintenance target catalog is a real executable authority for maintenance targets, but it does not unify delete, import, tag, reset, and other mutations. This makes `OperationSpec` descriptive metadata, not current mutation authority. + +One concrete operation-contract defect is already visible without a live deployment: daemon ingest returns a follow-up URL `/api/operations/{id}`, while the registered status route is `/api/maintenance/status/:id`; no generic `/api/operations/:id` route was found. The follow-up contract says the emitted identifier must be accepted by the same surface. The correct repair depends on which registry actually owns ingest ids; mapping ingest ids into the maintenance registry is not established by the snapshot. + +Status and browser differences divide cleanly into legitimate projections and real duplication. Browser receiver status may expose its spool path locally, while daemon public status removes spool and artifact paths; daemon machine mutations require bearer-style machine auth plus same-origin and a write gate; browser capture requires exact allowed origin plus an optional token; MCP uses process role; CLI/Python inherit local process authority. Those are legitimate edge policies. By contrast, daemon status and CLI status independently probe and assemble many of the same archive facts, and browser capture-job errors bypass the receiver’s otherwise stable safe error envelope. Those are parity defects. + +The correct remedy is not one giant “surface abstraction.” It is a small set of owning contracts: canonical query intent, one bounded query transaction and page result, shared fact payloads, executable operation bindings plus a mutation transaction, one status-fact assembly, and transport-specific projection/auth adapters. This ordering matches the existing Beads rather than inventing a parallel architecture. + +## Semantic ownership map + +| Repeated concept | Current substrate/product owner | Surface role | Assessment | +| --- | --- | --- | --- | +| Normalized session/message/block identity | generated identity law and split archive storage | render refs, anchors, native provenance | Coherent | +| Public source family | `Origin`; `Provider`→`Origin` bridge at normalization | accept/render `origin`; retain `provider` before normalization | Coherent with deliberate non-injective bridge | +| Query selection intent | `SessionQuerySpec`, expression compiler, query plan | parse transport arguments into one spec | Correct owner, incompletely used | +| Query execution, snapshot, bounds, totals, page, continuation, receipts | no single production owner yet; partial `QueryExecutionContext`, `build_search_envelope_for_spec`, `build_search_envelope` | invoke and project one page | Systemic gap; Beads `z9gh.9/.1` own it | +| Read wire payloads and target refs | `polylogue/surfaces/payloads.py` | JSON/text/YAML/MCP/browser projection | Mostly coherent; adapters still reassemble facts | +| Mutation target/effect mechanics | concrete ArchiveStore/facade methods; maintenance target catalog/planner/replay | admission, auth, preview, confirmation, projection | Mechanics exist; authority is fragmented | +| Mutation declaration | `OperationSpec` catalog | metadata, verification, discovery | Descriptive only; not a dispatcher/authorizer | +| Authorization/disclosure | transport edge plus local process/OS boundary | enforce edge policy | Legitimate variation; domain guards still need one owner | +| Status facts | daemon and CLI currently assemble separately | redact/render per surface | Duplicate semantic assembly | +| Evidence absence/provenance | typed payloads plus emerging `EvidenceValue` design | preserve unknown/degraded/redacted/coverage axes | Partial; several families still use ad hoc envelopes | + +## Actual surface call paths + +### CLI + +Common non-mutating session pages first attempt `_try_emit_daemon_session_page()` in `polylogue/cli/archive_query.py:894-980`. `_daemon_session_page_supported()` deliberately rejects projections and query features that the HTTP route cannot represent (`:1071-1101`). The adapter then normalizes the daemon web row (`word_count`→legacy CLI `words`) and builds a CLI envelope (`:1335-1416`). This shape conversion is legitimate compatibility work; discarding search cursors is not. + +Unsupported reads and all mutations fall back to a local `ArchiveStore`. Direct list/search emitters create their own envelopes and set `total` to the number of emitted rows (`:1971-2050`). Delete performs its own dry-run, interactive/plain confirmation, and direct `delete_sessions()` call (`:1880-1946`). Thus the CLI has two execution paths with different totals and continuation behavior, plus a third semantic path for writes. + +### Python API + +`Polylogue` is a broad async facade, but many methods open the synchronous split-tier `ArchiveStore` directly. `list_sessions()`, `list_summaries()`, `list_sessions_for_spec()`, `search_session_hits()`, legacy `search()`, `search_envelope()`, `archive_list_sessions()`, `archive_search_sessions()`, `query_sessions()`, counts, and mutations overlap in capability (`polylogue/api/archive.py:3661-4085`, `:4541-4570`, `:4820-4850`). `search_envelope()` uses the canonical payload builder but duplicates spec/cursor/fetch/count assembly instead of delegating to `build_search_envelope_for_spec()`. + +The public Python facade’s smaller keyword signature is a legitimate projection difference. The duplicate execution and counting paths are not. + +### Repository / operations + +`SessionRepository` wraps a separate async SQLite backend and retains legacy `source`-named query concepts, while current API and daemon paths use the split-tier sync `ArchiveStore`. Source evidence therefore does not support “the repository” as the semantic owner merely because of its name. The practical fact owner is the current storage/domain implementation; Bead `polylogue-hiu` already decides to collapse the storage twins onto the sync core behind async adapters. + +`OperationSpec` is used by artifact graphs, scenarios, verification, and contract tests. No production surface dispatches a mutation from `OperationCatalog.resolve()` or `.kind`. Concrete maintenance does use `MaintenanceTargetCatalog` in `polylogue/maintenance/planner.py` and `polylogue/maintenance/replay.py`, so that narrower catalog is current executable domain authority for maintenance target resolution. + +### Daemon HTTP + +Without the web-reader split archive root, `/api/sessions` constructs a `SessionQuerySpec`, compiles the expression into it, and routes ranked search through `build_search_envelope_for_spec()` (`polylogue/daemon/http.py:2267-2408`). With a split archive root, the same route bypasses that code and calls `_do_archive_list_sessions()` (`:2282-2285`, `:2410-2734`). The fast path says it must “mirror” public parameters manually (`:2476-2479`), constructs `_filter_kw`, and hand-assembles search output. It hardcodes the dialogue lane and mixed policy, sets `total=len(hits)`, and emits no `next_offset` or `next_cursor` (`:2610-2678`). + +Daemon machine mutation routes have a clear edge policy: machine auth only, cross-origin rejection, and a write gate (`:1709-1733`). That policy should remain at the HTTP boundary. It does not replace the need for one domain mutation request/preview/receipt contract. + +### MCP + +Canonical `search` builds `MCPSessionQueryRequest`, lowers it to `SessionQuerySpec`, uses `ArchiveStore`, and emits `archive_search_payload()` under a replay context (`polylogue/mcp/server_tools.py:209-266`). Canonical `list_sessions` follows the same pattern. `query_units()` is already the best model for the next step: it creates a shared `QueryExecutionContext` and calls `execute_archive_read()` (`:268-373`). + +Legacy `archive_list_sessions` and `archive_search_sessions` remain separately registered and call Python facade methods with separate totals and pagination. The MCP response budget is enforced after full serialization; when exceeded, the body is replaced by metadata and a narrowed continuation (`polylogue/mcp/server_support.py:193-277`). This is transport-safe but not lossless query paging. Beads `polylogue-t46.8.2.1` and `polylogue-z9gh.9.1` already own removal/compatibility and physical paging respectively. + +### Browser capture / status + +Browser capture is a pre-normalization acquisition surface. Its session model correctly uses `Provider` and `provider_session_id`; default `capture_id` is provider-native (`polylogue/browser_capture/models.py:112-181`). Archive state later carries both raw/native provenance and an optional indexed archive session id (`:209-228`). Forcing public `Origin` into this receiver would erase useful acquisition truth. + +Receiver auth is exact-origin plus optional bearer token, with preflight origin-only because CORS preflights cannot carry Authorization (`polylogue/browser_capture/server.py:197-230`). Receiver status includes local spool information; daemon public status removes it (`polylogue/daemon/status.py:419-444`). Those are legitimate disclosure differences. The receiver’s general errors use `BrowserCaptureErrorPayload`, while capture-job errors emit a nested `{error:{code,details}}` object without the shared `ok/receiver/schema_version` wrapper (`polylogue/browser_capture/server.py:193-195`, `:520-525`); no versioned contract exception was found. + +## Ranked findings + +### F1 — The declared read contract is not the production read transaction + +**Observed.** `SessionQuerySpec` owns selection and `SearchEnvelope` documents uniform totals/cursors, but CLI, Python, daemon split mode, MCP canonical helpers, and MCP legacy helpers independently execute, count, page, and assemble results. + +**Source-supported inference.** A consumer can receive different `total`, continuation, ranking metadata, and even filter semantics for the same logical request solely because an execution path changed. The daemon fast path and CLI daemon/direct discriminator make this path switch routine rather than theoretical. + +**Decision.** Implement the existing `polylogue-z9gh.9/.1` shared query transaction beneath surface adapters. Do not replace the query algebra or payload models; make them inputs/outputs of one bounded execution/page boundary. + +### F2 — The daemon split-archive route is a concrete semantic fork + +**Observed.** `_do_archive_list_sessions()` manually mirrors a subset of `SessionQuerySpec`. `daemon-query-field-risk.csv` records each field. Search totals are page length, lane is hardcoded, and continuation is absent. + +**Source-supported inference.** Tests with only three matches cannot detect a page-total defect. The source-inspection test only checks that the bounded SQL helper appears, not that every spec field reaches the same storage predicates. + +**Decision.** `polylogue-4p1.1` is the immediate contained repair: construct the spec once from the route parameters, use canonical execution/envelope code, and delete the manual mirror block. This should land before the broader transaction work because it removes an active semantic fork and provides the first field-inventory differential test. + +### F3 — Totals and pagination mean different things across adapters + +**Observed.** Canonical `SearchEnvelope.total` is documented as the complete matching-session count. Daemon split search, CLI direct list/search, and legacy MCP search use page length. CLI daemon search discards continuation. MCP post-serialization budget replacement can erase all result rows and substitute a narrower replay call. + +**Decision.** One page result must name both logical `total` and emitted `page_count` when both are needed. Exactness must be explicit (`exact`, `estimate`, `capped`, `sampled`). Cursor state must preserve the canonical expression, filters, projection, order, snapshot/epoch, and query identity. A response budget may choose a smaller physical page but must not redefine the logical result. + +### F4 — `OperationSpec` is declaration metadata, not current mutation authority + +**Observed.** Runtime catalog users resolve metadata for artifact/scenario/verification purposes. CLI, daemon, MCP, and API mutation handlers call concrete implementations directly. `operation_contract.py` explicitly states that generic operation bases were removed because only import had a production consumer and nothing dispatches on `.kind`. + +**Decision.** Follow `polylogue-t46.9` and `polylogue-kwsb.2`, but bind only real operations. Introduce executable handler/capability/preview/receipt bindings around existing mutation implementations and the existing maintenance target catalog. Do not resurrect a speculative polymorphic base for operation kinds that still have no handler. + +### F5 — Daemon ingest advertises an unregistered status URL + +**Observed.** `POST /api/ingest` emits `/api/operations/{id}`. The route registry exposes `/api/maintenance/status/:id`; no `/api/operations/:id` registration or dispatch was found. + +**Unresolved.** The snapshot does not establish that ingest operation ids are present in the maintenance registry. Therefore changing the link to `/api/maintenance/status/{id}` without proving registry ownership could replace a broken URL with a semantic mismatch. + +**Decision.** Add an endpoint-contract test that schedules a stub ingest operation and follows the returned URL through actual route dispatch and registry lookup. Then either register a generic operation-status route backed by the ingest registry or emit the existing route that truly owns that id. + +### F6 — Identity and origin/provider semantics are already substantially aligned + +**Observed.** Public archive payloads and filters use `origin`; normalized ids use `origin:native_id`; provider/native ids remain parser, raw, capture, and topology provenance. Daemon archive rows intentionally omit `provider`. Browser capture remains provider-native before normalization. + +**Decision.** Preserve this division. Continue `polylogue-2qx/.1` for generated declaration/drift control, not a runtime vocabulary rewrite. Test that public normalized rows never regress to `provider`, and that ImportExplain/browser capture never lose provider/material/capture-mode provenance. + +### F7 — Absence/error/provenance are typed in important places but not governed end to end + +**Observed.** Query miss, degraded route state, target refs, exactness, and safe receiver error models exist. Different surfaces still construct empty/degraded/error envelopes manually; capture-job errors have a separate shape; several numeric and status facts do not yet carry independent authority, coverage, freshness, and absence axes. + +**Decision.** Use `polylogue-cuxz.2/.3` to govern fact families, not to wrap every scalar indiscriminately. Parity tests must distinguish known zero from unknown/unavailable, exact census from estimate/sample, stale from absent, and redacted from missing. + +### F8 — Status needs one fact assembly and multiple projections + +**Observed.** `DaemonStatus` is intended for all surfaces, but `polylogue/daemon/status.py` and `polylogue/cli/commands/status.py` each perform substantial archive probing and readiness assembly. Browser public status legitimately removes local paths. + +**Decision.** Implement `polylogue-703`: one substrate status snapshot/fact assembly, then CLI, daemon, MCP, web, and browser projections with explicit disclosure policies. Do not force all surfaces to expose the same fields. + +### F9 — Storage/API twins amplify drift but should be sequenced after semantic tests + +**Observed.** The Python facade exposes overlapping old/new query methods; an async repository/backend and the sync split-tier store coexist. Bead `polylogue-hiu` has already selected sync core plus async adapter. + +**Decision.** First land differential semantic tests and the shared query transaction; then execute `polylogue-hiu`, generate the operation-level surface matrix under `polylogue-s1kr`, and only then decompose the broad facade under `polylogue-1fp`. File splitting before semantic convergence would redistribute drift rather than remove it. + +## Real divergence versus legitimate presentation differences + +| Case | Classification | Reason | +| --- | --- | --- | +| `word_count` in daemon web rows versus `words` in legacy CLI rows | Legitimate compatibility projection | Same underlying fact; CLI adapter normalizes the stable wire shape | +| Browser receiver exposes spool path; daemon public status redacts it | Legitimate disclosure projection | Same readiness fact under different privacy boundary | +| CLI text/YAML/JSON, daemon JSON, MCP serialized model | Legitimate render difference | Allowed if logical ids/order/totals/absence/provenance remain equal | +| Browser `provider` before normalization versus archive `origin` | Legitimate lifecycle vocabulary | Different coordinates with an explicit bridge | +| Daemon split route omits filters and hardcodes lane | Real semantic divergence | Selection and ranking change with route implementation | +| Page length used as `total` | Real semantic divergence | Contradicts the canonical complete-match count contract | +| Cursor removed by CLI daemon adapter | Real semantic divergence | A successful logical result becomes non-resumable | +| MCP full serialization followed by metadata-only replacement | Real execution/transport mismatch | Payload size is checked only after full serialization, and rows can be erased | +| OperationSpec safety guards not consumed by runtime mutation dispatch | Real authority gap | Declaration and enforcement can drift independently | +| Ingest follow-up URL has no registered matching route | Real route-contract defect | Client cannot follow the advertised lookup identifier | +| CLI and daemon independently compute status facts | Real duplication | Same archive state can produce conflicting semantic claims | +| Browser capture-job nested error object | Real envelope divergence unless versioned | Same receiver has two incompatible error shapes without documented split | + +## Priority and ownership + +1. **Remove the active daemon query fork** — `polylogue-4p1.1`. +2. **Land bounded, resumable query execution across surfaces** — `polylogue-z9gh.1`, `polylogue-z9gh.9`, `polylogue-z9gh.9.1`, with parity laws in `polylogue-yeq.3` and read algebra in `polylogue-4p1`. +3. **Converge MCP tools and physical paging** — `polylogue-t46.8`, `polylogue-t46.8.2.1`, under the same query transaction. +4. **Make real mutations executable from declared policy** — `polylogue-t46.9`, `polylogue-kwsb.2`, and `polylogue-71ey`; include the ingest follow-up repair. +5. **Converge status facts** — `polylogue-703`. +6. **Collapse implementation twins and govern the public matrix** — `polylogue-hiu`, `polylogue-s1kr`, then `polylogue-1fp`. +7. **Continue identity/evidence declarations and browser delivery** — `polylogue-2qx/.1`, `polylogue-cuxz.2/.3`, `polylogue-06zm`, `polylogue-ptx`, `polylogue-gnie`, `polylogue-jlme.5`. + +Detailed acceptance criteria and falsification checks are in `NEXT-ACTIONS.md`. Source and test evidence are in `EVIDENCE.md`. The machine-readable cross-surface map is `surface-semantic-matrix.csv`; the daemon field inventory is `daemon-query-field-risk.csv`. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/daemon-query-field-risk.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/daemon-query-field-risk.csv new file mode 100644 index 000000000..23987d17d --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/daemon-query-field-risk.csv @@ -0,0 +1,46 @@ +session_query_spec_field,public_input,canonical_semantics,daemon_split_fast_path,classification,minimum_differential_test,owning_bead +query_terms,query,FTS/ranked search terms,supported via compiled query,aligned subset,seed > limit and compare hits/order/total,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +contains_terms,contains,"literal content term, not DSL",supported by literal append to FTS terms,aligned subset,value with spaces/field-like token,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +exclude_text_terms,exclude_text / DSL exclusion,negative text predicate,compiled spec value not forwarded to ArchiveStore search,dropped,positive and excluded term in same rows,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +retrieval_lane,retrieval_lane,planner lane request,not consumed; search response hardcodes dialogue,dropped/changed,actions/hybrid request and lane/order comparison,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +referenced_path,referenced_path,path-evidence filter,manually forwarded,manual parity risk,multiple paths and exclusions,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +cwd_prefix,cwd_prefix,working-directory prefix,manually forwarded,manual parity risk,nested cwd prefixes,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +action_terms,action,action-type filter,manually forwarded,manual parity risk,single and multiple action types,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +excluded_action_terms,exclude_action,negative action filter,manually forwarded,manual parity risk,include/exclude interaction,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +action_sequence,action_sequence,ordered action sequence,manually forwarded,manual parity risk,sequence versus unordered rows,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +action_text_terms,action_text,action content filter,manually forwarded,manual parity risk,matching tool payload/text,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +tool_terms,tool,tool-name filter,manually forwarded,manual parity risk,canonicalized tool aliases,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +excluded_tool_terms,exclude_tool,negative tool filter,manually forwarded,manual parity risk,include/exclude tool interaction,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +origins,origin / DSL origin,public source-origin filter,merged from DSL and HTTP,manual parity risk,multiple origins and aliases,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +excluded_origins,exclude_origin,negative public origin filter,merged from DSL and HTTP,manual parity risk,included and excluded origins,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +repo_names,repo,repository filter,manually forwarded,manual parity risk,multiple repositories,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +project_refs,DSL `project:`,project reference filter,not in _filter_kw,dropped,"same repo, different projects",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +tags,tag,user tag filter,merged from DSL and HTTP,manual parity risk,multiple tags,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +excluded_tags,exclude_tag,negative tag filter,merged from DSL and HTTP,manual parity risk,include/exclude tag interaction,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +has_types,has_type,content-type filter,manually forwarded,manual parity risk,mixed type sessions,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +title,title,title predicate,manually forwarded,manual parity risk,case/substring semantics,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +session_id,conv_id / DSL id,resolve exact/prefix session identity,only spec produced from query expression is used; route-built conv_id mapping is discarded,partial/silent ignore risk,"standalone conv_id, DSL id, missing, ambiguous",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +since,since,lower date bound,manually parsed and forwarded as ms,manual parity risk,relative/absolute boundary rows,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +until,until,upper date bound,manually parsed and forwarded as ms,manual parity risk,inclusive/exclusive boundary rows,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +latest,latest,select latest and alter plan limit/sort,not consumed,dropped,latest versus ordinary order,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +sort,sort,declared sort field,not consumed by fast path,dropped/changed,date/messages/words/random where valid,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +reverse,reverse,reverse declared order,not consumed,dropped/changed,same sort forward/reverse,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +limit,limit,logical page size subject to shared clamp,externally applied,supported,more than two pages,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +sample,sample,sample result semantics/exactness,not consumed,dropped,deterministic seeded sample policy,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +filter_has_tool_use,filter_has_tool_use / has_tool_use route alias,stats pushdown boolean,manual DSL/route alias merge,partial/manual alias risk,true/false and absent,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +filter_has_thinking,filter_has_thinking / has_thinking route alias,stats pushdown boolean,manual DSL/route alias merge,partial/manual alias risk,true/false and absent,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +filter_has_paste,filter_has_paste / has_paste_evidence route alias,paste-evidence boolean,manual DSL/route alias merge,partial/manual alias risk,true/false and absent,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +typed_only,typed_only,typed-message/session constraint,not forwarded,dropped,typed and untyped sessions,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +min_messages,min_messages,inclusive lower count bound; zero is meaningful,forwarded but zero erased by `or ... or None`,changed for zero,"0, 1, exact boundary",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +max_messages,max_messages,inclusive upper count bound; zero is meaningful,forwarded but zero erased by `or ... or None`,changed for zero,"0, 1, exact boundary",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +min_words,min_words,inclusive lower word bound; zero is meaningful,forwarded but zero erased by `or ... or None`,changed for zero,"0, 1, exact boundary",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +max_words,max_words,inclusive upper word bound; zero is meaningful,forwarded but zero erased by `or ... or None`,changed for zero,"0, 1, exact boundary",polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +similar_text,similar_text,semantic/vector selection,route-built value not consumed; fast path lexical only,dropped,embedding-ready and not-ready cases,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +similar_session_id,similar_session_id,semantic neighbors of session,route-built value not consumed,dropped,known session and missing session,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +since_session_id,since_session_id,relative position/time boundary from another session,not consumed,dropped,anchor session boundary,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +message_type,message_type,message-type predicate,not forwarded,dropped,mixed message types,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +offset,offset,page offset,externally applied to ArchiveStore,supported but no stable cursor,page walk under insertion,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +cursor,cursor,opaque keyset continuation,read by route builder but raw params path never applies canonical cursor,dropped,second page from returned cursor,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +boolean_predicate,DSL boolean expression,structured predicate tree,compiled but not passed to ArchiveStore; flattened terms/selected fields only,dropped/changed,AND/OR/NOT grouping that differs from flattening,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +with_units,DSL `with `,post-selection unit projection,not attached by either /api/sessions execution path,route-wide projection gap,request attached actions/messages and compare surfaces,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 +with_unit_fields,DSL unit field allowlists,post-selection field projection,not attached,route-wide projection gap,field allowlist versus default payload,polylogue-4p1.1;polylogue-yeq.3;polylogue-z9gh.9.1 diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/surface-semantic-matrix.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/surface-semantic-matrix.csv new file mode 100644 index 000000000..3de26bce3 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-04/a01/extracted/surface-semantic-matrix.csv @@ -0,0 +1,27 @@ +concept,surface,entry_point,actual_call_path,semantic_owner,projection,origin_provider_vocabulary,identity,absence_error_provenance,pagination,authorization,classification,observed_divergence_or_rationale,owning_beads +session list,CLI daemon mode,cli/archive_query.py:_try_emit_daemon_session_page,CLI request -> daemon /api/sessions -> CLI normalizer/envelope,SessionQuerySpec + archive store; missing shared transaction,legacy CLI row fields/text/json/yaml,origin,archive session id/TargetRef,daemon route_state/diagnostics partially retained,offset; list next_offset derived; cursor discarded,local client to daemon credential policy,divergent,path-dependent totals and no cursor,4p1.1;z9gh.9.1;yeq.3 +session list,CLI direct mode,cli/archive_query.py direct dispatch/_emit_list,compiled spec -> local ArchiveStore.list_summaries -> local envelope,SessionQuerySpec/ArchiveStore,legacy CLI row fields/text/json/yaml,origin,archive session id,empty list success,local cursor; total is page length,OS/process authority,divergent,total means page count,z9gh.9.1;yeq.3 +ranked search,CLI daemon mode,cli/archive_query.py:_emit_daemon_search_payload,daemon SearchEnvelope-like JSON -> CLI envelope,canonical query intent; daemon execution path varies,CLI items envelope,origin,session and match refs when supplied,degraded payload handled separately,next_offset and next_cursor forced null,OS/process plus daemon auth,divergent,successful page becomes non-resumable,4p1.1;z9gh.9.1 +ranked search,CLI direct mode,cli/archive_query.py:_emit_search,local ArchiveStore search -> hit hydration -> local envelope,SessionQuerySpec/ArchiveStore,CLI items rather than SearchEnvelope.hits,origin,archive refs in hit payload,no-result exit behavior,cursor supported; total is page length,OS/process authority,divergent,logical total contract not preserved,z9gh.9.1;yeq.3 +session list/search,Python API,api/archive.py list/search families,Polylogue method -> direct ArchiveStore or spec methods,domain/store; facade is adapter,typed domain objects or SearchEnvelope,mostly origin; legacy search keyword named source,archive ids,None/empty varies by method; typed diagnostics in envelope,several independent limit/offset/cursor paths,caller process authority,duplicate,overlapping methods independently execute/count,hiu;s1kr;1fp;z9gh.9.1 +ranked search,Python API,api/archive.py:search_envelope,kwargs + expression -> spec -> direct fetch/count -> build_search_envelope,SessionQuerySpec + shared payload builder; missing shared transaction,typed SearchEnvelope,origin,target refs in hits,typed invalid cursor/diagnostics,cursor logic duplicated from shared spec builder,caller process authority,partial,uses shared shape but duplicates execution orchestration,z9gh.9.1;1fp +session query,Repository,storage/repository SessionRepository,async repository -> separate async SQLite backend,"legacy storage twin, not canonical by name",domain records,legacy source aliases coexist,archive ids/domain records,repository-specific Optional/list behavior,separate async paging,caller process authority,duplicate,parallel storage/query implementation,hiu +session list/search,Daemon canonical mode,daemon/http.py:_do_list/_do_search_list,route params -> SessionQuerySpec -> filter/spec count or shared envelope builder,SessionQuerySpec + domain/store,web JSON rows/SearchEnvelope,origin,TargetRef/anchors,route_state + diagnostics,offset/cursor via canonical builder for search,credential policy for reads,mostly aligned,list and search still not under one transaction,z9gh.9.1 +session list/search,Daemon split mode,daemon/http.py:_do_archive_list_sessions,raw params -> partial expression compile/manual filter map -> ArchiveStore -> manual JSON,manual route code competing with SessionQuerySpec,web JSON rows/manual search envelope,origin,TargetRef/anchors,manual degraded/no-results envelopes,offset only; total page length for search; no cursor,credential policy for reads,divergent,"filter omissions, hardcoded lane, total/cursor drift",4p1.1;z9gh.9.1;yeq.3 +session list/search,MCP canonical,mcp/server_tools.py search/list_sessions,typed MCP request -> SessionQuerySpec -> ArchiveStore -> archive_support payload,SessionQuerySpec plus archive_support; missing shared transaction,typed MCP JSON,origin,TargetRef/evidence payloads,typed MCP error wrapper/query diagnostics,own list/search orchestration; budget after serialization,process MCP role read,partial,canonical request/payload but duplicate execution/page,z9gh.9.1;t46.8 +session list/search,MCP legacy,mcp/server_tools.py archive_list_sessions/archive_search_sessions,legacy tool -> Polylogue archive_* facade -> legacy payload,facade/store path,legacy MCP tool JSON,origin,archive ids,legacy empty/error behavior,limited offset; search total page length/no cursor,process MCP role read,duplicate,second tool and semantics owner,t46.8.2.1;z9gh.9.1 +terminal query units,MCP,mcp/server_tools.py:query_units,expression -> QueryExecutionContext -> execute_archive_read -> unit payload,shared execution-control types,MCP JSON page,origin,unit target refs,typed query error,bounded executor path; still must converge with all reads,process MCP role read,aligned-direction,best existing model for shared read transaction,z9gh.1;z9gh.9.1 +normalized identity,CLI/Python/daemon/MCP,shared payload/domain models,normalized store rows -> TargetRef/anchors,identity law + generated storage columns,surface-specific rendering,origin,session origin:native; message/block beneath,native ids preserved as provenance,not applicable,surface-specific,aligned,no material divergence found,2qx;2qx.1 +capture identity,Browser receiver,browser_capture/models.py,extension payload -> BrowserCaptureEnvelope -> spool/raw ingest,capture schema before normalization,receiver JSON,provider,provider_session_id and provider-based capture_id; indexed_session_id later,raw/provider/capture provenance explicit,job/checkpoint paging is receiver-specific,allowed origin + optional token,legitimate,must not be collapsed into public Origin too early,06zm;gnie;jlme.5 +delete session,CLI,cli/archive_query.py:_emit_delete,matched ids -> local preview/prompt -> ArchiveStore.delete_sessions,concrete store mechanics; no shared mutation transaction,MutationResultPayload,origin only in selection,archive session ids,preview/aborted/deleted local outcomes,not applicable,OS/process + interactive/force guard,divergent-authority,surface owns confirmation and execute,t46.9;kwsb.2 +delete session,Python API,api/archive.py:delete_session_safe,resolve id -> ArchiveStore.delete_sessions -> DeleteSessionResult,concrete store mechanics,typed result,not applicable,resolved archive id,deleted/not_found,not applicable,caller process authority,divergent-authority,no shared preview/capability/receipt,t46.9;kwsb.2 +mutation/maintenance,Daemon HTTP,daemon/http.py ingest/reset/maintenance handlers,HTTP auth/write gate -> direct concrete implementation,concrete import/maintenance/store code,HTTP ack/envelope,origin/provider depends operation,operation id/target refs,typed ack partly; follow-up defect,operation polling route,machine auth + same-origin + write gate,partial,"good edge auth, fragmented domain authority",t46.9;kwsb.2;71ey +mutation/maintenance,MCP,MCP mutation/maintenance tools,MCP role guard -> facade/planner concrete method,concrete mechanics; OperationSpec metadata not dispatcher,MCP JSON,operation-specific,archive/operation refs,tool-specific result/error,not applicable,MCP write/admin role,partial,edge role is valid; handler/preview/receipt fragmented,t46.9;kwsb.2;71ey +operation declaration,Repository/operations,operations/specs.py,catalog -> artifact/scenario/verification metadata,OperationSpec declaration only,metadata/catalog JSON,not applicable,operation name,"effects/guards declared, not enforced",not applicable,none at catalog resolution,descriptive,no production .kind dispatch or handler authority,t46.9 +operation status follow-up,Daemon HTTP,POST /api/ingest ack,ImportAck -> OperationFollowUp /api/operations/{id},actual ingest registry unresolved,HTTP URL,not applicable,operation id,pending status,poll_after_ms but URL unregistered,machine mutation auth; status read credential policy,defect,advertised endpoint not registered,t46.9 +status facts,Daemon,daemon/status.py,collectors -> DaemonStatus -> public payload,daemon status assembly,full/public JSON with redaction,origin where source facts are normalized,component/run/archive refs,many readiness/degraded fields,"snapshot-like, not result paging",credential/public route policy,duplicate,same facts also assembled by CLI,703 +status facts,CLI,cli/commands/status.py,direct DB/filesystem probes or daemon payload -> CLI compact/full render,parallel CLI status assembly,text/JSON compact/full,mixed underlying source facts,archive/component identity,ad hoc unavailable/degraded handling,not applicable,OS/process or daemon client,duplicate,can disagree with daemon facts/frame,703 +receiver status,Browser local,browser_capture receiver /v1/status,receiver config/spool probe -> BrowserCaptureReceiverStatusPayload,browser receiver,local receiver JSON,provider not central,stable receiver_id,spool readiness and path,not applicable,allowed origin + optional token,legitimate,local path exposure is within receiver boundary,jlme.5;gnie +receiver status,Daemon public projection,daemon/status.py:browser_capture_status_public_payload,receiver status -> remove spool/artifact paths -> daemon status,same receiver facts,privacy-safe daemon JSON,not applicable,receiver/component identity,redacted distinguished by projection policy,not applicable,daemon read auth,legitimate,"field omission is disclosure, not semantic recomputation",703;jlme.5 +receiver errors,Browser normal routes,server.py:_safe_error,exception/validation -> BrowserCaptureErrorPayload,receiver protocol,flat safe error JSON,not applicable,receiver/schema identity,ok=false + error; no paths/stack,not applicable,origin/token edge,aligned,stable safe wrapper,jlme.5 +capture-job errors,Browser capture jobs,server.py:_capture_job_error,CaptureJobError -> nested error dict,capture-job handler local code,nested error JSON,not applicable,no receiver/schema identity,code/details only,not applicable,origin/token edge,divergent,incompatible envelope in same receiver,06zm;jlme.5 diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/AUTHORITY-CENSUS.csv b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/AUTHORITY-CENSUS.csv new file mode 100644 index 000000000..7d17841f7 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/AUTHORITY-CENSUS.csv @@ -0,0 +1,11 @@ +rank,concept,classification,current_authority,competing_copy_or_projection,observed_effect,decision,tracker +1,query_units grammar and recipes,unresolved collision,archive/query parser and shared query-unit request builder,MCP prompt literal recipes,"two real prompt recipes return invalid_query",generate and execute recipes from query declarations,polylogue-z9gh.3 +2,maintenance target semantics,unresolved collision,maintenance target catalog for identity,"replay dispatch plus preview/repair handler maps and split orchestrators","7 advertised; replay 6; planner 7; CLI failed exit 0",one target execution declaration consumed by all surfaces,polylogue-71ey +3,configuration effective values,unresolved collision,no single complete authority,"inventory defaults coercion sets legacy Config direct env reads nested decoders","partial nested override deletes inherited siblings; 16 known-var bypass reads",typed key declaration plus one resolver and derived legacy adapter,polylogue-9gh1 +4,embeddings schema and writers,unresolved collision,archive-tier embeddings DDL by repository doctrine,runtime sqlite-vec DDL and legacy writer,"create order determines origin vs source_name and breaks the other writer",canonical parameterized tier DDL and one writer contract,polylogue-mhx.7 +5,MCP tool surface,unresolved multi-authority currently aligned,runtime registration,"expected names contract map minimal invocation map test-sourced docs manual count","104 sets align; docs arrow is reversed; instructions say 103",production MCPToolSpec generates all projections,"polylogue-o21; polylogue-t46.8.1" +6,daemon routes and auth,projection/execution split currently parity-guarded,HTTP dispatcher for execution,RouteContract and OpenAPI metadata,"77 paths align; POST/DELETE do not consume auth_policy",RouteSpec drives router auth and OpenAPI,polylogue-3utv +7,Python facade routing status,unresolved validation mirror,actual Polylogue methods and store calls,static facade route catalog,"141 catalog entries vs 146 public callables; 5 unclassified",semantic OperationSpec plus explicit exclusions,"polylogue-s1kr; polylogue-9e5.31" +8,devtools commands,unresolved bypass,CommandSpec dispatcher for registered commands,three direct workflow/hook module or script calls,"catalog cannot claim complete capability inventory",route calls through catalog or sanctioned bypass manifest,polylogue-l8ee +9,durable user schema,compatibility copies plus incomplete projection,fresh USER_DDL for current schema,numbered migrations plus hand required-table list,"fresh 15 tables; test subset 13 omits two",retain migrations and derive exact fresh-versus-upgraded manifest,polylogue-ihp0 +10,user_settings capability,dead declared capability and compatibility schema copy,current tracker decision to keep and wire,"fresh DDL migration status docs tests","no runtime helper reader writer or consumer",typed key registry storage twins and first cost consumer,polylogue-at44 diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/DECISIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/DECISIONS.md new file mode 100644 index 000000000..d236f8083 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/DECISIONS.md @@ -0,0 +1,156 @@ +# Consolidation decisions and deletion gates + +## Decision rules + +A production authority is the declaration or implementation that the real consumer executes. Generated projections may remain separate files when their arrow points from that authority and a drift check proves regeneration. Compatibility copies remain when they preserve supported historical state. A validation list is not allowed to become a third product model. + +The following decisions are scoped to existing owning areas and tracker work. They do not introduce a parallel architecture. + +## D01 — Generate and execute query recipes from query declarations + +**Decision:** Adopt the executable query parser/declaration model as canonical for grammar, terminal result semantics, fields, examples, completions, errors, prompt recipes, and docs. Preserve the six cookbook intents, but remove handwritten executable expressions from prompt implementations. + +**Immediate repair:** Replace the two sessions-only recipes with valid terminal forms. The precise form should be selected from the existing supported grammar, such as a sessions scope followed by a terminal action/file source, without inventing a second parser rule merely to accept the stale text. + +**Delete only when:** + +1. Every shipped `query_units(expression=...)` recipe is represented as structured declaration data or generated text. +2. Registration/build validates every recipe with `parse_unit_source_expression`. +3. The generated prompt text is byte-stable or intentionally versioned. +4. The literal executable recipes in `server_prompts.py` and any available installed skill source are removed. + +**Real-consumer proof:** Build the real MCP server, render every prompt, extract each `query_units` call, invoke the registered tool against a fresh bounded archive, and assert the declared result semantic class, continuation metadata, and non-error envelope. Mutate one recipe to sessions-only syntax and prove the gate fails. + +**Falsification evidence:** A recipe that compiles but returns a different unit, silently truncates an exhaustive intent, or only passes a parser mock means the declaration is insufficient. + +## D02 — Make maintenance target declarations own all execution paths + +**Decision:** Extend the maintenance target declaration—or a single adjacent execution registry keyed by its spec—to own preview handler, execute handler, destructive policy, resumability capability, invalidation keys, and surface visibility. Both `execute_replay` and `execute_backfill` consume that same dispatch. + +A target may be explicitly unsupported for resumable execution only if the declaration says so and every surface presents that capability state before invocation. The current implicit omission is not acceptable because the same target already executes through another path. + +**Delete only when:** + +1. Catalog names, preview handlers, execute handlers, and replay-capable handlers are equality-checked from one declaration. +2. `superseded_raw_snapshots` runs through the unified path. +3. CLI, MCP, and HTTP choose the same target set and targetless semantics. +4. Failed CLI operations exit nonzero in both plain and JSON modes while preserving the operation envelope. +5. `_REPLAY_DISPATCH`, `_PREVIEW_HANDLERS`, and `_REPAIR_HANDLERS` no longer independently enumerate names. + +**Real-consumer proof:** On a freshly initialized archive, iterate every declared target through CLI, MCP, and HTTP dry-run routes, then execute safe fixtures for destructive targets. Assert identical target identity, status, failure shape, and resumability claim. Kill a run after a checkpoint and resume through the same declaration. + +**Falsification evidence:** A target can be added to help text without executing, a surface still chooses a different orchestrator, or JSON failure still exits zero. + +## D03 — One typed config declaration and one effective-value resolver + +**Decision:** Evolve the current config inventory into a typed `ConfigSpec`-style declaration per key. It must own default, value type/coercer, TOML path, environment variable, CLI override, secrecy, reload behavior, merge strategy, and provenance behavior. `load_polylogue_config` is the effective-value authority. Legacy `Config` remains temporarily as a derived runtime adapter, not an independent resolver. + +Nested objects require explicit merge semantics. Health threshold maps should deep-merge by declared key/family unless a declaration explicitly marks full replacement. + +**Delete only when:** + +1. The partial nested override preserves inherited sibling keys and records winning provenance at the leaf level or an equally precise explain form. +2. Every direct read of an inventoried environment variable is migrated to an injected resolved config, or is placed in a reviewed runtime-only bypass manifest with a reason. +3. The legacy `get_config` path derives from the layered resolver and no longer calls separate path/env authorities. +4. Type coercion sets and default maps are generated from the typed declaration. +5. All existing CLI, daemon, MCP, ingest, and theme consumers pass composition tests. + +**Real-consumer proof:** For each key class, invoke at least one actual consumer under default, site TOML, user TOML, env, and CLI layers. For nested thresholds, run daemon alert evaluation, not only loader assertions. For secrets, prove inspection output redacts values while the consumer receives them. + +**Falsification evidence:** A consumer reads `os.environ` directly for an inventoried key, a nested override deletes an unrelated value, or an explain command reports a layer different from the value actually used. + +## D04 — Archive-tier embeddings DDL is the only table-creation authority + +**Decision:** `polylogue.storage.sqlite.archive_tiers.embeddings` owns the current embeddings schema. If runtime-selectable dimension remains required, expose a canonical DDL builder parameterized by dimension and make both fresh initialization and provider setup consume it. Choose one public metadata name—current archive-tier `origin` is consistent with repository vocabulary—and migrate all writers/readers to it. + +**Delete only when:** + +1. `SqliteVecRuntimeMixin._ensure_tables` no longer contains an independent `CREATE TABLE` or `CREATE VIRTUAL TABLE` definition. +2. Legacy provider writes and archive-tier writes use one column contract and one metadata/status shape. +3. Fresh initialization in either call order produces identical `sqlite_master`, PRAGMA column metadata, indexes, and strictness. +4. Existing derived-tier data receives an explicit rebuild gate rather than an in-place hidden shape mutation. + +**Real-consumer proof:** Create databases in both historical orders, run both writer APIs, run similarity lookup and materialization/status consumers, then compare schema fingerprints and row semantics. Mutate the canonical auxiliary column and prove both writers’ tests fail until regenerated/migrated together. + +**Falsification evidence:** `IF NOT EXISTS` still lets a first writer win, either writer needs a compatibility-only column not represented in the declaration, or a dimension change silently drops only one table without rebuilding dependent metadata. + +## D05 — Declare MCP tools once in production code + +**Decision:** Pilot the existing declaration work with an `MCPToolSpec` that owns tool name, role visibility, callable, input schema, result contract, minimal smoke invocation, destructive/confirmation semantics, and docs text. Runtime registration, expected sets, envelope tests, role matrices, and generated docs derive from it. + +**Delete only when:** + +1. `build_server(role=...)` registers from the specs. +2. `EXPECTED_TOOL_NAMES`, `TOOL_CONTRACT`, and `_KNOWN_MINIMAL` are generated views or eliminated. +3. Devtools docs no longer import from `tests/`. +4. The manual 103-tool count in repo instructions is generated or removed. +5. Every declared tool is invoked through the real server in the roles where it is visible. + +**Real-consumer proof:** Iterate specs through read/write/admin servers, invoke minimal arguments on a fresh archive, validate runtime envelopes, and test role rejection and destructive confirmation. A docs generator must read production specs, not a test mirror. + +**Falsification evidence:** A decorator registers a tool outside the specs, a spec exists without a callable, or generated docs name a tool absent from runtime. + +## D06 — Route contracts must drive dispatch, not only describe it + +**Decision:** Use the existing `RouteContract` data as the migration seed for the planned typed `RouteSpec`; do not create another route table. Before ASGI migration, bind `_StaticPostRoute` and DELETE routes to contracts and dispatch authentication from the declared policy or a policy strategy keyed by it. + +**Delete only when:** + +1. GET, POST, and DELETE route entries all carry the same production spec. +2. Router construction and OpenAPI consume the spec. +3. Hard-coded auth grouping in `do_POST`/`do_DELETE` is removed or generated. +4. Pattern census remains exact without a separately authored implemented-pattern list. + +**Real-consumer proof:** For every route spec, issue unauthenticated, wrong-origin, valid read, valid write/admin, and feature-disabled requests as applicable against the actual handler. Assert response schema and streaming mode from the spec. + +**Falsification evidence:** Route path parity passes while auth behavior diverges, or a test derives both expected route and mock handler from the same spec without making an HTTP request. + +## D07 — Status reports derive from semantic operations, with explicit exclusions + +**Decision:** Key facade/CLI parity by stable semantic operation IDs, in line with `polylogue-s1kr`. Status may project method names, but it must classify intentional exclusions such as lifecycle/builders explicitly. Do not make Python reflection the product model. + +**Delete only when:** + +1. Every public callable maps to an operation or an explicit exclusion reason. +2. `embedding_preflight` and `embedding_status` receive correct routing classifications. +3. `_ARCHIVE_FACADE_ROUTES` is generated or removed. +4. Status readiness is based on executable route checks or representative operations, not map completeness alone. + +**Real-consumer proof:** Invoke one real method per route/tier class against a temporary archive and compare the observed store/path effect with the operation declaration. Mutation of a method’s store route must fail the status proof. + +**Falsification evidence:** A generated matrix is complete by name but reports a route that the method no longer executes. + +## D08 — Close or declare devtools catalog bypasses + +**Decision:** CI and hooks should invoke `devtools` catalog paths. A bypass is acceptable only when a machine-readable declaration records why catalog dispatch is impossible or undesirable. + +**Delete only when:** + +1. The three known sites route through catalog commands or a sanctioned bypass manifest. +2. A lint scans workflow YAML, shell, and Python subprocess calls for direct `devtools.*` module/script execution. +3. The lint has a mutation witness that introduces a fourth bypass and fails. + +**Real-consumer proof:** Run the actual workflow command lines and pre-push branch through the catalog dispatcher; compare exit code, arguments, and artifacts with the former direct call. + +## D09 — Retain migrations; derive exact schema parity + +**Decision:** Keep numbered durable migrations as compatibility copies. Build a canonical schema manifest by initializing fresh DDL and introspecting tables, columns, indexes, triggers, constraints, and `user_version`. Compare every supported migrated starting version to that manifest. + +**Delete only when:** + +1. The hand-written required-table subset is replaced by exact manifest comparison. +2. Fresh and upgraded schemas match at the supported contract boundary. +3. Explicit forbidden legacy overlays remain a separate semantic denylist where useful. +4. Migration tests use the production migration runner and verified backup manifest. + +**Real-consumer proof:** Open each upgraded database through the real archive API and execute representative reads/writes for every durable capability, including holdout policies and receipts. + +**Falsification evidence:** Schema names match but constraints/indexes differ, or direct SQL tests pass while archive API operations fail. + +## D10 — Keep and wire `user_settings`; do not delete by census alone + +**Decision:** Follow `polylogue-at44`: retain the separate state table, add a typed allowed-key registry, sync and async storage twins, facade/CLI access, and a first subscription-tier cost consumer. Secrets remain outside user storage. + +**Delete only if the product decision is reversed:** A forward migration or copy-forward design must preserve supported data, fresh DDL and status/docs must be updated, and old archives must receive an explicit compatibility plan. Mere absence of a current consumer is not enough to erase durable user data design. + +**Real-consumer proof:** Set and get a typed setting through CLI/API and both storage paths, then run cost computation and show the setting changes the result while an unknown or secret key is rejected. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..284b4d805 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/EVIDENCE.md @@ -0,0 +1,331 @@ +# Evidence ledger + +## Evidence authority and provenance + +The question is defined by `05-duplicate-authority-census(1).md`. Evidence was adjudicated in this order: current source and focused execution, repository instructions, current Beads records, then the explicitly slightly stale test-suite context. + +Input provenance: + +| Input | Size | SHA-256 | +|---|---:|---| +| `00-polylogue-all.tar(14).gz` | 92,457,778 bytes | `3d0c5a2e5024632c05438dbc3a08aae1dcd31529472b1185ad5b80c0cd933e30` | +| `00-slightly-stale-context-testsuite-diet.tar(14).gz` | 934,006 bytes | `d4fa7fc31c70ff30db076e526836ffc81b8124d3f54c7add5e58d3f57c9dcbff` | +| `05-duplicate-authority-census(1).md` | 3,287 bytes | `8b96d48996e1dc34d2b5e482a65243a475fe074f8fb2bf40f5ee19b016fb8774` | + +The snapshot manifest names `master` commit `f654480cadb7cc4c194704e24dfd483199547b35`, generated `2026-07-17T043202Z`, and reports a dirty working tree. The supplied working-tree tar contained 2,501 of 3,739 tracked paths; 1,238 omitted paths were predominantly excluded `.agent/` history. All 2,501 included tracked paths matched the Git bundle’s HEAD byte-for-byte. + +The stale context’s L27 states the relevant law precisely: a production authority should enumerate and dispatch a capability, real dispatch and schema introspection should prove it, fresh and upgraded state should agree, and a test must not create a third mirror catalog. That context was used as a search seed, not as proof that an old defect still existed. + +## Method + +The census combined source tracing, AST inventory, runtime introspection, fresh archive initialization, actual CLI and MCP invocation, and focused tests. A repeated string was counted only when separate copies could affect execution, generated output, validation, operator status, compatibility behavior, or contributor edits. + +For each candidate, the trace sought: + +- writers or declaration owners; +- generators or projections; +- validators and their proof strength; +- real consumers and dispatch routes; +- current tracker decisions and acceptance criteria; +- an exact deletion gate rather than a general preference for deduplication. + +## E01 — MCP query recipes contradict executable grammar + +**Observed facts** + +- `polylogue/mcp/server_prompts.py:456-468` emits a `query_units` recipe whose expression begins `sessions where ... exists action(...)`. +- `polylogue/mcp/server_prompts.py:470-484` emits another sessions-only recipe before a valid `files where ...` recipe. +- `polylogue/mcp/server_tools.py:268-320` calls `parse_unit_source_expression` and returns `invalid_query` when it returns `None`. +- `polylogue/archive/query/unit_results.py:160-178` enforces the same terminal-unit requirement for shared execution. +- `tests/unit/mcp/test_server_surfaces.py:1014-1024` explicitly tests rejection of a nonterminal/session expression. +- `tests/unit/mcp/test_server_surfaces.py:1394-1439` tests only that cookbook prompt text names expected tools; it does not compile or execute embedded expressions. + +**Executable witness** + +The real read-role MCP server rendered both prompts and each embedded recipe was passed back to its real `query_units` tool on a fresh archive: + +- `unacknowledged_failures`: sessions-only expression → `is_error=true`, `code=invalid_query`. +- `sessions_touching_file`: sessions-only expression → `invalid_query`. +- `sessions_touching_file`: `files where path:...` expression → successful envelope. + +A direct parser probe likewise returned `None` for both sessions-only forms and a `QueryUnitSource` for files-terminal and sessions-scope-pipeline forms. + +**Writer / validator / consumer trace** + +- Writer: literal prompt recipes in `server_prompts.py`. +- Executable authority: parser in `archive/query/expression.py` and shared request builder in `archive/query/unit_results.py`. +- Validator: prompt-name test and independent query rejection test; together they currently prove the contradiction rather than prevent it. +- Consumer: a model following the MCP prompt and invoking the registered `query_units` tool. + +**Tracker** + +`polylogue-z9gh.3` is open P0. Its current notes independently identify the same two product recipes as an active curriculum regression and require recipes to derive from executable declarations. + +**Inference** + +The parser and shared request builder are the current behavior authority. The prompt literals are stale product curriculum copies, not alternative supported syntax. + +**Unresolved evidence** + +The Bead says an installed shared skill repeats the expressions. That installed artifact was not present in the supplied evidence, so this report does not claim to have verified it. + +## E02 — Maintenance target catalog and executors disagree + +**Observed facts** + +- `polylogue/maintenance/targets.py:158-234` declares seven `MAINTENANCE_TARGET_SPECS`, including `superseded_raw_snapshots`. +- `polylogue/maintenance/replay.py:101-128` declares six `_REPLAY_DISPATCH` handlers and omits that target. +- `polylogue/maintenance/replay.py:607-618` converts the missing handler into `UnsupportedReplayTargetError`. +- `polylogue/storage/repair.py:6133-6152` has seven preview handlers and seven repair handlers, including the omitted target. +- CLI uses `execute_replay` at `polylogue/cli/commands/maintenance/_run.py:116-124`. +- MCP uses `execute_backfill` at `polylogue/mcp/server_maintenance_tools.py:134-157`. +- HTTP uses `execute_backfill` in `polylogue/daemon/http.py:4482-4499`. +- The existing coverage test at `tests/unit/maintenance/test_idempotency.py:216-224` checks only a three-target subset, so the seventh target can be missing while the test passes. + +**Executable witness** + +On a freshly initialized five-tier archive: + +- `execute_replay(... targets=('superseded_raw_snapshots',), dry_run=True)` returned status `failed` with `UnsupportedReplayTargetError`. +- `execute_backfill` with the same target returned status `completed` and the real handler result, `Would: delete 0 superseded raw snapshots`. +- The actual command `polylogue ops maintenance run --target superseded_raw_snapshots --dry-run --output-format json` emitted a failed operation envelope but exited with code `0`. + +**Writer / validator / consumer trace** + +- Identity/metadata writer: `MAINTENANCE_TARGET_SPECS`. +- Competing execution writers: `_REPLAY_DISPATCH`, `_PREVIEW_HANDLERS`, `_REPAIR_HANDLERS`. +- Validators: subset coverage test and per-path tests; no equality test through each public surface. +- Consumers: CLI resumable replay, MCP planner, HTTP planner, readiness/doctor projections. + +**Tracker** + +`polylogue-71ey` is open P1 and explicitly requires the maintenance catalog to own replay semantics. + +**Inference** + +This is not a target that lacks an implementation. The implementation exists and is reachable through another production orchestrator. The duplicate registry is the defect. + +## E03 — Config effective values have multiple authorities + +**Observed facts** + +- Legacy `Config`, `get_config`, and `IndexConfig.from_env` live at `polylogue/config.py:80-164`. +- Layered `PolylogueConfig` begins at `config.py:172`. +- `_CONFIG_INVENTORY` at `config.py:475-915` contains 50 keys and exactly matches the 50 built-in default keys. +- Type conversion is separately authored in `_INT_CONFIG_KEYS`, `_FLOAT_CONFIG_KEYS`, and `_BOOL_CONFIG_KEYS` at `config.py:917-944`. +- `load_polylogue_config` documents and implements five layers at `config.py:1105-1155`. +- `_merge_toml` assigns nested health tables wholesale at `config.py:1215-1227`. +- AST inventory found 16 direct reads of seven already-inventoried `POLYLOGUE_*` variables outside `config.py`, across 11 files. Eight are in production package files and eight in devtools. It also found 30 reads of 23 non-inventory variables, which require classification rather than automatic migration. +- Direct production examples include `polylogue/ui/theme.py:280`, `polylogue/cli/commands/status.py:76,141`, `polylogue/daemon/http.py:374`, and `polylogue/pipeline/services/ingest_batch/_core.py:1421`. +- A repository search found 37 textual `get_config()` call sites including the definition and service-hook calls; legacy configuration is still live across daemon, MCP, CLI, demo, and devtools paths. + +**Executable witness** + +A site TOML declared: + +- `warning_total = 10` +- `critical_total = 20` +- `families.index = 3` + +A user TOML overrode only `warning_total = 12`. `load_polylogue_config` resolved `health_convergence_debt` to `{'warning_total': 12}` with provenance `user`, deleting both inherited siblings. + +**Writer / validator / consumer trace** + +- Surface metadata writer: `_CONFIG_INVENTORY`. +- Default writer: `_default_config_values`. +- Type writers: three coercion sets and nested downstream decoders. +- Competing effective-value writers: legacy `Config`/paths, layered resolver, and direct environment reads. +- Consumers: CLI defaults, daemon startup and status, theme resolution, ingest schema validation, MCP services, devtools. +- Current validators establish inventory/default equality but do not enforce all consumers through the resolver or nested-key preservation. + +**Tracker** + +`polylogue-9gh1` is an open P1 epic for the documented-versus-actual five-layer config gap. The stale test-law seed specifically retained the partial-health-override sibling-loss witness; current execution confirms it remains live. + +**Inference** + +The inventory is a useful surface projection but is not yet a single authority for type, default, merge semantics, or effective runtime value. + +## E04 — Two live embeddings DDL and writer contracts are incompatible + +**Observed facts** + +- `polylogue/storage/search_providers/sqlite_vec_runtime.py:57-105` creates `message_embeddings` with auxiliary columns `source_name` and `session_id`, plus non-STRICT metadata/status tables. +- `polylogue/storage/sqlite/archive_tiers/embeddings.py:5-58` creates the same vec0 table with `session_id` and `origin`, STRICT metadata/status tables, and an additional `embedding_failures` table. +- Fresh archive initialization consumes the tier DDL through `ARCHIVE_DDL_BY_TIER` and `initialize_archive_tier` at `archive_tiers/__init__.py:14-27` and `archive_tiers/bootstrap.py:75-93`. +- Legacy provider writes `source_name` at `search_providers/sqlite_vec_queries.py:62-96`. +- Archive-tier materialization writes `origin` at `archive_tiers/embedding_write.py:107-145`. + +**Executable witness** + +With the actual sqlite-vec extension and production initializers: + +- Tier DDL first, then runtime ensure: schema contains `origin`; a legacy provider-form insert fails `OperationalError: table message_embeddings has no column named source_name`. +- Runtime ensure first, then tier initializer: schema contains `source_name`; an archive-tier insert fails `OperationalError: table message_embeddings has no column named origin`. +- `CREATE ... IF NOT EXISTS` means the second schema writer silently leaves the first shape in place. + +**Writer / validator / consumer trace** + +- Writers: runtime mixin `_ensure_tables` and archive-tier `EMBEDDINGS_DDL`. +- Competing data writers: `SqliteVecProvider` query mixin and `upsert_message_embeddings`. +- Consumers: similarity/search provider, archive embedding materialization, demo seeding, daemon status/convergence, repair and excision paths. +- No create-order parity test protects the shared table shape. + +**Tracker and doctrine** + +`polylogue-mhx.7` is open P2 and describes the same two live definitions. Repository instructions state that embeddings are a derived tier whose current schema must come from canonical DDL plus rebuild, not a parallel runtime schema writer. + +**Inference** + +The archive-tier DDL is the intended canonical current schema. A runtime dimension parameter may be required, but it must be a parameter to the same builder rather than a second schema. + +## E05 — MCP tool declaration constellation + +**Observed facts** + +Runtime introspection of `build_server` found: + +- read role: 66 tools; +- write role: 95 tools; +- admin role: 104 tools. + +The admin set exactly equals `tests/infra/mcp.py:EXPECTED_TOOL_NAMES`, and the 104-key `TOOL_CONTRACT` exactly covers it. `_KNOWN_MINIMAL` contains 66 entries and exactly covers the read role after its one explicit no-kwargs exception. + +The direction of authority is still wrong: + +- `devtools/render_mcp_tool_index.py:1-7,23-26` calls the test fixture the registered tool set and generates reader docs from it. +- `devtools/verify_docs_coverage.py:11-21` also imports the test fixture for MCP inventory. +- `CLAUDE.md:209-214` and `CLAUDE.md:478-479` tell contributors there are 103 tools and require manual updates to both expected names and contracts. The live count is 104. + +**Writer / generator / validator / consumer trace** + +- Execution writers: decorators spread across `polylogue/mcp/server_*.py`. +- Validation copies: `EXPECTED_TOOL_NAMES`, `TOOL_CONTRACT`, `_KNOWN_MINIMAL`. +- Generator: MCP docs index imports `EXPECTED_TOOL_NAMES` from tests. +- Consumers: MCP clients, role-based servers, docs readers, contributor workflow. + +**Focused proof** + +The parametrized real read-role smoke test invoked all 66 tools with minimal arguments against an empty archive and passed. Current alignment is therefore real, not inferred from set equality alone. + +**Tracker** + +`polylogue-o21` and `polylogue-t46.8.1` are open P1 declaration/algebra work. This finding should feed those existing designs rather than create another registry. + +**Inference** + +This is broad edit-cost and authority-direction debt, with one current manual documentation drift. It is not a claim that the live tool sets are presently mismatched. + +## E06 — Daemon route contracts do not own POST/DELETE dispatch + +**Observed facts** + +- `polylogue/daemon/route_contracts.py:1-7` explicitly says the module is descriptive and dispatch remains in `daemon.http`. +- Runtime introspection found 77 contract method/pattern pairs and 77 implemented pairs, with exact equality: 56 GET, 15 POST, 6 DELETE. +- GET route structs carry a `RouteContract` and builders resolve it at `daemon/http.py:115-137,235-258`. +- `_StaticPostRoute` re-declares only pattern, segments, and handler at `daemon/http.py:140-145`. +- POST route groups are authored at `daemon/http.py:353-406`; `implemented_daemon_route_patterns` re-enumerates them at `409-430`. +- `do_POST` applies auth by hard-coded route grouping at `daemon/http.py:1670-1749`; `do_DELETE` applies another hard-coded policy at `1762-1784`. +- Search of production source found no runtime read of daemon `RouteContract.auth_policy`; it is consumed by OpenAPI generation and tests. + +**Validators** + +`tests/unit/daemon/test_route_contracts.py:31-62` proves exact pattern parity and GET binding. Security tests derive route sets from contracts and exercise actual HTTP auth behavior, so the split is meaningfully guarded today. The residual defect is that changing dispatch and contract requires coordinated edits rather than one executable declaration. + +**Tracker** + +`polylogue-3utv` is the existing typed route-registry plan. Its current P3 priority is consistent with the absence of a demonstrated live mismatch. + +## E07 — Static facade routing status omits live methods + +**Observed facts** + +- `polylogue/cli/commands/status.py:298-509` hand-authors `_ARCHIVE_FACADE_ROUTES`. +- `_archive_facade_route_status` reports source `static_facade_route_catalog` at `status.py:512-532`. +- `_archive_runtime_path_status` derives `archive_routing_ready` from that map at `status.py:558-600`. +- Runtime introspection found 146 public callable attributes on `Polylogue` and 141 map entries. No stale map entry named a nonexistent method. Five public callables were absent: `embedding_preflight`, `embedding_status`, `filter`, `iter_messages`, and `open`. + +**Inference** + +Some omissions may be intentional lifecycle/query-builder exclusions, but the current map has no explicit exclusion authority. Because `close` is classified while `open` is absent and the two embedding status methods are user-visible, the status result cannot distinguish an intentional exclusion from drift. + +**Tracker** + +`polylogue-s1kr` requires semantic-operation parity rather than a fossilized method-name matrix. `polylogue-9e5.31` requires definition-to-production closure but warns against letting authored definitions certify authored registrations. + +## E08 — Devtools command catalog has three real bypasses + +**Observed facts** + +- `devtools/command_catalog.py:881-899` registers `lab policy backlog-hygiene`. +- `.github/workflows/mutation-testing.yml:52` invokes `python -m devtools.verify_mutation_freshness` directly. +- `.github/workflows/nightly-scale.yml:64` invokes `devtools/benchmark_compare_nightly.py` as a raw script. +- `devtools/pre_push_gate.py:99-109` invokes `python -m devtools.verify_backlog_hygiene` instead of its own catalog name. +- `devtools/click_dispatch.py:18-23` builds the executable command surface from `COMMAND_SPECS`. + +**Tracker** + +`polylogue-l8ee` is open P3 and names these exact three bypasses. No source evidence contradicted it. + +**Inference** + +The catalog is the execution authority for registered commands, but it is not a complete authority for devtools capabilities while direct production workflow and hook paths remain. + +## E09 — Durable user schema copies are legitimate; its validator is incomplete + +**Observed facts** + +- Repository instructions require additive numbered migrations for durable tiers and canonical DDL plus rebuild for derived tiers (`CLAUDE.md:162-173`). +- `USER_DDL` is the fresh current schema; `polylogue/storage/sqlite/migrations/user/004_user_settings.sql` and later migrations are required upgrade compatibility paths. +- A freshly initialized user tier contains 15 non-SQLite tables. +- `tests/unit/storage/test_archive_tiers_assertions.py:155-189` asserts a hand-written required subset of 13 tables and a legacy-overlay denylist. +- The fresh tables `holdout_access_receipts` and `result_set_holdout_policies` are not in the required subset. + +**Inference** + +The migrations are not dead duplicates and must remain. The hand list is an incomplete projection of current schema authority and cannot prove exact fresh-versus-upgraded convergence. + +**Tracker** + +`polylogue-ihp0` is open P2 and calls for durable-tier schema assertions derived from canonical inventory. + +## E10 — `user_settings` is declared but has no runtime consumer + +**Observed facts** + +Repository search found `user_settings` in: + +- fresh DDL at `archive_tiers/user.py:288-294`; +- migration `storage/sqlite/migrations/user/004_user_settings.sql`; +- status table probes at `cli/commands/status.py:273-279`; +- schema/internals docs; +- migration/schema/status tests. + +No production API, storage helper, reader, writer, or consumer references the table. Tests insert/select directly at the migration layer, which proves the table exists but not that a product operation uses it. + +**Tracker decision** + +`polylogue-at44` is open P3. Its current decision is explicit: keep the separate table, define typed allowed keys, wire sync and async storage helpers, and make subscription-tier cost computation the first consumer. This report preserves that decision. + +## Validated non-defect — Read-view projections + +`READ_VIEW_PROFILES`, `READ_VIEW_HANDLER_METADATA`, and `READ_VIEW_HANDLERS` each contain 11 view IDs. `polylogue/cli/read_view_registry.py:78-94` fails fast on ID drift, and `polylogue/cli/read_view_handlers.py:174-209` additionally compares duplicated metadata fields. The split exists to keep static Click option ownership import-light. It is a compatibility/import-layer projection with an executable gate, not a high-value consolidation target. + +## Focused test record + +Two focused pytest invocations completed successfully: + +1. Eleven tests passed in 3.02 seconds: maintenance subset coverage, query-unit rejection, six cookbook prompt-name cases, two route parity tests, and the read-tool minimal-kwargs census. +2. Sixty-eight tests passed in 11.72 seconds: all 66 read-role MCP tools invoked with minimal arguments, plus two fresh user-tier schema tests. + +Total: **79 focused tests passed**. + +The passing result is important but not exculpatory. The custom production-route probes demonstrated that the maintenance subset test, prompt-name test, and user required-table subset can all pass while the targeted semantic defect remains. + +## Missing evidence + +- No live deployment, real operator archive, browser session, hosted CI run, or daemon process was inspected. +- No installed out-of-repository shared skill was available. +- No full test suite was run; repository instructions explicitly prefer focused affected tests. +- Fresh-versus-every-supported-version migration parity was not executed in this iteration. +- The direct environment census classifies call sites, not intent. The 23 non-inventory variables must be adjudicated as sanctioned runtime controls or config omissions before migration. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/NEXT-ACTIONS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/NEXT-ACTIONS.md new file mode 100644 index 000000000..b7806c6cc --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/NEXT-ACTIONS.md @@ -0,0 +1,128 @@ +# Ordered next actions + +## Gate zero — Land semantic regression tests before consolidation + +These tests should be small, focused, and attached to the owning production routes. They are the deletion gates for the first implementation wave. + +1. **Query recipe execution law — owner: MCP/query.** Render every registered cookbook prompt, extract declared `query_units` recipes, compile them, and invoke the real tool on a fresh archive. Preserve the two exact failing source recipes as regression fixtures until fixed. Acceptance: no generated recipe returns `invalid_query`; valid result-unit and continuation semantics are asserted. Falsification mutation: replace one recipe with a sessions-only expression and observe failure. + +2. **Maintenance all-target surface law — owner: maintenance/storage.** Iterate every `MAINTENANCE_TARGET_SPECS` entry through the actual CLI command, MCP tool, and HTTP handler in dry-run mode. Acceptance: every advertised target is executable or explicitly declared unsupported before invocation; statuses and target identities agree; failed CLI operations exit nonzero. Falsification mutation: remove one handler registration. + +3. **Config composition law — owner: config/daemon.** Apply default, site, user, env, and CLI layers while invoking real consumers. Acceptance: a partial health-table override preserves unrelated site keys and the daemon alert decoder observes the composed value. Falsification mutation: restore wholesale nested assignment or add a direct inventoried env read. + +4. **Embeddings create-order law — owner: embeddings/storage.** Initialize in tier-first and provider-first orders, run both production writer APIs, and compare schema fingerprints. Acceptance: both orders are identical and both writers work. Falsification mutation: rename one auxiliary column in only one writer. + +## Wave one — Repair the active contradictions + +### A1. Fix and generate query recipes (`polylogue-z9gh.3`) + +Owning files: `polylogue/archive/query/expression.py`, query metadata/declaration modules, `polylogue/mcp/server_prompts.py`, `polylogue/mcp/server_tools.py`, query docs/completions. + +Implementation order: + +1. Express the six cookbook intents as structured recipe data tied to terminal result semantics. +2. Correct the two invalid sessions-only expressions using existing grammar. +3. Generate prompt text from the recipes and make registration validate compilation. +4. Add recovery guidance to `invalid_query` from the same declaration data. +5. Remove literal executable expressions only after prompt output and actual tool behavior pass. + +Acceptance: the real MCP probe that currently produces two `invalid_query` results produces successful, correctly typed envelopes; DSL and any structured-plan form lower to the same canonical plan. + +### A2. Unify maintenance dispatch (`polylogue-71ey`) + +Owning files: `polylogue/maintenance/targets.py`, `maintenance/replay.py`, `maintenance/planner.py`, `storage/repair.py`, CLI maintenance command, MCP and HTTP maintenance surfaces. + +Implementation order: + +1. Add handlers and resumability capability to the target declaration or one adjacent registry. +2. Make replay and planner consume it. +3. Normalize targetless semantics across CLI, MCP, and HTTP. +4. Route `superseded_raw_snapshots` through the unified executor. +5. Correct CLI nonzero exit behavior for failed operation envelopes. +6. Delete independent handler-name maps after the all-target surface law passes. + +Acceptance: fresh-archive dry run for all seven targets succeeds through all supported surfaces; checkpoint/resume behavior is proved for resumable targets; explicitly non-resumable targets report that capability consistently. + +### A3. Repair config composition and authority (`polylogue-9gh1`) + +Owning files: `polylogue/config.py`, `polylogue/services.py`, direct consumer files from the AST census, daemon alert decoders, CLI config/status surfaces. + +Implementation order: + +1. Add type and merge strategy to each config declaration. +2. Deep-merge nested health maps with precise provenance. +3. Make legacy `Config` derive from the resolved layered config. +4. Migrate the eight production-package direct reads of inventoried vars first, then the eight devtools reads. +5. Classify the 23 non-inventory variables as typed config, sanctioned runtime control, test-only control, or obsolete. +6. Generate defaults and coercion from the declarations and remove independent sets. + +Acceptance: the recorded site/user sibling-loss probe preserves `critical_total` and `families.index`; actual daemon, CLI, MCP, ingest, and theme consumers agree with `polylogue config` explain output. + +### A4. Canonicalize embeddings schema (`polylogue-mhx.7`) + +Owning files: `archive_tiers/embeddings.py`, `archive_tiers/embedding_write.py`, `search_providers/sqlite_vec_runtime.py`, `search_providers/sqlite_vec_queries.py`, materialization and rebuild commands. + +Implementation order: + +1. Ratify `origin` versus `source_name` and the canonical metadata/status shape. +2. Add a dimension-parameterized canonical DDL builder if required. +3. Move provider setup to the canonical initializer. +4. Migrate the legacy provider writer to the archive-tier writer contract. +5. Add an explicit derived-tier rebuild/version gate. +6. Delete runtime inline DDL after both-order proof passes. + +Acceptance: both historical initialization orders produce one schema and both writer APIs, similarity reads, status, and materialization work. + +## Wave two — Reduce broad edit cost without creating shadow models + +### B1. MCP declaration pilot (`polylogue-o21`, `polylogue-t46.8.1`) + +Start with a small but representative family containing one read tool, one write tool, one admin maintenance tool, one resource, and one prompt. Generate runtime registration, role visibility, input schema, envelope contract, minimal invocation, and docs. Then migrate all 104 tools. Remove the stale manual count from `CLAUDE.md`. + +Acceptance: no generator imports `tests.infra.mcp`; adding one tool changes one production declaration and all projections update or fail with one actionable error. + +### B2. Bind daemon POST/DELETE routes to contracts (`polylogue-3utv`) + +Before the ASGI rewrite, add the contract reference to POST/DELETE route entries and dispatch policy through it. During the rewrite, make the registry construct the router and OpenAPI directly. + +Acceptance: 77-route behavior remains byte/semantics compatible; a policy mutation fails an actual HTTP auth test; there is no separate implemented-pattern enumeration. + +### B3. Replace static facade status with semantic operations (`polylogue-s1kr`, `polylogue-9e5.31`) + +Classify all five currently missing callables. Add explicit operation IDs or exclusion reasons, especially for `embedding_preflight`, `embedding_status`, `open`, `filter`, and `iter_messages`. Generate status projections and preserve representative real-route proof. + +Acceptance: every public callable is classified, but readiness does not become a name-count claim; changing an operation’s actual store route fails a consumer test. + +### B4. Close devtools bypasses (`polylogue-l8ee`) + +Route the mutation-freshness workflow, nightly benchmark comparison, and pre-push backlog hygiene through catalog commands or declare narrow sanctioned bypasses. Add a workflow/subprocess scanner. + +Acceptance: the catalog inventory is complete modulo an explicit machine-readable allowlist; a fourth direct call fails the lint. + +## Wave three — Compatibility and dead-capability cleanup + +### C1. Derive durable schema parity (`polylogue-ihp0`) + +Generate an exact current user-schema manifest from fresh initialization, then run the production migrator from every supported version with a backup manifest and compare. Include the two currently unasserted holdout tables. + +Acceptance: fresh and upgraded schemas and real API behavior match; numbered migrations remain intact as compatibility evidence. + +### C2. Wire typed user settings (`polylogue-at44`) + +Implement typed allowed keys, sync/async helpers, facade/CLI access, and subscription-tier cost usage. Keep deployment secrets excluded. + +Acceptance: a setting changes the real cost result through both storage paths; unknown/secret keys are rejected; the table is no longer only DDL/status/docs. + +## Verification package for each pull request + +Each change should attach: + +- the focused real-consumer test command and result; +- the declaration/projection arrow changed; +- the exact old duplicate deleted or retained as compatibility data; +- a sensitivity mutation that proves the gate notices a reintroduced split; +- remaining tracker scope, with existing Bead IDs rather than a new parallel epic. + +## Expected value of a follow-up census + +A follow-up pass should concentrate on lower-ranked breadth: all table-creating helpers, generated docs sourcing, CLI operation maps, route/resource/prompt declarations, and the 23 non-inventory environment variables. Its expected value is medium: it can discover more consumers and sharpen ranks five through ten, but the executable evidence already makes the top four implementation decisions stable. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/REPORT.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/REPORT.md new file mode 100644 index 000000000..59d39512f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/analysis/results/analysis-05/a01/extracted/REPORT.md @@ -0,0 +1,56 @@ +# Definition-to-production duplicate-authority census + +**Job:** `analysis-05` +**Revision:** `r01` +**Source snapshot:** Polylogue `master` at `f654480cadb7cc4c194704e24dfd483199547b35` +**Snapshot generated:** `2026-07-17T043202Z` + +## Executive result + +The census found ten decision-relevant authority families. Four are active production collisions with executable witnesses, four are currently aligned but impose broad edit cost or leave security/status claims on a separate authority, one is a legitimate compatibility-copy family with an incomplete validator, and one is a dead declared capability whose current tracker decision is to wire rather than delete. + +The four active collisions are: + +1. Two shipped MCP cookbook prompts instruct `query_units` to execute sessions-only expressions that the production parser and tool reject as `invalid_query`. +2. The maintenance catalog advertises seven targets, the resumable replay executor dispatches six, and the non-resumable planner already executes all seven. The omitted target fails through the CLI while the CLI exits zero. +3. The five-layer config resolver replaces nested health tables instead of composing them, so a partial user override deletes inherited site thresholds. Legacy `Config` construction and direct environment reads remain parallel effective-value authorities. +4. Two live embeddings schema writers create incompatible `vec0` metadata columns. Whichever writer creates the table first makes the other production writer fail. + +The highest-value consolidation is not a generic registry framework. It is four concrete authority repairs, in this order: executable query declarations, maintenance target execution, typed config declaration/resolution, and canonical embeddings DDL. The broader `DeclarationSpec` work can then absorb already-aligned MCP, route, and API projections without creating another shadow catalog. + +## Ranked consolidation portfolio + +| Rank | Concept | Classification | Current production effect | Canonical decision | Tracker | +|---:|---|---|---|---|---| +| 1 | `query_units` grammar and shipped recipes | **Unresolved collision** | Two product-owned prompt recipes invoke the real MCP tool and receive `invalid_query`; a files-terminal recipe succeeds | Executable query declarations and parser semantics own recipes, schemas, completions, errors, and docs; generated recipes must compile and run | `polylogue-z9gh.3` P0 | +| 2 | Maintenance target identity and execution | **Unresolved collision** | Seven catalog targets, six replay handlers, seven legacy planner handlers; `superseded_raw_snapshots` fails in replay but completes in planner on a fresh archive; CLI returns exit 0 for failed JSON operation | One target execution declaration owns identity, preview, execute, resumability capability, destructive policy, and surface exposure | `polylogue-71ey` P1 | +| 3 | Config declaration, merge, and effective-value resolution | **Unresolved collision** | Partial nested TOML override drops inherited siblings; 16 direct reads of seven inventoried env vars occur outside `config.py`; legacy and layered config systems coexist | One typed key declaration owns default, type, path, env, CLI, secrecy, merge semantics, and provenance; legacy `Config` becomes a derived adapter | `polylogue-9gh1` P1 | +| 4 | Embeddings schema and vector writers | **Unresolved collision** | Tier-first schema rejects the legacy `source_name` insert; runtime-first schema rejects the archive-tier `origin` insert | Archive-tier embeddings DDL is canonical; runtime provider imports a dimension-parameterized canonical builder and uses the archive-tier writer contract | `polylogue-mhx.7` P2 | +| 5 | MCP tool registration, contracts, smoke inputs, and docs | **Unresolved multi-authority; currently aligned** | Admin runtime, expected-name fixture, and envelope contract each contain 104 tools; read smoke map has 66. Generated docs import the test fixture, and repo instructions still say 103 | A production `MCPToolSpec` owns registration, role, schema, result contract, minimal invocation, and docs projection | `polylogue-o21`, `polylogue-t46.8.1` P1 | +| 6 | Daemon routes, auth policy, dispatch, and OpenAPI | **Projection/execution split; currently parity-guarded** | 77 contract patterns equal 77 implemented patterns. GET dispatch consumes contracts; POST/DELETE dispatch re-declares route groups and does not read `auth_policy` | Typed `RouteSpec` drives dispatch and OpenAPI. Bind POST/DELETE entries to contracts before the planned ASGI migration | `polylogue-3utv` P3 | +| 7 | Python facade routing status | **Unresolved validation mirror** | Static status map has 141 entries versus 146 public callables; five methods are absent, including `embedding_preflight` and `embedding_status` | Stable semantic `OperationSpec` or an explicit exclusion classification drives status; method-name introspection is only a census check | `polylogue-s1kr` P2, `polylogue-9e5.31` P1 | +| 8 | Devtools command catalog and direct entry points | **Unresolved bypass** | Three production workflow/hook paths invoke modules or scripts outside the `CommandSpec` dispatcher | CI and hooks invoke catalog names, or a machine-readable sanctioned-bypass manifest records the exception | `polylogue-l8ee` P3 | +| 9 | Durable user-tier fresh schema, migrations, and assertions | **Compatibility copies plus incomplete projection** | Fresh user tier has 15 tables; the hand-written required-table test asserts 13 and omits two current holdout tables | Fresh DDL owns current schema; numbered migrations remain required compatibility copies; tests derive an exact introspected manifest and compare fresh versus upgraded databases | `polylogue-ihp0` P2 | +| 10 | `user_settings` declared storage capability | **Dead declared capability / compatibility schema copy** | Fresh DDL, migration, status probe, and docs exist, but no production read/write helper or consumer exists | Preserve the current tracker decision: keep the table, add a typed key registry and first real consumer; do not delete merely to remove textual duplication | `polylogue-at44` P3 | + +## What is already sound + +Not every repeated declaration is a defect. The read-view surface has 11 profiles, 11 lightweight handler-metadata entries, and 11 executable handlers. Both registry modules fail fast on missing, extra, or mismatched entries. This is a deliberate import-layer projection with an executable equality gate, so it is not a priority consolidation target. + +Durable-tier migrations are also not duplicate authority to delete. They are compatibility copies required by the repository’s durability doctrine. Their current weakness is validation: the fresh schema and every supported upgraded schema need to converge on one introspected current manifest. + +The MCP and daemon name/path sets are aligned today. Their problem is the direction of authority and broad edit cost, not a fabricated current count mismatch. The exception is the manual `CLAUDE.md` MCP count, which says 103 while the live admin surface and fixtures contain 104. + +## Recommended sequence + +First add regression tests that invoke the real consumers and fail on the four active collisions. Then repair query recipes and maintenance dispatch, because both currently hand users or agents an advertised operation that fails. Next fix nested config composition and migrate direct consumers. Consolidate embeddings DDL immediately afterward, before another writer or schema bump increases the data-rebuild blast radius. + +Only after those repairs should the general declaration work generate MCP, route, facade, and docs projections. That ordering prevents a new abstraction from faithfully generating today’s contradictions. + +## Limitations and residual uncertainty + +This analysis used the supplied complete repository snapshot, its Git bundle, Beads export, repository instructions, and focused local execution. It did not inspect a live operator archive, a deployed daemon, browser traffic, hosted CI, or an installed out-of-repository Polylogue skill. Tracker notes state that the installed skill repeats the invalid query recipes, but that installed artifact was not available here; the source-confirmed finding is the two MCP prompts. + +The snapshot manifest reports a dirty source tree. Of 3,739 tracked paths at the snapshot commit, the working-tree archive included 2,501 and omitted 1,238, primarily excluded `.agent/` history; every included tracked path matched Git HEAD byte-for-byte. No recommendation depends on an omitted tracked path. + +Another iteration has **medium value**. The top four findings already have direct production-route witnesses, so their decision risk is low. A second pass would be most valuable for expanding the census to every schema-writing helper, generated docs arrow, CLI operation map, and config bypass, and for running fresh-versus-upgraded durable-tier parity across all supported versions. It is unlikely to change the first four priorities, but it could reorder ranks five through ten or uncover additional lower-level consumers. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/11-query-discovery-declarations.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/11-query-discovery-declarations.md new file mode 100644 index 000000000..871999c70 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/11-query-discovery-declarations.md @@ -0,0 +1,18 @@ +Title: "[beads 11] Executable agent query discovery" + +Job ID: `beads-11` +Result ZIP: `beads-11-query-discovery-declarations-r01.zip` + +Implement the strongest coherent production slice of `polylogue-z9gh.3`: +declare agent-facing query units, fields, examples, projections, and recovery +guidance once, then generate the relevant MCP discovery surface and compile +every shipped recipe/skill expression against the live grammar. The direct +regression is product-owned curriculum teaching invalid sessions-only +`query_units` expressions while the parser rejects them without useful repair. + +Do not build a second query registry, hard-code a static catalog, or replace +valid expensive queries with refusal. Work from current source and the complete +Bead notes. Preserve the shared query-transaction boundary: integrate only +where it exists, otherwise leave an explicit dependency. Deliver a real patch, +real-route tests, and an anti-vacuity account showing the production parser and +description/recipe path each exercised. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/12-agent-manual-sessionstart.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/12-agent-manual-sessionstart.md new file mode 100644 index 000000000..3b06ab264 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/12-agent-manual-sessionstart.md @@ -0,0 +1,18 @@ +Title: "[beads 12] Comprehensive executable agent manual" + +Job ID: `beads-12` +Result ZIP: `beads-12-agent-manual-sessionstart-r01.zip` + +Implement the highest coherent slice of `polylogue-3gd.2` and `.3`: canonical +project-owned AgentIntegrationSpec/manual sources, generated standing guidance, +and an installable artifact that Sinnix/client integration can consume. The +manual must be substantive standing context, not a pointer banner: it teaches +automatic invocation, breadth of capability, valid routes, result/freshness +semantics, evidence limits, and recovery. Treat stable sections as cacheable, +not disposable. + +The current installed SessionStart hook advertises nonexistent tools and stale +recipes. Do not patch a Nix-store symlink or duplicate the manual in Sinnix. +Use `beads-11` output if available; otherwise keep executable query examples in +checked-in fixtures and state the exact dependency. Deliver a patch with +compiled/executed manual claims and realistic cold-agent regression fixtures. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/13-lineage-order-independence.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/13-lineage-order-independence.md new file mode 100644 index 000000000..fe14adc78 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/13-lineage-order-independence.md @@ -0,0 +1,15 @@ +Title: "[beads 13] Order-independent lineage writes" + +Job ID: `beads-13` +Result ZIP: `beads-13-lineage-order-independence-r01.zip` + +Implement `polylogue-866e`: equivalent parent/child/replacement histories must +converge to the same physical tails, links, and composed transcript regardless +of ingestion order. Current stateful property failures cover stale sibling +replacement and a missing parent branch point that can crash read composition. + +Start from the saved property failures and reduce them to named deterministic +transition fixtures with physical-row/link-row/composed-read oracles. Fix +production write/composition behavior rather than weakening lineage semantics, +discarding valid child content, or adding a test-only repair path. Missing +branch points must remain readable through a typed relation/completeness state. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/14-assertion-write-concurrency.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/14-assertion-write-concurrency.md new file mode 100644 index 000000000..f9cd5acea --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/briefs/14-assertion-write-concurrency.md @@ -0,0 +1,17 @@ +Title: "[beads 14] Assertion-write concurrency repair" + +Job ID: `beads-14` +Result ZIP: `beads-14-assertion-write-concurrency-r01.zip` + +Repair the reproduced `polylogue-41ow` operator-judgment TOCTOU race in +`user_write.py`. Concurrent assertion upserts can silently revert an accepted +operator judgment; this is durable user-tier correctness, not a cosmetic retry +problem. Trace every read-modify-write path and preserve immutable assertion, +authorization, provenance, and revision semantics. + +Implement one atomic, typed production route with a deterministic concurrency +fixture that demonstrates the old lost update and proves the repaired state +contains the intended successor(s). Do not solve it with timing sleeps, +best-effort retries, broad SQLite locking that breaks ordinary writes, or a +test-only serialization wrapper. Include migration/compatibility reasoning if +the durable schema contract changes. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/11-query-discovery-declarations.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/11-query-discovery-declarations.md new file mode 100644 index 000000000..77595efa5 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/11-query-discovery-declarations.md @@ -0,0 +1,101 @@ +Title: "[beads 11] Executable agent query discovery" + +Job ID: `beads-11` +Result ZIP: `beads-11-query-discovery-declarations-r01.zip` + +Implement the strongest coherent production slice of `polylogue-z9gh.3`: +declare agent-facing query units, fields, examples, projections, and recovery +guidance once, then generate the relevant MCP discovery surface and compile +every shipped recipe/skill expression against the live grammar. The direct +regression is product-owned curriculum teaching invalid sessions-only +`query_units` expressions while the parser rejects them without useful repair. + +Do not build a second query registry, hard-code a static catalog, or replace +valid expensive queries with refusal. Work from current source and the complete +Bead notes. Preserve the shared query-transaction boundary: integrate only +where it exists, otherwise leave an explicit dependency. Deliver a real patch, +real-route tests, and an anti-vacuity account showing the production parser and +description/recipe path each exercised. + +## Context and authority + +You are a long-running ChatGPT Pro engineering worker. A recent Polylogue +project-state archive will be attached. Retrieve and inspect it broadly; do not +assume attachment bytes consume your active prompt context. The attached +snapshot is the code authority. This prompt defines your mission. Repository +instructions and complete relevant Beads records define constraints and intent; +later Beads notes may supersede older descriptions. Current source wins when a +stale plan names paths or APIs that no longer exist. + +Start by reporting the snapshot commit/branch/dirty-patch identity you found and +the source, tests, Beads, and history you inspected. Follow dependencies beyond +the obvious files when they affect the production route. Do not invent an API, +test helper, product contract, or parallel framework to make the task easy. + +## Working contract + +- Produce the largest internally coherent implementation draft that fits the + mission. Prefer one real end-to-end behavior over disconnected scaffolding. +- Preserve Polylogue's substrate-first architecture and existing typed + interfaces. Small production seams are allowed only when real production + behavior needs observation or control. +- Write concrete production changes and real-route tests. A test must name the + production dependency it exercises and the representative implementation + mutation/removal that should make it fail. +- Do not delete existing tests or helpers. Identify proposed dominated + deletions separately for independent local certification. +- Use your container and run meaningful self-contained checks when possible. + Never claim access to the operator's live daemon, browser, archive, secrets, + NixOS deployment, or current worktree. Mark those checks `unverified`. +- If the full scope is unsafe, complete the strongest coherent subset and make + the remaining decisions and exact continuation steps explicit. Do not return + placeholders, ellipses, pseudocode presented as code, or a generic plan in + place of implementation. + +## Deliverable + +Create the exact `Result ZIP` named near the top of this prompt under +`/mnt/data/`. Do not include the supplied repository/project-state archive or +other copied inputs in the result. The finished ZIP must be attached to the +conversation through a working, user-clickable download link. Work left only +in an internal shell directory, temporary notebook, scattered sandbox files, +or prose is not delivered. + +The ZIP must contain: + +- `HANDOFF.md`: mission, snapshot identity, inspected evidence, mechanism, + decisions, changed files, acceptance matrix, apply order, risks, and exact + verification performed/remaining; +- `PATCH.diff`: one apply-ready unified diff against the named snapshot; +- `TESTS.md`: test design, production dependencies, anti-vacuity mutation, + commands, and honest execution results; +- `EVIDENCE.md`: relevant source/Bead/history findings and any contradictions; +- `FILES/`: complete replacements only where they materially disambiguate the + patch; omit it when unnecessary. + +Before answering, reopen the ZIP, list and validate its members, compute its +SHA-256 and byte size, and confirm that `PATCH.diff` has no placeholders or +copied source snapshot. Your final chat response must begin with a substantive +operator-readable report of what you did and why. It must also state important +limitations, missing or unverified work, and how much additional value another +iteration could plausibly add—distinguishing a small repair from a substantial +second pass. Then report verification and risks and give a prominent working +link to the exact `/mnt/data/` ZIP. A bare download receipt is not acceptable. + +## Continuation protocol + +Do not perform a separate adversarial review unless the user explicitly asks +for one. If the user asks to **iterate** or **continue**, preserve valid prior +work, perform the highest-value remaining implementation/research pass, and +publish a new cohesive package revision with the same complete structure—not a +loose supplemental patch. Explain exactly what changed, what improved, what +still remains, and whether another iteration is likely to pay off. + +If the user explicitly asks for an **adversarial review**, attack your prior +result against the original mission and current attached authority: search for +unsupported claims, invented or stale APIs, missing call sites, composition +failures, unsafe assumptions, vacuous tests, patch/apply defects, incomplete +acceptance criteria, and evidence that would falsify the design. Preserve work +that survives. Then repair every legitimate finding you can, regenerate the +entire cohesive package as the next revision, and report findings, repairs, +remaining disputes, and the value of another adversarial/implementation pass. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/12-agent-manual-sessionstart.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/12-agent-manual-sessionstart.md new file mode 100644 index 000000000..ca0fcc39f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/12-agent-manual-sessionstart.md @@ -0,0 +1,101 @@ +Title: "[beads 12] Comprehensive executable agent manual" + +Job ID: `beads-12` +Result ZIP: `beads-12-agent-manual-sessionstart-r01.zip` + +Implement the highest coherent slice of `polylogue-3gd.2` and `.3`: canonical +project-owned AgentIntegrationSpec/manual sources, generated standing guidance, +and an installable artifact that Sinnix/client integration can consume. The +manual must be substantive standing context, not a pointer banner: it teaches +automatic invocation, breadth of capability, valid routes, result/freshness +semantics, evidence limits, and recovery. Treat stable sections as cacheable, +not disposable. + +The current installed SessionStart hook advertises nonexistent tools and stale +recipes. Do not patch a Nix-store symlink or duplicate the manual in Sinnix. +Use `beads-11` output if available; otherwise keep executable query examples in +checked-in fixtures and state the exact dependency. Deliver a patch with +compiled/executed manual claims and realistic cold-agent regression fixtures. + +## Context and authority + +You are a long-running ChatGPT Pro engineering worker. A recent Polylogue +project-state archive will be attached. Retrieve and inspect it broadly; do not +assume attachment bytes consume your active prompt context. The attached +snapshot is the code authority. This prompt defines your mission. Repository +instructions and complete relevant Beads records define constraints and intent; +later Beads notes may supersede older descriptions. Current source wins when a +stale plan names paths or APIs that no longer exist. + +Start by reporting the snapshot commit/branch/dirty-patch identity you found and +the source, tests, Beads, and history you inspected. Follow dependencies beyond +the obvious files when they affect the production route. Do not invent an API, +test helper, product contract, or parallel framework to make the task easy. + +## Working contract + +- Produce the largest internally coherent implementation draft that fits the + mission. Prefer one real end-to-end behavior over disconnected scaffolding. +- Preserve Polylogue's substrate-first architecture and existing typed + interfaces. Small production seams are allowed only when real production + behavior needs observation or control. +- Write concrete production changes and real-route tests. A test must name the + production dependency it exercises and the representative implementation + mutation/removal that should make it fail. +- Do not delete existing tests or helpers. Identify proposed dominated + deletions separately for independent local certification. +- Use your container and run meaningful self-contained checks when possible. + Never claim access to the operator's live daemon, browser, archive, secrets, + NixOS deployment, or current worktree. Mark those checks `unverified`. +- If the full scope is unsafe, complete the strongest coherent subset and make + the remaining decisions and exact continuation steps explicit. Do not return + placeholders, ellipses, pseudocode presented as code, or a generic plan in + place of implementation. + +## Deliverable + +Create the exact `Result ZIP` named near the top of this prompt under +`/mnt/data/`. Do not include the supplied repository/project-state archive or +other copied inputs in the result. The finished ZIP must be attached to the +conversation through a working, user-clickable download link. Work left only +in an internal shell directory, temporary notebook, scattered sandbox files, +or prose is not delivered. + +The ZIP must contain: + +- `HANDOFF.md`: mission, snapshot identity, inspected evidence, mechanism, + decisions, changed files, acceptance matrix, apply order, risks, and exact + verification performed/remaining; +- `PATCH.diff`: one apply-ready unified diff against the named snapshot; +- `TESTS.md`: test design, production dependencies, anti-vacuity mutation, + commands, and honest execution results; +- `EVIDENCE.md`: relevant source/Bead/history findings and any contradictions; +- `FILES/`: complete replacements only where they materially disambiguate the + patch; omit it when unnecessary. + +Before answering, reopen the ZIP, list and validate its members, compute its +SHA-256 and byte size, and confirm that `PATCH.diff` has no placeholders or +copied source snapshot. Your final chat response must begin with a substantive +operator-readable report of what you did and why. It must also state important +limitations, missing or unverified work, and how much additional value another +iteration could plausibly add—distinguishing a small repair from a substantial +second pass. Then report verification and risks and give a prominent working +link to the exact `/mnt/data/` ZIP. A bare download receipt is not acceptable. + +## Continuation protocol + +Do not perform a separate adversarial review unless the user explicitly asks +for one. If the user asks to **iterate** or **continue**, preserve valid prior +work, perform the highest-value remaining implementation/research pass, and +publish a new cohesive package revision with the same complete structure—not a +loose supplemental patch. Explain exactly what changed, what improved, what +still remains, and whether another iteration is likely to pay off. + +If the user explicitly asks for an **adversarial review**, attack your prior +result against the original mission and current attached authority: search for +unsupported claims, invented or stale APIs, missing call sites, composition +failures, unsafe assumptions, vacuous tests, patch/apply defects, incomplete +acceptance criteria, and evidence that would falsify the design. Preserve work +that survives. Then repair every legitimate finding you can, regenerate the +entire cohesive package as the next revision, and report findings, repairs, +remaining disputes, and the value of another adversarial/implementation pass. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/13-lineage-order-independence.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/13-lineage-order-independence.md new file mode 100644 index 000000000..850402d46 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/13-lineage-order-independence.md @@ -0,0 +1,98 @@ +Title: "[beads 13] Order-independent lineage writes" + +Job ID: `beads-13` +Result ZIP: `beads-13-lineage-order-independence-r01.zip` + +Implement `polylogue-866e`: equivalent parent/child/replacement histories must +converge to the same physical tails, links, and composed transcript regardless +of ingestion order. Current stateful property failures cover stale sibling +replacement and a missing parent branch point that can crash read composition. + +Start from the saved property failures and reduce them to named deterministic +transition fixtures with physical-row/link-row/composed-read oracles. Fix +production write/composition behavior rather than weakening lineage semantics, +discarding valid child content, or adding a test-only repair path. Missing +branch points must remain readable through a typed relation/completeness state. + +## Context and authority + +You are a long-running ChatGPT Pro engineering worker. A recent Polylogue +project-state archive will be attached. Retrieve and inspect it broadly; do not +assume attachment bytes consume your active prompt context. The attached +snapshot is the code authority. This prompt defines your mission. Repository +instructions and complete relevant Beads records define constraints and intent; +later Beads notes may supersede older descriptions. Current source wins when a +stale plan names paths or APIs that no longer exist. + +Start by reporting the snapshot commit/branch/dirty-patch identity you found and +the source, tests, Beads, and history you inspected. Follow dependencies beyond +the obvious files when they affect the production route. Do not invent an API, +test helper, product contract, or parallel framework to make the task easy. + +## Working contract + +- Produce the largest internally coherent implementation draft that fits the + mission. Prefer one real end-to-end behavior over disconnected scaffolding. +- Preserve Polylogue's substrate-first architecture and existing typed + interfaces. Small production seams are allowed only when real production + behavior needs observation or control. +- Write concrete production changes and real-route tests. A test must name the + production dependency it exercises and the representative implementation + mutation/removal that should make it fail. +- Do not delete existing tests or helpers. Identify proposed dominated + deletions separately for independent local certification. +- Use your container and run meaningful self-contained checks when possible. + Never claim access to the operator's live daemon, browser, archive, secrets, + NixOS deployment, or current worktree. Mark those checks `unverified`. +- If the full scope is unsafe, complete the strongest coherent subset and make + the remaining decisions and exact continuation steps explicit. Do not return + placeholders, ellipses, pseudocode presented as code, or a generic plan in + place of implementation. + +## Deliverable + +Create the exact `Result ZIP` named near the top of this prompt under +`/mnt/data/`. Do not include the supplied repository/project-state archive or +other copied inputs in the result. The finished ZIP must be attached to the +conversation through a working, user-clickable download link. Work left only +in an internal shell directory, temporary notebook, scattered sandbox files, +or prose is not delivered. + +The ZIP must contain: + +- `HANDOFF.md`: mission, snapshot identity, inspected evidence, mechanism, + decisions, changed files, acceptance matrix, apply order, risks, and exact + verification performed/remaining; +- `PATCH.diff`: one apply-ready unified diff against the named snapshot; +- `TESTS.md`: test design, production dependencies, anti-vacuity mutation, + commands, and honest execution results; +- `EVIDENCE.md`: relevant source/Bead/history findings and any contradictions; +- `FILES/`: complete replacements only where they materially disambiguate the + patch; omit it when unnecessary. + +Before answering, reopen the ZIP, list and validate its members, compute its +SHA-256 and byte size, and confirm that `PATCH.diff` has no placeholders or +copied source snapshot. Your final chat response must begin with a substantive +operator-readable report of what you did and why. It must also state important +limitations, missing or unverified work, and how much additional value another +iteration could plausibly add—distinguishing a small repair from a substantial +second pass. Then report verification and risks and give a prominent working +link to the exact `/mnt/data/` ZIP. A bare download receipt is not acceptable. + +## Continuation protocol + +Do not perform a separate adversarial review unless the user explicitly asks +for one. If the user asks to **iterate** or **continue**, preserve valid prior +work, perform the highest-value remaining implementation/research pass, and +publish a new cohesive package revision with the same complete structure—not a +loose supplemental patch. Explain exactly what changed, what improved, what +still remains, and whether another iteration is likely to pay off. + +If the user explicitly asks for an **adversarial review**, attack your prior +result against the original mission and current attached authority: search for +unsupported claims, invented or stale APIs, missing call sites, composition +failures, unsafe assumptions, vacuous tests, patch/apply defects, incomplete +acceptance criteria, and evidence that would falsify the design. Preserve work +that survives. Then repair every legitimate finding you can, regenerate the +entire cohesive package as the next revision, and report findings, repairs, +remaining disputes, and the value of another adversarial/implementation pass. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/14-assertion-write-concurrency.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/14-assertion-write-concurrency.md new file mode 100644 index 000000000..39afd8cbd --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/prompts/14-assertion-write-concurrency.md @@ -0,0 +1,100 @@ +Title: "[beads 14] Assertion-write concurrency repair" + +Job ID: `beads-14` +Result ZIP: `beads-14-assertion-write-concurrency-r01.zip` + +Repair the reproduced `polylogue-41ow` operator-judgment TOCTOU race in +`user_write.py`. Concurrent assertion upserts can silently revert an accepted +operator judgment; this is durable user-tier correctness, not a cosmetic retry +problem. Trace every read-modify-write path and preserve immutable assertion, +authorization, provenance, and revision semantics. + +Implement one atomic, typed production route with a deterministic concurrency +fixture that demonstrates the old lost update and proves the repaired state +contains the intended successor(s). Do not solve it with timing sleeps, +best-effort retries, broad SQLite locking that breaks ordinary writes, or a +test-only serialization wrapper. Include migration/compatibility reasoning if +the durable schema contract changes. + +## Context and authority + +You are a long-running ChatGPT Pro engineering worker. A recent Polylogue +project-state archive will be attached. Retrieve and inspect it broadly; do not +assume attachment bytes consume your active prompt context. The attached +snapshot is the code authority. This prompt defines your mission. Repository +instructions and complete relevant Beads records define constraints and intent; +later Beads notes may supersede older descriptions. Current source wins when a +stale plan names paths or APIs that no longer exist. + +Start by reporting the snapshot commit/branch/dirty-patch identity you found and +the source, tests, Beads, and history you inspected. Follow dependencies beyond +the obvious files when they affect the production route. Do not invent an API, +test helper, product contract, or parallel framework to make the task easy. + +## Working contract + +- Produce the largest internally coherent implementation draft that fits the + mission. Prefer one real end-to-end behavior over disconnected scaffolding. +- Preserve Polylogue's substrate-first architecture and existing typed + interfaces. Small production seams are allowed only when real production + behavior needs observation or control. +- Write concrete production changes and real-route tests. A test must name the + production dependency it exercises and the representative implementation + mutation/removal that should make it fail. +- Do not delete existing tests or helpers. Identify proposed dominated + deletions separately for independent local certification. +- Use your container and run meaningful self-contained checks when possible. + Never claim access to the operator's live daemon, browser, archive, secrets, + NixOS deployment, or current worktree. Mark those checks `unverified`. +- If the full scope is unsafe, complete the strongest coherent subset and make + the remaining decisions and exact continuation steps explicit. Do not return + placeholders, ellipses, pseudocode presented as code, or a generic plan in + place of implementation. + +## Deliverable + +Create the exact `Result ZIP` named near the top of this prompt under +`/mnt/data/`. Do not include the supplied repository/project-state archive or +other copied inputs in the result. The finished ZIP must be attached to the +conversation through a working, user-clickable download link. Work left only +in an internal shell directory, temporary notebook, scattered sandbox files, +or prose is not delivered. + +The ZIP must contain: + +- `HANDOFF.md`: mission, snapshot identity, inspected evidence, mechanism, + decisions, changed files, acceptance matrix, apply order, risks, and exact + verification performed/remaining; +- `PATCH.diff`: one apply-ready unified diff against the named snapshot; +- `TESTS.md`: test design, production dependencies, anti-vacuity mutation, + commands, and honest execution results; +- `EVIDENCE.md`: relevant source/Bead/history findings and any contradictions; +- `FILES/`: complete replacements only where they materially disambiguate the + patch; omit it when unnecessary. + +Before answering, reopen the ZIP, list and validate its members, compute its +SHA-256 and byte size, and confirm that `PATCH.diff` has no placeholders or +copied source snapshot. Your final chat response must begin with a substantive +operator-readable report of what you did and why. It must also state important +limitations, missing or unverified work, and how much additional value another +iteration could plausibly add—distinguishing a small repair from a substantial +second pass. Then report verification and risks and give a prominent working +link to the exact `/mnt/data/` ZIP. A bare download receipt is not acceptable. + +## Continuation protocol + +Do not perform a separate adversarial review unless the user explicitly asks +for one. If the user asks to **iterate** or **continue**, preserve valid prior +work, perform the highest-value remaining implementation/research pass, and +publish a new cohesive package revision with the same complete structure—not a +loose supplemental patch. Explain exactly what changed, what improved, what +still remains, and whether another iteration is likely to pay off. + +If the user explicitly asks for an **adversarial review**, attack your prior +result against the original mission and current attached authority: search for +unsupported claims, invented or stale APIs, missing call sites, composition +failures, unsafe assumptions, vacuous tests, patch/apply defects, incomplete +acceptance criteria, and evidence that would falsify the design. Preserve work +that survives. Then repair every legitimate finding you can, regenerate the +entire cohesive package as the next revision, and report findings, repairs, +remaining disputes, and the value of another adversarial/implementation pass. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-02/a01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-02/a01/extracted/PATCH.diff new file mode 100644 index 000000000..e59288ac5 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-02/a01/extracted/PATCH.diff @@ -0,0 +1,20807 @@ +diff --git a/devtools/command_catalog.py b/devtools/command_catalog.py +index f621ebb30ba4383d0b5f4aa732a16363f0f81c1e..a28a6ae61e09bf1561979077acd6b1c581a76524 100644 +--- a/devtools/command_catalog.py ++++ b/devtools/command_catalog.py +@@ -175,6 +175,17 @@ COMMAND_SPECS: tuple[CommandSpec, ...] = ( + "Render docs/test-quality-workflows.md from executable lane, mutation, and benchmark registries.", + "devtools.render_quality_reference", + ), ++ CommandSpec( ++ "render mcp-equivalence", ++ "generated surfaces", ++ "Render docs/generated/mcp-equivalence.json from executable MCP declarations.", ++ "devtools.render_mcp_equivalence", ++ use_when=( ++ "Refresh or verify MCP discovery names, input/output contracts, role gates, operation owners, " ++ "Python parity expectations, and disjoint migration ownership after changing the compatibility surface." ++ ), ++ examples=("devtools render mcp-equivalence", "devtools render mcp-equivalence --check"), ++ ), + CommandSpec( + "render mcp-tool-index", + "generated surfaces", +diff --git a/devtools/generated_surfaces.py b/devtools/generated_surfaces.py +index 7072da5945cc838bc357a6c11b151365622f7079..6399db9b9e83f1dc942fb6e57e2e082415457a19 100644 +--- a/devtools/generated_surfaces.py ++++ b/devtools/generated_surfaces.py +@@ -11,6 +11,7 @@ from devtools import ( + render_demo_corpus_datasheet, + render_devtools_reference, + render_docs_surface, ++ render_mcp_equivalence, + render_mcp_tool_index, + render_openapi, + render_pages, +@@ -182,13 +183,25 @@ GENERATED_SURFACES: tuple[GeneratedSurface, ...] = ( + "README.md", + ), + ), ++ GeneratedSurface( ++ name="mcp-equivalence", ++ label="MCP algebra equivalence map", ++ description="Render the executable MCP contract and migration map as generated JSON.", ++ command=control_plane_argv("render mcp-equivalence"), ++ main=render_mcp_equivalence.main, ++ inputs=( ++ "devtools/render_mcp_equivalence.py", ++ "polylogue/declarations/", ++ "polylogue/mcp/declarations/", ++ ), ++ ), + GeneratedSurface( + name="mcp-tool-index", + label="MCP tool index", + description="Render the exhaustive generated tool-name appendix into docs/mcp-reference.md.", + command=control_plane_argv("render mcp-tool-index"), + main=render_mcp_tool_index.main, +- inputs=("devtools/render_mcp_tool_index.py", "tests/infra/mcp.py"), ++ inputs=("devtools/render_mcp_tool_index.py", "polylogue/mcp/declarations/registry.py"), + ), + GeneratedSurface( + name="topology-status", +diff --git a/devtools/render_mcp_equivalence.py b/devtools/render_mcp_equivalence.py +new file mode 100644 +index 0000000000000000000000000000000000000000..2f93ca80ed869ebcb3f5ce8584e595c834f6c427 +--- /dev/null ++++ b/devtools/render_mcp_equivalence.py +@@ -0,0 +1,132 @@ ++"""Render the executable MCP algebra inventory and compatibility map.""" ++ ++from __future__ import annotations ++ ++import argparse ++import json ++import sys ++from collections import Counter ++from dataclasses import asdict ++from pathlib import Path ++ ++from devtools.command_catalog import control_plane_command ++from devtools.render_support import write_if_changed ++from polylogue.mcp.declarations.models import MCPRole, MCPTransactionDeclaration ++from polylogue.mcp.declarations.registry import ( ++ MCP_TOOL_DECLARATIONS, ++ PRIVILEGED_ALGEBRA, ++ TARGET_DEFAULT_READ_ALGEBRA, ++ TARGET_PROMPTS, ++ TARGET_RESOURCES, ++) ++ ++DEFAULT_OUTPUT_PATH = Path("docs/generated/mcp-equivalence.json") ++SCHEMA_VERSION = 1 ++ ++ ++def _transaction_payload(item: MCPTransactionDeclaration) -> dict[str, object]: ++ return { ++ "name": item.name, ++ "verb": item.verb.value, ++ "minimum_role": item.minimum_role, ++ "object_kinds": list(item.object_kinds), ++ "result_semantics": [value.value for value in item.result_semantics], ++ "purpose": item.purpose, ++ "migration_owner": item.migration_owner, ++ } ++ ++ ++def build_equivalence_payload() -> dict[str, object]: ++ """Return the stable generated map consumed by drift checks and operators.""" ++ ++ role_counts = Counter(declaration.minimum_role for declaration in MCP_TOOL_DECLARATIONS) ++ read_retirements = sorted( ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.retirement_owner == "polylogue-t46.8.2" ++ ) ++ privileged_retirements = sorted( ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.retirement_owner == "polylogue-t46.8.3" ++ ) ++ bound_python = sorted( ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.python_parity.binding is not None ++ ) ++ governed_absences = sorted( ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.python_parity.binding is None ++ ) ++ ++ role_order: tuple[MCPRole, ...] = ("read", "write", "review", "admin") ++ ++ return { ++ "schema_version": SCHEMA_VERSION, ++ "generated_by": control_plane_command("render mcp-equivalence"), ++ "authority": { ++ "declarations": "polylogue/mcp/declarations/registry.py", ++ "live_registration": "polylogue/mcp/declarations/adapter.py", ++ "independent_name_baseline": "tests/infra/mcp.py::MCP_TOOL_NAME_BASELINE", ++ "independent_output_baseline": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "migration_authority": { ++ "read": "polylogue-t46.8.2", ++ "privileged": "polylogue-t46.8.3", ++ "python_parity": "polylogue-s1kr", ++ }, ++ }, ++ "compatibility_surface": { ++ "tool_count": len(MCP_TOOL_DECLARATIONS), ++ "minimum_role_counts": {role: role_counts.get(role, 0) for role in role_order}, ++ "python_binding_count": len(bound_python), ++ "governed_python_absence_count": len(governed_absences), ++ "tool_names": [declaration.name for declaration in MCP_TOOL_DECLARATIONS], ++ }, ++ "target_algebra": { ++ "default_read_transaction_count": len(TARGET_DEFAULT_READ_ALGEBRA), ++ "default_read_transactions": [_transaction_payload(item) for item in TARGET_DEFAULT_READ_ALGEBRA], ++ "privileged_transactions": [_transaction_payload(item) for item in PRIVILEGED_ALGEBRA], ++ "resources": [asdict(item) for item in TARGET_RESOURCES], ++ "prompts": [asdict(item) for item in TARGET_PROMPTS], ++ }, ++ "migration_groups": { ++ "polylogue-t46.8.2": read_retirements, ++ "polylogue-t46.8.3": privileged_retirements, ++ }, ++ "python_parity": { ++ "bound_tools": bound_python, ++ "governed_absences": governed_absences, ++ }, ++ "tools": [declaration.to_dict() for declaration in MCP_TOOL_DECLARATIONS], ++ } ++ ++ ++def render_output() -> str: ++ return json.dumps(build_equivalence_payload(), indent=2, sort_keys=True, ensure_ascii=False) + "\n" ++ ++ ++def main(argv: list[str] | None = None) -> int: ++ parser = argparse.ArgumentParser(description="Render the generated MCP algebra equivalence map.") ++ parser.add_argument( ++ "--output-path", ++ default=str(DEFAULT_OUTPUT_PATH), ++ help=f"target JSON artifact (default: {DEFAULT_OUTPUT_PATH})", ++ ) ++ parser.add_argument("--check", action="store_true", help="Exit non-zero when the artifact is out of sync.") ++ args = parser.parse_args(argv) ++ output_path = Path(args.output_path) ++ rendered = render_output() ++ ++ if args.check: ++ current = output_path.read_text(encoding="utf-8") if output_path.exists() else "" ++ if current != rendered: ++ print("render mcp-equivalence: out of sync:", file=sys.stderr) ++ print(f" - {output_path}", file=sys.stderr) ++ print( ++ f"render mcp-equivalence: run: {control_plane_command('render mcp-equivalence')}", ++ file=sys.stderr, ++ ) ++ return 1 ++ print("render mcp-equivalence: sync OK") ++ return 0 ++ ++ write_if_changed(output_path, rendered) ++ return 0 ++ ++ ++if __name__ == "__main__": ++ raise SystemExit(main()) +diff --git a/devtools/render_mcp_tool_index.py b/devtools/render_mcp_tool_index.py +index fe66cc79d236f69edfb1fb03195fd57b48347ae6..bd8b982a91104c2c01aeebaf6b2a31453b9b14b6 100644 +--- a/devtools/render_mcp_tool_index.py ++++ b/devtools/render_mcp_tool_index.py +@@ -2,7 +2,7 @@ + + ``docs/mcp-reference.md`` hand-curates a representative sample of tools by + category; that stays. This adds a generated appendix enumerating every +-registered tool name (``tests/infra/mcp.py:EXPECTED_TOOL_NAMES``), so each ++declared tool name (``polylogue.mcp.declarations.registry``), so each + tool is individually reachable from the docs tree (polylogue-3tl.9's + docs-coverage lint checks exactly this) without hand-duplicating the list. + """ +@@ -21,9 +21,9 @@ DEFAULT_DOC_PATH = Path("docs/mcp-reference.md") + + + def _tool_names() -> tuple[str, ...]: +- from tests.infra.mcp import EXPECTED_TOOL_NAMES ++ from polylogue.mcp.declarations.registry import declared_tool_names + +- return tuple(sorted(EXPECTED_TOOL_NAMES)) ++ return tuple(sorted(declared_tool_names("admin"))) + + + def build_tool_index_section(tool_names: tuple[str, ...] | None = None) -> str: +diff --git a/docs/devtools.md b/docs/devtools.md +index 356f6110a4c066b8a25083d0e39d2f360de2419e..d5e791332f645dbb5aa37ac50ce08cb966c08962 100644 +--- a/docs/devtools.md ++++ b/docs/devtools.md +@@ -105,6 +105,7 @@ These are the commands worth remembering during normal repo work: + | `devtools render demo-corpus-datasheet` | Render docs/plans/demo-corpus-construct-audit.md from the demo family registry and a measured seed archive. | + | `devtools render devtools-reference` | Render the command catalog inside docs/devtools.md. | + | `devtools render docs-surface` | Render docs/README.md and the README documentation table. | ++| `devtools render mcp-equivalence` | Render docs/generated/mcp-equivalence.json from executable MCP declarations. | + | `devtools render mcp-tool-index` | Render the generated exhaustive tool-name appendix into docs/mcp-reference.md. | + | `devtools render openapi` | Render docs/openapi/search.yaml from typed daemon query payload models. | + | `devtools render pages` | Build the GitHub Pages documentation site into .cache/site/. | +diff --git a/docs/generated/mcp-equivalence.json b/docs/generated/mcp-equivalence.json +new file mode 100644 +index 0000000000000000000000000000000000000000..9a86ff90d31761bde6dd9c7d5cc643d996883e19 +--- /dev/null ++++ b/docs/generated/mcp-equivalence.json +@@ -0,0 +1,14827 @@ ++{ ++ "authority": { ++ "declarations": "polylogue/mcp/declarations/registry.py", ++ "independent_name_baseline": "tests/infra/mcp.py::MCP_TOOL_NAME_BASELINE", ++ "independent_output_baseline": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "live_registration": "polylogue/mcp/declarations/adapter.py", ++ "migration_authority": { ++ "privileged": "polylogue-t46.8.3", ++ "python_parity": "polylogue-s1kr", ++ "read": "polylogue-t46.8.2" ++ } ++ }, ++ "compatibility_surface": { ++ "governed_python_absence_count": 18, ++ "minimum_role_counts": { ++ "admin": 7, ++ "read": 66, ++ "review": 2, ++ "write": 29 ++ }, ++ "python_binding_count": 86, ++ "tool_count": 104, ++ "tool_names": [ ++ "action_affordances", ++ "add_mark", ++ "add_tag", ++ "agent_coordination", ++ "aggregate_sessions", ++ "archive_coverage", ++ "archive_debt", ++ "archive_get_session", ++ "archive_list_sessions", ++ "archive_search_sessions", ++ "blackboard_list", ++ "blackboard_post", ++ "build_context_image", ++ "bulk_tag_sessions", ++ "capture_assertion_candidate", ++ "clear_corrections", ++ "compare_sessions", ++ "compile_context", ++ "compose_context_preamble", ++ "correlate_session", ++ "correlate_sessions", ++ "cost_outlook", ++ "cost_rollups", ++ "delete_annotation", ++ "delete_metadata", ++ "delete_recall_pack", ++ "delete_saved_view", ++ "delete_session", ++ "delete_workspace", ++ "embedding_preflight", ++ "embedding_status", ++ "explain_import", ++ "explain_query_expression", ++ "facets", ++ "find_abandoned_sessions", ++ "find_resume_candidates", ++ "find_similar_sessions", ++ "find_stuck_sessions", ++ "get_context_delivery", ++ "get_logical_session", ++ "get_messages", ++ "get_metadata", ++ "get_pathologies", ++ "get_postmortem_bundle", ++ "get_resume_brief", ++ "get_session_summary", ++ "get_session_topology", ++ "get_session_tree", ++ "get_stats_by", ++ "import_annotation_batch", ++ "insight_rigor_audit", ++ "join_typed_annotations", ++ "judge_assertion_candidate", ++ "judge_assertion_candidates", ++ "list_annotations", ++ "list_assertion_candidate_reviews", ++ "list_assertion_candidates", ++ "list_assertion_claims", ++ "list_corrections", ++ "list_marks", ++ "list_read_view_profiles", ++ "list_recall_packs", ++ "list_saved_views", ++ "list_sessions", ++ "list_tags", ++ "list_workspaces", ++ "maintenance_execute", ++ "maintenance_list", ++ "maintenance_preview", ++ "maintenance_status", ++ "named_source_freshness", ++ "neighbor_candidates", ++ "provider_usage", ++ "query_completions", ++ "query_units", ++ "raw_artifacts", ++ "readiness_check", ++ "rebuild_index", ++ "rebuild_session_insights", ++ "record_correction", ++ "remove_mark", ++ "remove_tag", ++ "resolve_ref", ++ "save_annotation", ++ "save_recall_pack", ++ "save_saved_view", ++ "save_workspace", ++ "search", ++ "session_costs", ++ "session_latency_profile", ++ "session_phases", ++ "session_profile", ++ "session_profiles", ++ "session_tag_rollups", ++ "session_tool_timing", ++ "session_work_events", ++ "set_metadata", ++ "stats", ++ "threads", ++ "tool_call_latency_distribution", ++ "tool_usage", ++ "update_index", ++ "usage_timeline", ++ "workflow_shape_distribution" ++ ] ++ }, ++ "generated_by": "devtools render mcp-equivalence", ++ "migration_groups": { ++ "polylogue-t46.8.2": [ ++ "action_affordances", ++ "agent_coordination", ++ "aggregate_sessions", ++ "archive_coverage", ++ "archive_debt", ++ "archive_get_session", ++ "archive_list_sessions", ++ "archive_search_sessions", ++ "blackboard_list", ++ "build_context_image", ++ "compare_sessions", ++ "compile_context", ++ "compose_context_preamble", ++ "correlate_session", ++ "correlate_sessions", ++ "cost_outlook", ++ "cost_rollups", ++ "embedding_preflight", ++ "embedding_status", ++ "explain_import", ++ "explain_query_expression", ++ "facets", ++ "find_abandoned_sessions", ++ "find_resume_candidates", ++ "find_similar_sessions", ++ "find_stuck_sessions", ++ "get_context_delivery", ++ "get_logical_session", ++ "get_messages", ++ "get_pathologies", ++ "get_postmortem_bundle", ++ "get_resume_brief", ++ "get_session_summary", ++ "get_session_topology", ++ "get_session_tree", ++ "get_stats_by", ++ "insight_rigor_audit", ++ "join_typed_annotations", ++ "list_assertion_candidate_reviews", ++ "list_assertion_candidates", ++ "list_assertion_claims", ++ "list_read_view_profiles", ++ "list_sessions", ++ "named_source_freshness", ++ "neighbor_candidates", ++ "provider_usage", ++ "query_completions", ++ "query_units", ++ "raw_artifacts", ++ "readiness_check", ++ "resolve_ref", ++ "search", ++ "session_costs", ++ "session_latency_profile", ++ "session_phases", ++ "session_profile", ++ "session_profiles", ++ "session_tag_rollups", ++ "session_tool_timing", ++ "session_work_events", ++ "stats", ++ "threads", ++ "tool_call_latency_distribution", ++ "tool_usage", ++ "usage_timeline", ++ "workflow_shape_distribution" ++ ], ++ "polylogue-t46.8.3": [ ++ "add_mark", ++ "add_tag", ++ "blackboard_post", ++ "bulk_tag_sessions", ++ "capture_assertion_candidate", ++ "clear_corrections", ++ "delete_annotation", ++ "delete_metadata", ++ "delete_recall_pack", ++ "delete_saved_view", ++ "delete_session", ++ "delete_workspace", ++ "get_metadata", ++ "import_annotation_batch", ++ "judge_assertion_candidate", ++ "judge_assertion_candidates", ++ "list_annotations", ++ "list_corrections", ++ "list_marks", ++ "list_recall_packs", ++ "list_saved_views", ++ "list_tags", ++ "list_workspaces", ++ "maintenance_execute", ++ "maintenance_list", ++ "maintenance_preview", ++ "maintenance_status", ++ "rebuild_index", ++ "rebuild_session_insights", ++ "record_correction", ++ "remove_mark", ++ "remove_tag", ++ "save_annotation", ++ "save_recall_pack", ++ "save_saved_view", ++ "save_workspace", ++ "set_metadata", ++ "update_index" ++ ] ++ }, ++ "python_parity": { ++ "bound_tools": [ ++ "add_mark", ++ "add_tag", ++ "aggregate_sessions", ++ "archive_coverage", ++ "archive_debt", ++ "archive_get_session", ++ "archive_list_sessions", ++ "archive_search_sessions", ++ "blackboard_list", ++ "blackboard_post", ++ "build_context_image", ++ "bulk_tag_sessions", ++ "capture_assertion_candidate", ++ "clear_corrections", ++ "compare_sessions", ++ "compile_context", ++ "correlate_session", ++ "correlate_sessions", ++ "cost_outlook", ++ "cost_rollups", ++ "delete_annotation", ++ "delete_metadata", ++ "delete_recall_pack", ++ "delete_saved_view", ++ "delete_session", ++ "delete_workspace", ++ "explain_import", ++ "explain_query_expression", ++ "find_abandoned_sessions", ++ "find_resume_candidates", ++ "find_similar_sessions", ++ "find_stuck_sessions", ++ "get_context_delivery", ++ "get_logical_session", ++ "get_metadata", ++ "get_pathologies", ++ "get_postmortem_bundle", ++ "get_resume_brief", ++ "get_session_topology", ++ "get_session_tree", ++ "get_stats_by", ++ "import_annotation_batch", ++ "insight_rigor_audit", ++ "join_typed_annotations", ++ "judge_assertion_candidate", ++ "judge_assertion_candidates", ++ "list_annotations", ++ "list_assertion_candidate_reviews", ++ "list_assertion_candidates", ++ "list_assertion_claims", ++ "list_corrections", ++ "list_marks", ++ "list_read_view_profiles", ++ "list_recall_packs", ++ "list_saved_views", ++ "list_tags", ++ "list_workspaces", ++ "neighbor_candidates", ++ "provider_usage", ++ "query_completions", ++ "raw_artifacts", ++ "rebuild_index", ++ "rebuild_session_insights", ++ "record_correction", ++ "remove_mark", ++ "remove_tag", ++ "resolve_ref", ++ "save_annotation", ++ "save_recall_pack", ++ "save_saved_view", ++ "save_workspace", ++ "session_costs", ++ "session_latency_profile", ++ "session_phases", ++ "session_profile", ++ "session_profiles", ++ "session_tag_rollups", ++ "session_tool_timing", ++ "session_work_events", ++ "set_metadata", ++ "threads", ++ "tool_call_latency_distribution", ++ "tool_usage", ++ "update_index", ++ "usage_timeline", ++ "workflow_shape_distribution" ++ ], ++ "governed_absences": [ ++ "action_affordances", ++ "agent_coordination", ++ "compose_context_preamble", ++ "embedding_preflight", ++ "embedding_status", ++ "facets", ++ "get_messages", ++ "get_session_summary", ++ "list_sessions", ++ "maintenance_execute", ++ "maintenance_list", ++ "maintenance_preview", ++ "maintenance_status", ++ "named_source_freshness", ++ "query_units", ++ "readiness_check", ++ "search", ++ "stats" ++ ] ++ }, ++ "schema_version": 1, ++ "target_algebra": { ++ "default_read_transaction_count": 7, ++ "default_read_transactions": [ ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "query", ++ "object_kinds": [ ++ "query", ++ "result-set" ++ ], ++ "purpose": "Execute a declared DSL or typed plan with explicit result semantics and continuation.", ++ "result_semantics": [ ++ "exhaustive_page", ++ "top_k", ++ "sample", ++ "aggregate" ++ ], ++ "verb": "query" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "read", ++ "object_kinds": [ ++ "object-ref", ++ "evidence-ref" ++ ], ++ "purpose": "Read any stable archive ref through a declared projection/view.", ++ "result_semantics": [ ++ "single_object", ++ "exhaustive_page", ++ "bounded_context" ++ ], ++ "verb": "read" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "get", ++ "object_kinds": [ ++ "object-ref" ++ ], ++ "purpose": "Resolve one exact object identity when a generic read would add ambiguity.", ++ "result_semantics": [ ++ "single_object" ++ ], ++ "verb": "get" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "graph", ++ "object_kinds": [ ++ "session", ++ "topology-edge", ++ "thread" ++ ], ++ "purpose": "Traverse lineage/topology with an explicit recursive continuation contract.", ++ "result_semantics": [ ++ "recursive_graph" ++ ], ++ "verb": "get" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "explain", ++ "object_kinds": [ ++ "query", ++ "object-ref", ++ "capability" ++ ], ++ "purpose": "Discover grammar, fields, values, plans, authority, and recovery routes.", ++ "result_semantics": [ ++ "single_object" ++ ], ++ "verb": "explain" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "read", ++ "name": "context", ++ "object_kinds": [ ++ "context-snapshot", ++ "context-delivery" ++ ], ++ "purpose": "Compile and retrieve policy-gated bounded context plus receipts.", ++ "result_semantics": [ ++ "bounded_context" ++ ], ++ "verb": "context" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "name": "status", ++ "object_kinds": [ ++ "status", ++ "receipt" ++ ], ++ "purpose": "Read archive, source, embedding, coordination, and operation status.", ++ "result_semantics": [ ++ "single_object", ++ "aggregate" ++ ], ++ "verb": "status" ++ } ++ ], ++ "privileged_transactions": [ ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "write", ++ "name": "write", ++ "object_kinds": [ ++ "object-ref", ++ "assertion" ++ ], ++ "purpose": "Apply a declaration-owned mutation after shared authorization.", ++ "result_semantics": [ ++ "mutation" ++ ], ++ "verb": "write" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "review", ++ "name": "judge", ++ "object_kinds": [ ++ "assertion-candidate", ++ "judgment" ++ ], ++ "purpose": "Accept, reject, defer, or supersede candidates without collapsing candidate state.", ++ "result_semantics": [ ++ "mutation" ++ ], ++ "verb": "judge" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "write", ++ "name": "run", ++ "object_kinds": [ ++ "saved-query", ++ "recipe", ++ "result-set" ++ ], ++ "purpose": "Execute a saved query or governed recipe ref.", ++ "result_semantics": [ ++ "exhaustive_page", ++ "mutation" ++ ], ++ "verb": "run" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "admin", ++ "name": "maintenance", ++ "object_kinds": [ ++ "maintenance-plan", ++ "maintenance-operation" ++ ], ++ "purpose": "Preview, authorize, execute, inspect, and reconcile maintenance operations.", ++ "result_semantics": [ ++ "maintenance" ++ ], ++ "verb": "maintenance" ++ } ++ ], ++ "prompts": [ ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "resume_context", ++ "workflow": "resume" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "postmortem_last", ++ "workflow": "postmortem" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "decisions_about", ++ "workflow": "decision-recovery" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "unacknowledged_failures", ++ "workflow": "failure-recovery" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "sessions_touching_file", ++ "workflow": "file-touch" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "cost_of", ++ "workflow": "cost-analysis" ++ }, ++ { ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "read", ++ "mutation_authority": "none", ++ "name": "agent_coordination_brief", ++ "workflow": "coordination" ++ } ++ ], ++ "resources": [ ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "session" ++ ], ++ "uri_template": "polylogue://session/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "message" ++ ], ++ "uri_template": "polylogue://message/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "block" ++ ], ++ "uri_template": "polylogue://block/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "action" ++ ], ++ "uri_template": "polylogue://action/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "file" ++ ], ++ "uri_template": "polylogue://file/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "query" ++ ], ++ "uri_template": "polylogue://query/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "result-set" ++ ], ++ "uri_template": "polylogue://result-set/{id}" ++ }, ++ { ++ "authority": "read-only object projection; resources never acquire instruction or mutation authority", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimum_role": "read", ++ "object_kinds": [ ++ "recall-pack" ++ ], ++ "uri_template": "polylogue://recall-pack/{id}" ++ } ++ ] ++ }, ++ "tools": [ ++ { ++ "canonical_plan": "polylogue.operations.action_contracts.action_affordance_list_payload", ++ "canonical_projection": "single_object:root", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return shared query-action affordance metadata for agents.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.action_affordances:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:action-affordance", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.action_affordances" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.action_affordances" ++ } ++ ], ++ "declaration_id": "mcp.tool.action_affordances", ++ "discovery_text": "Return shared query-action affordance metadata for agents.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for action_affordances." ++ } ++ ], ++ "family_id": "mcp.tool.action_affordances", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:action_affordances", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "action_affordances" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/action_affordances" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.operations.action_contracts.action_affordance_list_payload", ++ "public_name": "action_affordances", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.action_affordances:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "action_affordances", ++ "object_kinds": [ ++ "action-affordance" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.operations.action_contracts.action_affordance_list_payload", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "action_affordances" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "action_affordances", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.add_mark", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Add mark.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "mark_type" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.add_mark:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:mark,object-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.add_mark" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.add_mark" ++ } ++ ], ++ "declaration_id": "mcp.tool.add_mark", ++ "discovery_text": "Add mark.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "mark_type", ++ "star" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for add_mark." ++ } ++ ], ++ "family_id": "mcp.tool.add_mark", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:add_mark", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "add_mark" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/add_mark" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.add_mark", ++ "public_name": "add_mark", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.add_mark:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "mark_type": "star", ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "add_mark", ++ "object_kinds": [ ++ "mark", ++ "object-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.add_mark", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.add_mark", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "add_mark" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "add_mark", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-add-tag", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Add tag.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "tag" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.add_tag:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tag,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.add_tag" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.add_tag" ++ } ++ ], ++ "declaration_id": "mcp.tool.add_tag", ++ "discovery_text": "Add tag.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "author_kind", ++ "agent" ++ ], ++ [ ++ "author_ref", ++ "agent:codex-session:tool-contract" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ], ++ [ ++ "tag", ++ "review" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for add_tag." ++ } ++ ], ++ "family_id": "mcp.tool.add_tag", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:add_tag", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "add_tag" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/add_tag" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-add-tag", ++ "public_name": "add_tag", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.add_tag:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "author_kind": "agent", ++ "author_ref": "agent:codex-session:tool-contract", ++ "session_id": "test:conv-mutation", ++ "tag": "review" ++ }, ++ "minimum_role": "write", ++ "name": "add_tag", ++ "object_kinds": [ ++ "tag", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-add-tag", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.add_tag", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "add_tag" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "add_tag", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.coordination.build_coordination_envelope", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.agent_coordination:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:coordination-envelope", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.agent_coordination" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.agent_coordination" ++ } ++ ], ++ "declaration_id": "mcp.tool.agent_coordination", ++ "discovery_text": "Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for agent_coordination." ++ } ++ ], ++ "family_id": "mcp.tool.agent_coordination", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:agent_coordination", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "agent_coordination" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/agent_coordination" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.coordination.build_coordination_envelope", ++ "public_name": "agent_coordination", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.agent_coordination:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "agent_coordination", ++ "object_kinds": [ ++ "coordination-envelope" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.coordination.build_coordination_envelope", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "agent_coordination_brief" ++ ], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "agent_coordination" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "agent_coordination", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "multi-agent-coordination" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.aggregate_sessions", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.aggregate_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-aggregate", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.aggregate_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.aggregate_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.aggregate_sessions", ++ "discovery_text": "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for aggregate_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.aggregate_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:aggregate_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "aggregate_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/aggregate_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.aggregate_sessions", ++ "public_name": "aggregate_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.aggregate_sessions:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "aggregate_sessions", ++ "object_kinds": [ ++ "session-aggregate" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.aggregate_sessions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.aggregate_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "aggregate_sessions" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "aggregate_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-archive-coverage", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List archive coverage from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ArchiveCoverageInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:coverage-rollup", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.archive_coverage" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.archive_coverage" ++ } ++ ], ++ "declaration_id": "mcp.tool.archive_coverage", ++ "discovery_text": "List archive coverage from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for archive_coverage." ++ } ++ ], ++ "family_id": "mcp.tool.archive_coverage", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:archive_coverage", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "archive_coverage" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/archive_coverage" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-archive-coverage", ++ "public_name": "archive_coverage", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ArchiveCoverageInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "archive_coverage", ++ "object_kinds": [ ++ "coverage-rollup" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-archive-coverage", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_archive_coverage_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "archive_coverage" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "archive_coverage", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-archive-debt", ++ "canonical_projection": "typed_envelope:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return the unified archive debt list used by CLI and daemon surfaces.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.archive_debt:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:typed_envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:archive-debt", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.archive_debt" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.archive_debt" ++ } ++ ], ++ "declaration_id": "mcp.tool.archive_debt", ++ "discovery_text": "Return the unified archive debt list used by CLI and daemon surfaces.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for archive_debt." ++ } ++ ], ++ "family_id": "mcp.tool.archive_debt", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:archive_debt", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "archive_debt" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "typed_envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/archive_debt" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-archive-debt", ++ "public_name": "archive_debt", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.archive_debt:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "archive_debt", ++ "object_kinds": [ ++ "archive-debt" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-archive-debt", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "typed_envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.archive_debt", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "archive_debt" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "archive_debt", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.archive_get_session", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Read a full session by exact id or prefix.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.archive_get_session:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.archive_get_session" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.archive_get_session" ++ } ++ ], ++ "declaration_id": "mcp.tool.archive_get_session", ++ "discovery_text": "Read a full session by exact id or prefix.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for archive_get_session." ++ } ++ ], ++ "family_id": "mcp.tool.archive_get_session", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:archive_get_session", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "archive_get_session" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/archive_get_session" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.archive_get_session", ++ "public_name": "archive_get_session", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.archive_get_session:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "archive_get_session", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.archive_get_session", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.archive_get_session", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "archive_get_session" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "archive_get_session", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "envelope:items,limit,offset,total", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "compatibility", ++ "description": "List sessions from the split archive store.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.archive_list_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,result-set", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.archive_list_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.archive_list_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.archive_list_sessions", ++ "discovery_text": "List sessions from the split archive store.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for archive_list_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.archive_list_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:archive_list_sessions", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "archive_list_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/archive_list_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "archive_list_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.archive_list_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "archive_list_sessions", ++ "object_kinds": [ ++ "session", ++ "result-set" ++ ], ++ "observed_use": "not_observed", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "offset", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.archive_list_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "archive_list_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}", ++ "polylogue://result-set/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "archive_list_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "envelope:items,limit,query,total", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "compatibility", ++ "description": "Search session block text.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "query" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.archive_search_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,result-set", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.archive_search_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.archive_search_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.archive_search_sessions", ++ "discovery_text": "Search session block text.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "query", ++ "hello" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for archive_search_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.archive_search_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:archive_search_sessions", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "archive_search_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/archive_search_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "archive_search_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.archive_search_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "query": "hello" ++ }, ++ "minimum_role": "read", ++ "name": "archive_search_sessions", ++ "object_kinds": [ ++ "session", ++ "result-set" ++ ], ++ "observed_use": "not_observed", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "query", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.archive_search_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "archive_search_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}", ++ "polylogue://result-set/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "archive_search_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_blackboard_notes", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List persistent agent blackboard notes, newest first (#1697).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.blackboard_list:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:blackboard-note", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.blackboard_list" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.blackboard_list" ++ } ++ ], ++ "declaration_id": "mcp.tool.blackboard_list", ++ "discovery_text": "List persistent agent blackboard notes, newest first (#1697).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for blackboard_list." ++ } ++ ], ++ "family_id": "mcp.tool.blackboard_list", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:blackboard_list", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "blackboard_list" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/blackboard_list" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_blackboard_notes", ++ "public_name": "blackboard_list", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.blackboard_list:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "blackboard_list", ++ "object_kinds": [ ++ "blackboard-note" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_blackboard_notes", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_blackboard_notes", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "blackboard_list" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "blackboard_list", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "multi-agent-coordination" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.post_blackboard_note", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Post a note to the persistent agent blackboard (#1697).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "kind", ++ "title", ++ "content" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.blackboard_post:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:blackboard-note", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.blackboard_post" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.blackboard_post" ++ } ++ ], ++ "declaration_id": "mcp.tool.blackboard_post", ++ "discovery_text": "Post a note to the persistent agent blackboard (#1697).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "content", ++ "c" ++ ], ++ [ ++ "kind", ++ "finding" ++ ], ++ [ ++ "title", ++ "t" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for blackboard_post." ++ } ++ ], ++ "family_id": "mcp.tool.blackboard_post", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:blackboard_post", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "blackboard_post" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/blackboard_post" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.post_blackboard_note", ++ "public_name": "blackboard_post", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.blackboard_post:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "content": "c", ++ "kind": "finding", ++ "title": "t" ++ }, ++ "minimum_role": "write", ++ "name": "blackboard_post", ++ "object_kinds": [ ++ "blackboard-note" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.post_blackboard_note", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.post_blackboard_note", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "blackboard_post" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "blackboard_post", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.context_image_payload", ++ "canonical_projection": "single_object:root", ++ "capability": "read:context", ++ "compatibility_route": "target:context(seed, policy, budget)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Build a bounded context image for agent analysis.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_context_tools.build_context_image:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "context:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:context-image,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.build_context_image" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.build_context_image" ++ } ++ ], ++ "declaration_id": "mcp.tool.build_context_image", ++ "discovery_text": "Build a bounded context image for agent analysis.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "project_repo", ++ "test" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for build_context_image." ++ } ++ ], ++ "family_id": "mcp.tool.build_context_image", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_context_tools:build_context_image", ++ "owner_path": "polylogue/mcp/server_context_tools.py", ++ "surface": "mcp", ++ "symbol": "build_context_image" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/build_context_image" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.context_image_payload", ++ "public_name": "build_context_image", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_context_tools.build_context_image:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "project_repo": "test" ++ }, ++ "minimum_role": "read", ++ "name": "build_context_image", ++ "object_kinds": [ ++ "context-image", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.context_image_payload", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.context_image_payload", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_context_tools", ++ "registrar": "register_context_tools", ++ "symbol": "build_context_image" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "build_context_image", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "context", ++ "workflow_coverage": [ ++ "continuity-handoff", ++ "bounded-context-delivery" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-bulk-tag-sessions", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Bulk tag sessions.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_ids", ++ "tags" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.bulk_tag_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tag,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.bulk_tag_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.bulk_tag_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.bulk_tag_sessions", ++ "discovery_text": "Bulk tag sessions.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_ids", ++ [ ++ "test:conv-mutation" ++ ] ++ ], ++ [ ++ "tags", ++ [ ++ "review" ++ ] ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for bulk_tag_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.bulk_tag_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:bulk_tag_sessions", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "bulk_tag_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/bulk_tag_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-bulk-tag-sessions", ++ "public_name": "bulk_tag_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.bulk_tag_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_ids": [ ++ "test:conv-mutation" ++ ], ++ "tags": [ ++ "review" ++ ] ++ }, ++ "minimum_role": "write", ++ "name": "bulk_tag_sessions", ++ "object_kinds": [ ++ "tag", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-bulk-tag-sessions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.bulk_tag_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "bulk_tag_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "bulk_tag_sessions", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "canonical_projection": "single_object:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Capture a terminal-equivalent assertion candidate for later judgment.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "body_text", ++ "author_ref" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.capture_assertion_candidate:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:single_object", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion-candidate", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.capture_assertion_candidate" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.capture_assertion_candidate" ++ } ++ ], ++ "declaration_id": "mcp.tool.capture_assertion_candidate", ++ "discovery_text": "Capture a terminal-equivalent assertion candidate for later judgment.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "author_ref", ++ "agent:codex-session:tool-contract" ++ ], ++ [ ++ "body_text", ++ "capture this" ++ ], ++ [ ++ "kind", ++ "lesson" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for capture_assertion_candidate." ++ } ++ ], ++ "family_id": "mcp.tool.capture_assertion_candidate", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:capture_assertion_candidate", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "capture_assertion_candidate" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/capture_assertion_candidate" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "public_name": "capture_assertion_candidate", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.capture_assertion_candidate:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "author_ref": "agent:codex-session:tool-contract", ++ "body_text": "capture this", ++ "kind": "lesson" ++ }, ++ "minimum_role": "write", ++ "name": "capture_assertion_candidate", ++ "object_kinds": [ ++ "assertion-candidate" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "capture_assertion_candidate" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "capture_assertion_candidate", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.clear_corrections", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete one or all corrections for a session.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.clear_corrections:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:correction,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.clear_corrections" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.clear_corrections" ++ } ++ ], ++ "declaration_id": "mcp.tool.clear_corrections", ++ "discovery_text": "Delete one or all corrections for a session.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for clear_corrections." ++ } ++ ], ++ "family_id": "mcp.tool.clear_corrections", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:clear_corrections", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "clear_corrections" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/clear_corrections" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.clear_corrections", ++ "public_name": "clear_corrections", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.clear_corrections:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "clear_corrections", ++ "object_kinds": [ ++ "correction", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.clear_corrections", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.clear_corrections", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "clear_corrections" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "clear_corrections", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.compare_sessions", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Compare multiple session profiles side-by-side.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.compare_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-comparison", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.compare_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.compare_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.compare_sessions", ++ "discovery_text": "Compare multiple session profiles side-by-side.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_ids", ++ "test:a,test:b" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for compare_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.compare_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:compare_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "compare_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/compare_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.compare_sessions", ++ "public_name": "compare_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.compare_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_ids": "test:a,test:b" ++ }, ++ "minimum_role": "read", ++ "name": "compare_sessions", ++ "object_kinds": [ ++ "session-comparison" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.compare_sessions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "compare_sessions" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.compare_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "compare_sessions" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "compare_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.compile_context", ++ "canonical_projection": "single_object:root", ++ "capability": "read:context", ++ "compatibility_route": "target:context(seed, policy, budget)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Compile a bounded context image from query/ref seeds and read views.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.compile_context:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "context:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:context-snapshot,object-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.compile_context" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.compile_context" ++ } ++ ], ++ "declaration_id": "mcp.tool.compile_context", ++ "discovery_text": "Compile a bounded context image from query/ref seeds and read views.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "read_views", ++ "messages" ++ ], ++ [ ++ "seed_ref", ++ "session:test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for compile_context." ++ } ++ ], ++ "family_id": "mcp.tool.compile_context", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:compile_context", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "compile_context" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/compile_context" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.compile_context", ++ "public_name": "compile_context", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.compile_context:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "read_views": "messages", ++ "seed_ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "compile_context", ++ "object_kinds": [ ++ "context-snapshot", ++ "object-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.compile_context", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.compile_context", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "compile_context" ++ }, ++ "resource_alternatives": [ ++ "polylogue://context-snapshot/{id}" ++ ], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "compile_context", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "context", ++ "workflow_coverage": [ ++ "continuity-handoff", ++ "bounded-context-delivery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.context.continuity.build_context_preamble", ++ "canonical_projection": "single_object:root", ++ "capability": "read:context", ++ "compatibility_route": "target:context(seed, policy, budget)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Compose a ContextPreamble for SessionStart context injection (#1494).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_context_tools.compose_context_preamble:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "context:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:context-preamble,workspace", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.compose_context_preamble" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.compose_context_preamble" ++ } ++ ], ++ "declaration_id": "mcp.tool.compose_context_preamble", ++ "discovery_text": "Compose a ContextPreamble for SessionStart context injection (#1494).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for compose_context_preamble." ++ } ++ ], ++ "family_id": "mcp.tool.compose_context_preamble", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_context_tools:compose_context_preamble", ++ "owner_path": "polylogue/mcp/server_context_tools.py", ++ "surface": "mcp", ++ "symbol": "compose_context_preamble" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/compose_context_preamble" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.context.continuity.build_context_preamble", ++ "public_name": "compose_context_preamble", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_context_tools.compose_context_preamble:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "compose_context_preamble", ++ "object_kinds": [ ++ "context-preamble", ++ "workspace" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.context.continuity.build_context_preamble", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_context_tools", ++ "registrar": "register_context_tools", ++ "symbol": "compose_context_preamble" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "compose_context_preamble", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "context", ++ "workflow_coverage": [ ++ "continuity-handoff", ++ "bounded-context-delivery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Link a session to git commits and GitHub issue/PR references (#1690).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.correlate_session:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:repository-correlation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.correlate_session" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.correlate_session" ++ } ++ ], ++ "declaration_id": "mcp.tool.correlate_session", ++ "discovery_text": "Link a session to git commits and GitHub issue/PR references (#1690).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for correlate_session." ++ } ++ ], ++ "family_id": "mcp.tool.correlate_session", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:correlate_session", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "correlate_session" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/correlate_session" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session", ++ "public_name": "correlate_session", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.correlate_session:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "correlate_session", ++ "object_kinds": [ ++ "repository-correlation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "sessions_touching_file" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "correlate_session" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "correlate_session", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.correlate_sessions", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Compute Pearson correlation between two numeric session metrics.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "metric_x", ++ "metric_y" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.correlate_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:correlation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.correlate_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.correlate_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.correlate_sessions", ++ "discovery_text": "Compute Pearson correlation between two numeric session metrics.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "metric_x", ++ "message_count" ++ ], ++ [ ++ "metric_y", ++ "word_count" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for correlate_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.correlate_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:correlate_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "correlate_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/correlate_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.correlate_sessions", ++ "public_name": "correlate_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.correlate_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "metric_x": "message_count", ++ "metric_y": "word_count" ++ }, ++ "minimum_role": "read", ++ "name": "correlate_sessions", ++ "object_kinds": [ ++ "correlation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.correlate_sessions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.correlate_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "correlate_sessions" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "correlate_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.cost_outlook", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Project the current billing cycle for a subscription plan (#1138).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "plan" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.cost_outlook:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:cost-outlook", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.cost_outlook" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.cost_outlook" ++ } ++ ], ++ "declaration_id": "mcp.tool.cost_outlook", ++ "discovery_text": "Project the current billing cycle for a subscription plan (#1138).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "plan", ++ "claude-pro" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for cost_outlook." ++ } ++ ], ++ "family_id": "mcp.tool.cost_outlook", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:cost_outlook", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "cost_outlook" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/cost_outlook" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.cost_outlook", ++ "public_name": "cost_outlook", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.cost_outlook:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "plan": "claude-pro" ++ }, ++ "minimum_role": "read", ++ "name": "cost_outlook", ++ "object_kinds": [ ++ "cost-outlook" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.cost_outlook", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "cost_of" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.cost_outlook", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "cost_outlook" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "cost_outlook", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_cost_rollup_insights", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List cost rollups from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(CostRollupInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:cost-rollup", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.cost_rollups" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.cost_rollups" ++ } ++ ], ++ "declaration_id": "mcp.tool.cost_rollups", ++ "discovery_text": "List cost rollups from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for cost_rollups." ++ } ++ ], ++ "family_id": "mcp.tool.cost_rollups", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:cost_rollups", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "cost_rollups" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/cost_rollups" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_cost_rollup_insights", ++ "public_name": "cost_rollups", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(CostRollupInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "cost_rollups", ++ "object_kinds": [ ++ "cost-rollup" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_cost_rollup_insights", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_cost_rollup_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "cost_rollups" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "cost_rollups", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.delete_annotation", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete annotation.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "annotation_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.delete_annotation:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:annotation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_annotation" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_annotation" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_annotation", ++ "discovery_text": "Delete annotation.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "annotation_id", ++ "ann-1" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_annotation." ++ } ++ ], ++ "family_id": "mcp.tool.delete_annotation", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:delete_annotation", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_annotation" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_annotation" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.delete_annotation", ++ "public_name": "delete_annotation", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.delete_annotation:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "annotation_id": "ann-1" ++ }, ++ "minimum_role": "write", ++ "name": "delete_annotation", ++ "object_kinds": [ ++ "annotation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.delete_annotation", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_annotation", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "delete_annotation" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_annotation", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-delete-metadata", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete metadata.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "key" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.delete_metadata:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-metadata", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_metadata" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_metadata" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_metadata", ++ "discovery_text": "Delete metadata.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "key", ++ "note" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_metadata." ++ } ++ ], ++ "family_id": "mcp.tool.delete_metadata", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:delete_metadata", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_metadata" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_metadata" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-delete-metadata", ++ "public_name": "delete_metadata", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.delete_metadata:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "key": "note", ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "delete_metadata", ++ "object_kinds": [ ++ "session-metadata" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-delete-metadata", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_metadata", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "delete_metadata" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_metadata", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.delete_recall_pack", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete recall pack.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "pack_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.delete_recall_pack:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:recall-pack", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_recall_pack" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_recall_pack" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_recall_pack", ++ "discovery_text": "Delete recall pack.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "pack_id", ++ "pack-1" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_recall_pack." ++ } ++ ], ++ "family_id": "mcp.tool.delete_recall_pack", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:delete_recall_pack", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_recall_pack" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_recall_pack" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.delete_recall_pack", ++ "public_name": "delete_recall_pack", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.delete_recall_pack:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "pack_id": "pack-1" ++ }, ++ "minimum_role": "write", ++ "name": "delete_recall_pack", ++ "object_kinds": [ ++ "recall-pack" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.delete_recall_pack", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_recall_pack", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "delete_recall_pack" ++ }, ++ "resource_alternatives": [ ++ "polylogue://recall-pack/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_recall_pack", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.delete_view", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete saved view.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "view_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.delete_saved_view:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:saved-query", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_saved_view" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_saved_view" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_saved_view", ++ "discovery_text": "Delete saved view.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "view_id", ++ "view-1" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_saved_view." ++ } ++ ], ++ "family_id": "mcp.tool.delete_saved_view", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:delete_saved_view", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_saved_view" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_saved_view" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.delete_view", ++ "public_name": "delete_saved_view", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.delete_saved_view:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "view_id": "view-1" ++ }, ++ "minimum_role": "write", ++ "name": "delete_saved_view", ++ "object_kinds": [ ++ "saved-query" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.delete_view", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_view", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "delete_saved_view" ++ }, ++ "resource_alternatives": [ ++ "polylogue://saved-query/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_saved_view", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-delete-session", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete session.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.delete_session:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_session" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_session" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_session", ++ "discovery_text": "Delete session.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "confirm", ++ true ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_session." ++ } ++ ], ++ "family_id": "mcp.tool.delete_session", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:delete_session", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_session" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_session" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-delete-session", ++ "public_name": "delete_session", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.delete_session:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "confirm": true, ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "delete_session", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-delete-session", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_session_safe", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "delete_session" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_session", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.delete_workspace", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Delete workspace.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "workspace_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.delete_workspace:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:reader-workspace", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.delete_workspace" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.delete_workspace" ++ } ++ ], ++ "declaration_id": "mcp.tool.delete_workspace", ++ "discovery_text": "Delete workspace.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "workspace_id", ++ "ws-1" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for delete_workspace." ++ } ++ ], ++ "family_id": "mcp.tool.delete_workspace", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:delete_workspace", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "delete_workspace" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/delete_workspace" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.delete_workspace", ++ "public_name": "delete_workspace", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.delete_workspace:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "workspace_id": "ws-1" ++ }, ++ "minimum_role": "write", ++ "name": "delete_workspace", ++ "object_kinds": [ ++ "reader-workspace" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.delete_workspace", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.delete_workspace", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "delete_workspace" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "delete_workspace", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "query-embedding-status", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Embedding preflight.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.embedding_preflight:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:embedding-plan", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.embedding_preflight" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.embedding_preflight" ++ } ++ ], ++ "declaration_id": "mcp.tool.embedding_preflight", ++ "discovery_text": "Embedding preflight.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for embedding_preflight." ++ } ++ ], ++ "family_id": "mcp.tool.embedding_preflight", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:embedding_preflight", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "embedding_preflight" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/embedding_preflight" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-embedding-status", ++ "public_name": "embedding_preflight", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.embedding_preflight:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "embedding_preflight", ++ "object_kinds": [ ++ "embedding-plan" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-embedding-status", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "embedding_preflight" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "embedding_preflight", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-embedding-status", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Embedding status.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.embedding_status:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:embedding-status", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.embedding_status" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.embedding_status" ++ } ++ ], ++ "declaration_id": "mcp.tool.embedding_status", ++ "discovery_text": "Embedding status.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for embedding_status." ++ } ++ ], ++ "family_id": "mcp.tool.embedding_status", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:embedding_status", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "embedding_status" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/embedding_status" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-embedding-status", ++ "public_name": "embedding_status", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.embedding_status:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "embedding_status", ++ "object_kinds": [ ++ "embedding-status" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-embedding-status", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "embedding_status" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "embedding_status", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.explain_import", ++ "canonical_projection": "typed_envelope:root", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Explain import detection/parsing from a local path or archived raw evidence.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.explain_import:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:single_object:typed_envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:raw-artifact,import-record", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.explain_import" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.explain_import" ++ } ++ ], ++ "declaration_id": "mcp.tool.explain_import", ++ "discovery_text": "Explain import detection/parsing from a local path or archived raw evidence.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "raw_ref", ++ "raw:discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for explain_import." ++ } ++ ], ++ "family_id": "mcp.tool.explain_import", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:explain_import", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "explain_import" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "typed_envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/explain_import" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.explain_import", ++ "public_name": "explain_import", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.explain_import:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "raw_ref": "raw:discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "explain_import", ++ "object_kinds": [ ++ "raw-artifact", ++ "import-record" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.explain_import", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "typed_envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.explain_import", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "explain_import" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "explain_import", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "source-authority-audit" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.explain_query_expression", ++ "canonical_projection": "single_object:root", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Explain query DSL parsing and lowering through the shared grammar.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "expression" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.explain_query_expression:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:query-plan", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.explain_query_expression" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.explain_query_expression" ++ } ++ ], ++ "declaration_id": "mcp.tool.explain_query_expression", ++ "discovery_text": "Explain query DSL parsing and lowering through the shared grammar.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "expression", ++ "repo:polylogue" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for explain_query_expression." ++ } ++ ], ++ "family_id": "mcp.tool.explain_query_expression", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:explain_query_expression", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "explain_query_expression" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/explain_query_expression" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.explain_query_expression", ++ "public_name": "explain_query_expression", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.explain_query_expression:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "expression": "repo:polylogue" ++ }, ++ "minimum_role": "read", ++ "name": "explain_query_expression", ++ "object_kinds": [ ++ "query-plan" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.explain_query_expression", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.explain_query_expression", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "explain_query_expression" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "explain_query_expression", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "single_object:root", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Compute scoped and global facet aggregates over the archive. Filter parameters mirror ``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries ``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency weights for each value.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:facet,session-query", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.facets" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.facets" ++ } ++ ], ++ "declaration_id": "mcp.tool.facets", ++ "discovery_text": "Compute scoped and global facet aggregates over the archive. Filter parameters mirror ``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries ``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency weights for each value.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for facets." ++ } ++ ], ++ "family_id": "mcp.tool.facets", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:facets", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "facets" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/facets" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "facets", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "facets", ++ "object_kinds": [ ++ "facet", ++ "session-query" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "facets" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "facets", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.find_abandoned_sessions", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Find sessions whose terminal state indicates dangling work.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.find_abandoned_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.find_abandoned_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.find_abandoned_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.find_abandoned_sessions", ++ "discovery_text": "Find sessions whose terminal state indicates dangling work.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for find_abandoned_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.find_abandoned_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:find_abandoned_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "find_abandoned_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/find_abandoned_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.find_abandoned_sessions", ++ "public_name": "find_abandoned_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.find_abandoned_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "find_abandoned_sessions", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.find_abandoned_sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.find_abandoned_sessions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "find_abandoned_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "find_abandoned_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.find_resume_candidates", ++ "canonical_projection": "envelope:candidates,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Rank logical sessions likely to match the operator's current context.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "repo_path" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.find_resume_candidates:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.find_resume_candidates" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.find_resume_candidates" ++ } ++ ], ++ "declaration_id": "mcp.tool.find_resume_candidates", ++ "discovery_text": "Rank logical sessions likely to match the operator's current context.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "repo_path", ++ "/tmp/nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for find_resume_candidates." ++ } ++ ], ++ "family_id": "mcp.tool.find_resume_candidates", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:find_resume_candidates", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "find_resume_candidates" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/find_resume_candidates" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.find_resume_candidates", ++ "public_name": "find_resume_candidates", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.find_resume_candidates:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "repo_path": "/tmp/nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "find_resume_candidates", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.find_resume_candidates", ++ "output_contract": { ++ "envelope_fields": [ ++ "candidates", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "resume_context" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.find_resume_candidates", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "find_resume_candidates" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "find_resume_candidates", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "resume-context" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ "canonical_projection": "single_object:root", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Find sessions similar to a given session.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.find_similar_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:top_k:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.find_similar_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.find_similar_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.find_similar_sessions", ++ "discovery_text": "Find sessions similar to a given session.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ], ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for find_similar_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.find_similar_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:find_similar_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "find_similar_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/find_similar_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ "public_name": "find_similar_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.find_similar_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "find_similar_sessions", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "find_similar_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "find_similar_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Find sessions with origin tool calls bounded as stuck.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.find_stuck_sessions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.find_stuck_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.find_stuck_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.find_stuck_sessions", ++ "discovery_text": "Find sessions with origin tool calls bounded as stuck.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for find_stuck_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.find_stuck_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:find_stuck_sessions", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "find_stuck_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/find_stuck_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ "public_name": "find_stuck_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.find_stuck_sessions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "find_stuck_sessions", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "find_stuck_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "find_stuck_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_context_delivery", ++ "canonical_projection": "single_object:root", ++ "capability": "read:context", ++ "compatibility_route": "target:context(seed, policy, budget)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Read the exact persisted context image for its recorded recipient.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "snapshot_ref", ++ "recipient_ref" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_context_tools.get_context_delivery:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "context:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:context-delivery,context-snapshot,agent-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_context_delivery" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_context_delivery" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_context_delivery", ++ "discovery_text": "Read the exact persisted context image for its recorded recipient.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "recipient_ref", ++ "agent:discovery" ++ ], ++ [ ++ "snapshot_ref", ++ "context-snapshot:test:conv-discovery-nonexistent:explicit-recall" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_context_delivery." ++ } ++ ], ++ "family_id": "mcp.tool.get_context_delivery", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_context_tools:get_context_delivery", ++ "owner_path": "polylogue/mcp/server_context_tools.py", ++ "surface": "mcp", ++ "symbol": "get_context_delivery" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_context_delivery" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_context_delivery", ++ "public_name": "get_context_delivery", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_context_tools.get_context_delivery:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "recipient_ref": "agent:discovery", ++ "snapshot_ref": "context-snapshot:test:conv-discovery-nonexistent:explicit-recall" ++ }, ++ "minimum_role": "read", ++ "name": "get_context_delivery", ++ "object_kinds": [ ++ "context-delivery", ++ "context-snapshot", ++ "agent-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_context_delivery", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_context_delivery", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_context_tools", ++ "registrar": "register_context_tools", ++ "symbol": "get_context_delivery" ++ }, ++ "resource_alternatives": [ ++ "polylogue://context-snapshot/{id}" ++ ], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_context_delivery", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "context", ++ "workflow_coverage": [ ++ "continuity-handoff", ++ "bounded-context-delivery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_logical_session", ++ "canonical_projection": "envelope:cycle_detected,descendants,root_id,session_id,siblings,thread", ++ "capability": "read:get", ++ "compatibility_route": "target:graph(ref, direction, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{graph-hash}:{cursor}", ++ "exhaustive_route": "target:graph(ref, direction, page)", ++ "mode": "recursive_page", ++ "notes": "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return compact logical-session lineage for read-pull consumers.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_logical_session:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "get:recursive_graph:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,thread", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_logical_session" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_logical_session" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_logical_session", ++ "discovery_text": "Return compact logical-session lineage for read-pull consumers.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_logical_session." ++ } ++ ], ++ "family_id": "mcp.tool.get_logical_session", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_logical_session", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_logical_session" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_logical_session" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_logical_session", ++ "public_name": "get_logical_session", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_logical_session:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_logical_session", ++ "object_kinds": [ ++ "session", ++ "thread" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_logical_session", ++ "output_contract": { ++ "envelope_fields": [ ++ "cycle_detected", ++ "descendants", ++ "root_id", ++ "session_id", ++ "siblings", ++ "thread" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_logical_session", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_logical_session" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "recursive_graph", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_logical_session", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "get", ++ "workflow_coverage": [ ++ "topology-reconstruction", ++ "lineage-navigation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.mcp.archive_support.archive_messages_payload", ++ "canonical_projection": "envelope:messages,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return a filtered message page.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_messages:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:message,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_messages" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_messages" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_messages", ++ "discovery_text": "Return a filtered message page.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_messages." ++ } ++ ], ++ "family_id": "mcp.tool.get_messages", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_messages", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_messages" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_messages" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.mcp.archive_support.archive_messages_payload", ++ "public_name": "get_messages", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_messages:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_messages", ++ "object_kinds": [ ++ "message", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.mcp.archive_support.archive_messages_payload", ++ "output_contract": { ++ "envelope_fields": [ ++ "messages", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_messages" ++ }, ++ "resource_alternatives": [ ++ "polylogue://message/{id}", ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_messages", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_metadata", ++ "canonical_projection": "single_object:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get metadata.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.get_metadata:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:single_object:single_object", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-metadata", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_metadata" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_metadata" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_metadata", ++ "discovery_text": "Get metadata.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_metadata." ++ } ++ ], ++ "family_id": "mcp.tool.get_metadata", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:get_metadata", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "get_metadata" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_metadata" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_metadata", ++ "public_name": "get_metadata", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.get_metadata:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "get_metadata", ++ "object_kinds": [ ++ "session-metadata" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_metadata", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_metadata", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "get_metadata" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "get_metadata", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.pathology_report", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Agent-workflow pathology distribution over a matched scope (#2383).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.get_pathologies:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:pathology-report", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_pathologies" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_pathologies" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_pathologies", ++ "discovery_text": "Agent-workflow pathology distribution over a matched scope (#2383).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_pathologies." ++ } ++ ], ++ "family_id": "mcp.tool.get_pathologies", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:get_pathologies", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "get_pathologies" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_pathologies" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.pathology_report", ++ "public_name": "get_pathologies", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.get_pathologies:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "get_pathologies", ++ "object_kinds": [ ++ "pathology-report" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.pathology_report", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "postmortem_last" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.pathology_report", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "get_pathologies" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_pathologies", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "failure-postmortem" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.postmortem_bundle", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Distilled postmortem bundle over a matched session scope (#2380).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.get_postmortem_bundle:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:postmortem-bundle", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_postmortem_bundle" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_postmortem_bundle" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_postmortem_bundle", ++ "discovery_text": "Distilled postmortem bundle over a matched session scope (#2380).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_postmortem_bundle." ++ } ++ ], ++ "family_id": "mcp.tool.get_postmortem_bundle", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:get_postmortem_bundle", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "get_postmortem_bundle" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_postmortem_bundle" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.postmortem_bundle", ++ "public_name": "get_postmortem_bundle", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.get_postmortem_bundle:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "get_postmortem_bundle", ++ "object_kinds": [ ++ "postmortem-bundle" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.postmortem_bundle", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "postmortem_last" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.postmortem_bundle", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "get_postmortem_bundle" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_postmortem_bundle", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "failure-postmortem" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.resume_brief", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get a typed resume brief for one archived session.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.get_resume_brief:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:resume-brief", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_resume_brief" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_resume_brief" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_resume_brief", ++ "discovery_text": "Get a typed resume brief for one archived session.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_resume_brief." ++ } ++ ], ++ "family_id": "mcp.tool.get_resume_brief", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:get_resume_brief", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "get_resume_brief" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_resume_brief" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.resume_brief", ++ "public_name": "get_resume_brief", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.get_resume_brief:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_resume_brief", ++ "object_kinds": [ ++ "resume-brief" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.resume_brief", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "resume_context" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.resume_brief", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "get_resume_brief" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_resume_brief", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "resume-context" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.get_summary", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get session summary.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_session_summary:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_session_summary" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_session_summary" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_session_summary", ++ "discovery_text": "Get session summary.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_session_summary." ++ } ++ ], ++ "family_id": "mcp.tool.get_session_summary", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_session_summary", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_session_summary" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_session_summary" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.get_summary", ++ "public_name": "get_session_summary", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_session_summary:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_session_summary", ++ "object_kinds": [ ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.get_summary", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_session_summary" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_session_summary", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session_topology", ++ "canonical_projection": "envelope:ancestors,descendants,edges,nodes,root_id,siblings,target_id,thread", ++ "capability": "read:get", ++ "compatibility_route": "target:graph(ref, direction, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{graph-hash}:{cursor}", ++ "exhaustive_route": "target:graph(ref, direction, page)", ++ "mode": "recursive_page", ++ "notes": "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return typed lineage topology (ancestors/descendants/siblings/thread).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_session_topology:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "get:recursive_graph:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,topology-edge", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_session_topology" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_session_topology" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_session_topology", ++ "discovery_text": "Return typed lineage topology (ancestors/descendants/siblings/thread).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_session_topology." ++ } ++ ], ++ "family_id": "mcp.tool.get_session_topology", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_session_topology", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_session_topology" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_session_topology" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session_topology", ++ "public_name": "get_session_topology", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_session_topology:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_session_topology", ++ "object_kinds": [ ++ "session", ++ "topology-edge" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session_topology", ++ "output_contract": { ++ "envelope_fields": [ ++ "ancestors", ++ "descendants", ++ "edges", ++ "nodes", ++ "root_id", ++ "siblings", ++ "target_id", ++ "thread" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session_topology", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_session_topology" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "recursive_graph", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_session_topology", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "get", ++ "workflow_coverage": [ ++ "topology-reconstruction", ++ "lineage-navigation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session_tree", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:get", ++ "compatibility_route": "target:graph(ref, direction, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{graph-hash}:{cursor}", ++ "exhaustive_route": "target:graph(ref, direction, page)", ++ "mode": "recursive_page", ++ "notes": "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get session tree.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_session_tree:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "get:recursive_graph:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,lineage-edge", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_session_tree" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_session_tree" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_session_tree", ++ "discovery_text": "Get session tree.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_session_tree." ++ } ++ ], ++ "family_id": "mcp.tool.get_session_tree", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_session_tree", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_session_tree" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_session_tree" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session_tree", ++ "public_name": "get_session_tree", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_session_tree:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "get_session_tree", ++ "object_kinds": [ ++ "session", ++ "lineage-edge" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session_tree", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session_tree", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_session_tree" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "recursive_graph", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_session_tree", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "get", ++ "workflow_coverage": [ ++ "topology-reconstruction", ++ "lineage-navigation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_stats_by", ++ "canonical_projection": "stats_map:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Session counts grouped by a calendar/origin dimension.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.get_stats_by:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:aggregate:stats_map", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:archive-stats", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.get_stats_by" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.get_stats_by" ++ } ++ ], ++ "declaration_id": "mcp.tool.get_stats_by", ++ "discovery_text": "Session counts grouped by a calendar/origin dimension.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "group_by", ++ "origin" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for get_stats_by." ++ } ++ ], ++ "family_id": "mcp.tool.get_stats_by", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:get_stats_by", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "get_stats_by" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "stats_map", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/get_stats_by" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_stats_by", ++ "public_name": "get_stats_by", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.get_stats_by:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "group_by": "origin" ++ }, ++ "minimum_role": "read", ++ "name": "get_stats_by", ++ "object_kinds": [ ++ "archive-stats" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_stats_by", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "stats_map", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_stats_by", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "get_stats_by" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "get_stats_by", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.import_annotation_batch", ++ "canonical_projection": "single_object:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Import bounded JSONL labels as provenance-stamped candidates.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "jsonl", ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "target_ref", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.import_annotation_batch:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:single_object", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:annotation-batch,assertion", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.import_annotation_batch" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.import_annotation_batch" ++ } ++ ], ++ "declaration_id": "mcp.tool.import_annotation_batch", ++ "discovery_text": "Import bounded JSONL labels as provenance-stamped candidates.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "actor_ref", ++ "agent:contract" ++ ], ++ [ ++ "batch_id", ++ "contract-batch" ++ ], ++ [ ++ "jsonl", ++ "{\"row_key\":\"r1\",\"value\":{},\"evidence_refs\":[]}" ++ ], ++ [ ++ "model_ref", ++ "agent:model" ++ ], ++ [ ++ "prompt_ref", ++ "block:contract:0" ++ ], ++ [ ++ "schema_id", ++ "delegation.discourse" ++ ], ++ [ ++ "schema_version", ++ 1 ++ ], ++ [ ++ "source_result_ref", ++ "result-set:contract" ++ ], ++ [ ++ "target_ref", ++ "delegation:contract-target" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for import_annotation_batch." ++ } ++ ], ++ "family_id": "mcp.tool.import_annotation_batch", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:import_annotation_batch", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "import_annotation_batch" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/import_annotation_batch" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.import_annotation_batch", ++ "public_name": "import_annotation_batch", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.import_annotation_batch:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "actor_ref": "agent:contract", ++ "batch_id": "contract-batch", ++ "jsonl": "{\"row_key\":\"r1\",\"value\":{},\"evidence_refs\":[]}", ++ "model_ref": "agent:model", ++ "prompt_ref": "block:contract:0", ++ "schema_id": "delegation.discourse", ++ "schema_version": 1, ++ "source_result_ref": "result-set:contract", ++ "target_ref": "delegation:contract-target" ++ }, ++ "minimum_role": "write", ++ "name": "import_annotation_batch", ++ "object_kinds": [ ++ "annotation-batch", ++ "assertion" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.import_annotation_batch", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.import_annotation_batch", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "import_annotation_batch" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "import_annotation_batch", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.insight_rigor_audit", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Per-product rigor profile across materialized insights (#1275).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.insight_rigor_audit:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:rigor-report", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.insight_rigor_audit" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.insight_rigor_audit" ++ } ++ ], ++ "declaration_id": "mcp.tool.insight_rigor_audit", ++ "discovery_text": "Per-product rigor profile across materialized insights (#1275).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for insight_rigor_audit." ++ } ++ ], ++ "family_id": "mcp.tool.insight_rigor_audit", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:insight_rigor_audit", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "insight_rigor_audit" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/insight_rigor_audit" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.insight_rigor_audit", ++ "public_name": "insight_rigor_audit", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.insight_rigor_audit:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "insight_rigor_audit", ++ "object_kinds": [ ++ "rigor-report" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.insight_rigor_audit", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.insight_rigor_audit", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "insight_rigor_audit" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "insight_rigor_audit", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.join_typed_annotations", ++ "canonical_projection": "envelope:groups,joined_count,rows,selected_annotation_count", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "Join typed annotation labels to exact structural targets.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "schema_id", ++ "schema_version", ++ "statuses" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.join_typed_annotations:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:annotation,object-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.join_typed_annotations" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.join_typed_annotations" ++ } ++ ], ++ "declaration_id": "mcp.tool.join_typed_annotations", ++ "discovery_text": "Join typed annotation labels to exact structural targets.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "schema_id", ++ "discovery" ++ ], ++ [ ++ "schema_version", ++ 1 ++ ], ++ [ ++ "statuses", ++ [ ++ "candidate" ++ ] ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for join_typed_annotations." ++ } ++ ], ++ "family_id": "mcp.tool.join_typed_annotations", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:join_typed_annotations", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "join_typed_annotations" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/join_typed_annotations" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.join_typed_annotations", ++ "public_name": "join_typed_annotations", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.join_typed_annotations:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "schema_id": "discovery", ++ "schema_version": 1, ++ "statuses": [ ++ "candidate" ++ ] ++ }, ++ "minimum_role": "read", ++ "name": "join_typed_annotations", ++ "object_kinds": [ ++ "annotation", ++ "object-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.join_typed_annotations", ++ "output_contract": { ++ "envelope_fields": [ ++ "groups", ++ "joined_count", ++ "rows", ++ "selected_annotation_count" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.join_typed_annotations", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "join_typed_annotations" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "join_typed_annotations", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "canonical_projection": "envelope:applied_count,failed_count,items", ++ "capability": "review:judge", ++ "compatibility_route": "target:judge(candidate-ref, decision, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Judge one candidate; caller text is provenance, not authorization.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "candidate_ref", ++ "decision" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.judge_assertion_candidate:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "judge:mutation:envelope", ++ "authority": "mcp-role:review", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion-candidate,judgment", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.judge_assertion_candidate" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.judge_assertion_candidate" ++ } ++ ], ++ "declaration_id": "mcp.tool.judge_assertion_candidate", ++ "discovery_text": "Judge one candidate; caller text is provenance, not authorization.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "candidate_ref", ++ "assertion:contract-candidate" ++ ], ++ [ ++ "decision", ++ "accept" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for judge_assertion_candidate." ++ } ++ ], ++ "family_id": "mcp.tool.judge_assertion_candidate", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:judge_assertion_candidate", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "judge_assertion_candidate" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/judge_assertion_candidate" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "public_name": "judge_assertion_candidate", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:review", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.judge_assertion_candidate:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "candidate_ref": "assertion:contract-candidate", ++ "decision": "accept" ++ }, ++ "minimum_role": "review", ++ "name": "judge_assertion_candidate", ++ "object_kinds": [ ++ "assertion-candidate", ++ "judgment" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "output_contract": { ++ "envelope_fields": [ ++ "applied_count", ++ "failed_count", ++ "items" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_assertion_review_tools", ++ "symbol": "judge_assertion_candidate" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "judge_assertion_candidate", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "judge", ++ "workflow_coverage": [ ++ "assertion-review" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "canonical_projection": "envelope:applied_count,failed_count,items", ++ "capability": "review:judge", ++ "compatibility_route": "target:judge(candidate-ref, decision, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Bulk judge candidates with independently reported partial success.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "items" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.judge_assertion_candidates:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "judge:mutation:envelope", ++ "authority": "mcp-role:review", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion-candidate,judgment", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.judge_assertion_candidates" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.judge_assertion_candidates" ++ } ++ ], ++ "declaration_id": "mcp.tool.judge_assertion_candidates", ++ "discovery_text": "Bulk judge candidates with independently reported partial success.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "items", ++ [ ++ { ++ "candidate_ref": "assertion:contract-candidate", ++ "decision": "accept" ++ } ++ ] ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for judge_assertion_candidates." ++ } ++ ], ++ "family_id": "mcp.tool.judge_assertion_candidates", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:judge_assertion_candidates", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "judge_assertion_candidates" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/judge_assertion_candidates" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "public_name": "judge_assertion_candidates", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:review", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.judge_assertion_candidates:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "items": [ ++ { ++ "candidate_ref": "assertion:contract-candidate", ++ "decision": "accept" ++ } ++ ] ++ }, ++ "minimum_role": "review", ++ "name": "judge_assertion_candidates", ++ "object_kinds": [ ++ "assertion-candidate", ++ "judgment" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "output_contract": { ++ "envelope_fields": [ ++ "applied_count", ++ "failed_count", ++ "items" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_assertion_review_tools", ++ "symbol": "judge_assertion_candidates" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "judge_assertion_candidates", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "judge", ++ "workflow_coverage": [ ++ "assertion-review" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_annotations", ++ "canonical_projection": "envelope:items,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List annotations.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.list_annotations:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:annotation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_annotations" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_annotations" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_annotations", ++ "discovery_text": "List annotations.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_annotations." ++ } ++ ], ++ "family_id": "mcp.tool.list_annotations", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:list_annotations", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "list_annotations" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_annotations" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_annotations", ++ "public_name": "list_annotations", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.list_annotations:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_annotations", ++ "object_kinds": [ ++ "annotation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_annotations", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_annotations", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "list_annotations" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_annotations", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ "canonical_projection": "envelope:items,limit,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List durable candidate review history separately from active claims.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.list_assertion_candidate_reviews:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion-review", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_assertion_candidate_reviews" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_assertion_candidate_reviews" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_assertion_candidate_reviews", ++ "discovery_text": "List durable candidate review history separately from active claims.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_assertion_candidate_reviews." ++ } ++ ], ++ "family_id": "mcp.tool.list_assertion_candidate_reviews", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:list_assertion_candidate_reviews", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "list_assertion_candidate_reviews" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_assertion_candidate_reviews" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ "public_name": "list_assertion_candidate_reviews", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.list_assertion_candidate_reviews:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "list_assertion_candidate_reviews", ++ "object_kinds": [ ++ "assertion-review" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "list_assertion_candidate_reviews" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "list_assertion_candidate_reviews", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_assertion_candidates", ++ "canonical_projection": "envelope:items,limit,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List pending assertion candidates without promoting them.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.list_assertion_candidates:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion-candidate", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_assertion_candidates" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_assertion_candidates" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_assertion_candidates", ++ "discovery_text": "List pending assertion candidates without promoting them.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_assertion_candidates." ++ } ++ ], ++ "family_id": "mcp.tool.list_assertion_candidates", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:list_assertion_candidates", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "list_assertion_candidates" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_assertion_candidates" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_assertion_candidates", ++ "public_name": "list_assertion_candidates", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.list_assertion_candidates:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "list_assertion_candidates", ++ "object_kinds": [ ++ "assertion-candidate" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_assertion_candidates", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_assertion_candidates", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "list_assertion_candidates" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "list_assertion_candidates", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ "canonical_projection": "envelope:items,limit,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List assertion-backed lifecycle claims.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.list_assertion_claims:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:assertion", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_assertion_claims" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_assertion_claims" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_assertion_claims", ++ "discovery_text": "List assertion-backed lifecycle claims.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "target_ref", ++ "session:test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_assertion_claims." ++ } ++ ], ++ "family_id": "mcp.tool.list_assertion_claims", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:list_assertion_claims", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "list_assertion_claims" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_assertion_claims" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ "public_name": "list_assertion_claims", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.list_assertion_claims:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "target_ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "list_assertion_claims", ++ "object_kinds": [ ++ "assertion" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "list_assertion_claims" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "list_assertion_claims", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_corrections", ++ "canonical_projection": "envelope:corrections,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List stored learning corrections.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.list_corrections:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:correction", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_corrections" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_corrections" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_corrections", ++ "discovery_text": "List stored learning corrections.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_corrections." ++ } ++ ], ++ "family_id": "mcp.tool.list_corrections", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:list_corrections", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "list_corrections" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_corrections" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_corrections", ++ "public_name": "list_corrections", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.list_corrections:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_corrections", ++ "object_kinds": [ ++ "correction" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_corrections", ++ "output_contract": { ++ "envelope_fields": [ ++ "corrections", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_corrections", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "list_corrections" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_corrections", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_marks", ++ "canonical_projection": "envelope:items,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List marks.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.list_marks:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:mark", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_marks" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_marks" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_marks", ++ "discovery_text": "List marks.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_marks." ++ } ++ ], ++ "family_id": "mcp.tool.list_marks", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:list_marks", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "list_marks" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_marks" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_marks", ++ "public_name": "list_marks", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.list_marks:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_marks", ++ "object_kinds": [ ++ "mark" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_marks", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_marks", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "list_marks" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_marks", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_read_view_profiles", ++ "canonical_projection": "envelope:read_views,total", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:explain(ref-or-plan)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List executable read-view profile metadata for agents.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.list_read_view_profiles:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:read-view-profile", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_read_view_profiles" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_read_view_profiles" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_read_view_profiles", ++ "discovery_text": "List executable read-view profile metadata for agents.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_read_view_profiles." ++ } ++ ], ++ "family_id": "mcp.tool.list_read_view_profiles", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:list_read_view_profiles", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "list_read_view_profiles" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_read_view_profiles" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_read_view_profiles", ++ "public_name": "list_read_view_profiles", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.list_read_view_profiles:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "list_read_view_profiles", ++ "object_kinds": [ ++ "read-view-profile" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_read_view_profiles", ++ "output_contract": { ++ "envelope_fields": [ ++ "read_views", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_read_view_profiles", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "list_read_view_profiles" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "list_read_view_profiles", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_recall_packs", ++ "canonical_projection": "envelope:items,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List recall packs.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.list_recall_packs:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:recall-pack", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_recall_packs" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_recall_packs" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_recall_packs", ++ "discovery_text": "List recall packs.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_recall_packs." ++ } ++ ], ++ "family_id": "mcp.tool.list_recall_packs", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:list_recall_packs", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "list_recall_packs" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_recall_packs" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_recall_packs", ++ "public_name": "list_recall_packs", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.list_recall_packs:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_recall_packs", ++ "object_kinds": [ ++ "recall-pack" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_recall_packs", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_recall_packs", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "list_recall_packs" ++ }, ++ "resource_alternatives": [ ++ "polylogue://recall-pack/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_recall_packs", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_views", ++ "canonical_projection": "envelope:items,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List saved views.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.list_saved_views:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:saved-query", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_saved_views" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_saved_views" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_saved_views", ++ "discovery_text": "List saved views.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_saved_views." ++ } ++ ], ++ "family_id": "mcp.tool.list_saved_views", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:list_saved_views", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "list_saved_views" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_saved_views" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_views", ++ "public_name": "list_saved_views", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.list_saved_views:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_saved_views", ++ "object_kinds": [ ++ "saved-query" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_views", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_views", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "list_saved_views" ++ }, ++ "resource_alternatives": [ ++ "polylogue://saved-query/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_saved_views", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,result-set", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_sessions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_sessions" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_sessions", ++ "discovery_text": "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_sessions." ++ } ++ ], ++ "family_id": "mcp.tool.list_sessions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:list_sessions", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "list_sessions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_sessions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "list_sessions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "list_sessions", ++ "object_kinds": [ ++ "session", ++ "result-set" ++ ], ++ "observed_use": "observed", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "list_sessions" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}", ++ "polylogue://result-set/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "list_sessions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_tags", ++ "canonical_projection": "stats_map:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List deterministic tag-count pages without expanding a large tag map.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.list_tags:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:stats_map", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tag", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_tags" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_tags" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_tags", ++ "discovery_text": "List deterministic tag-count pages without expanding a large tag map.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_tags." ++ } ++ ], ++ "family_id": "mcp.tool.list_tags", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:list_tags", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "list_tags" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "stats_map", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_tags" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_tags", ++ "public_name": "list_tags", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.list_tags:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_tags", ++ "object_kinds": [ ++ "tag" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_tags", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "stats_map", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_tags", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "list_tags" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_tags", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_workspaces", ++ "canonical_projection": "envelope:items,total", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:write(operation, object-ref, authorization)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List workspaces.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.list_workspaces:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:exhaustive_page:envelope", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:reader-workspace", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.list_workspaces" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.list_workspaces" ++ } ++ ], ++ "declaration_id": "mcp.tool.list_workspaces", ++ "discovery_text": "List workspaces.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for list_workspaces." ++ } ++ ], ++ "family_id": "mcp.tool.list_workspaces", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:list_workspaces", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "list_workspaces" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/list_workspaces" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_workspaces", ++ "public_name": "list_workspaces", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.list_workspaces:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "write", ++ "name": "list_workspaces", ++ "object_kinds": [ ++ "reader-workspace" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_workspaces", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_workspaces", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "list_workspaces" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "list_workspaces", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.maintenance.planner.execute_backfill", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Run (or dry-run) maintenance backfill targets.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.maintenance_execute:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:maintenance-operation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.maintenance_execute" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.maintenance_execute" ++ } ++ ], ++ "declaration_id": "mcp.tool.maintenance_execute", ++ "discovery_text": "Run (or dry-run) maintenance backfill targets.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "dry_run", ++ true ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for maintenance_execute." ++ } ++ ], ++ "family_id": "mcp.tool.maintenance_execute", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:maintenance_execute", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "maintenance_execute" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/maintenance_execute" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.maintenance.planner.execute_backfill", ++ "public_name": "maintenance_execute", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.maintenance_execute:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "dry_run": true ++ }, ++ "minimum_role": "admin", ++ "name": "maintenance_execute", ++ "object_kinds": [ ++ "maintenance-operation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.maintenance.planner.execute_backfill", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "maintenance_execute" ++ }, ++ "resource_alternatives": [ ++ "polylogue://maintenance-operation/{id}" ++ ], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "maintenance_execute", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ "canonical_projection": "envelope:items,total", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List every persisted maintenance operation snapshot (#1197).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.maintenance_list:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:envelope", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:maintenance-operation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.maintenance_list" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.maintenance_list" ++ } ++ ], ++ "declaration_id": "mcp.tool.maintenance_list", ++ "discovery_text": "List every persisted maintenance operation snapshot (#1197).", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for maintenance_list." ++ } ++ ], ++ "family_id": "mcp.tool.maintenance_list", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:maintenance_list", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "maintenance_list" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/maintenance_list" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ "public_name": "maintenance_list", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.maintenance_list:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "admin", ++ "name": "maintenance_list", ++ "object_kinds": [ ++ "maintenance-operation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "maintenance_list" ++ }, ++ "resource_alternatives": [ ++ "polylogue://maintenance-operation/{id}" ++ ], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "maintenance_list", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.maintenance.planner.preview_backfill", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Dry-run summary of maintenance backfill targets.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.maintenance_preview:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:maintenance-plan", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.maintenance_preview" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.maintenance_preview" ++ } ++ ], ++ "declaration_id": "mcp.tool.maintenance_preview", ++ "discovery_text": "Dry-run summary of maintenance backfill targets.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for maintenance_preview." ++ } ++ ], ++ "family_id": "mcp.tool.maintenance_preview", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:maintenance_preview", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "maintenance_preview" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/maintenance_preview" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.maintenance.planner.preview_backfill", ++ "public_name": "maintenance_preview", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.maintenance_preview:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "admin", ++ "name": "maintenance_preview", ++ "object_kinds": [ ++ "maintenance-plan" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.maintenance.planner.preview_backfill", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "maintenance_preview" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "maintenance_preview", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return one persisted maintenance operation snapshot by id (#1197).", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "operation_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.maintenance_status:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:maintenance-operation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.maintenance_status" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.maintenance_status" ++ } ++ ], ++ "declaration_id": "mcp.tool.maintenance_status", ++ "discovery_text": "Return one persisted maintenance operation snapshot by id (#1197).", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "operation_id", ++ "operation:missing" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for maintenance_status." ++ } ++ ], ++ "family_id": "mcp.tool.maintenance_status", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:maintenance_status", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "maintenance_status" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/maintenance_status" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ "public_name": "maintenance_status", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.maintenance_status:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "operation_id": "operation:missing" ++ }, ++ "minimum_role": "admin", ++ "name": "maintenance_status", ++ "object_kinds": [ ++ "maintenance-operation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "maintenance_status" ++ }, ++ "resource_alternatives": [ ++ "polylogue://maintenance-operation/{id}" ++ ], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "maintenance_status", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.archive.query.source_freshness.project_named_source_freshness", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight convergence.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "source_path" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.named_source_freshness:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:source,freshness-receipt", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.named_source_freshness" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.named_source_freshness" ++ } ++ ], ++ "declaration_id": "mcp.tool.named_source_freshness", ++ "discovery_text": "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight convergence.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "source_path", ++ "/tmp/polylogue-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for named_source_freshness." ++ } ++ ], ++ "family_id": "mcp.tool.named_source_freshness", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:named_source_freshness", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "named_source_freshness" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/named_source_freshness" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.archive.query.source_freshness.project_named_source_freshness", ++ "public_name": "named_source_freshness", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.named_source_freshness:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "source_path": "/tmp/polylogue-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "named_source_freshness", ++ "object_kinds": [ ++ "source", ++ "freshness-receipt" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.archive.query.source_freshness.project_named_source_freshness", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "named_source_freshness" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "named_source_freshness", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "source-authority-audit" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.neighbor_candidates", ++ "canonical_projection": "envelope:items,limit,total", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Neighbor candidates.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.neighbor_candidates:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,embedding-neighbor", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.neighbor_candidates" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.neighbor_candidates" ++ } ++ ], ++ "declaration_id": "mcp.tool.neighbor_candidates", ++ "discovery_text": "Neighbor candidates.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "query", ++ "test" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for neighbor_candidates." ++ } ++ ], ++ "family_id": "mcp.tool.neighbor_candidates", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:neighbor_candidates", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "neighbor_candidates" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/neighbor_candidates" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.neighbor_candidates", ++ "public_name": "neighbor_candidates", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.neighbor_candidates:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "query": "test" ++ }, ++ "minimum_role": "read", ++ "name": "neighbor_candidates", ++ "object_kinds": [ ++ "session", ++ "embedding-neighbor" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.neighbor_candidates", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.neighbor_candidates", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "neighbor_candidates" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "neighbor_candidates", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.provider_usage_report", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return provider usage accounting diagnostics without estimating billing cost.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.provider_usage:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:usage-record", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.provider_usage" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.provider_usage" ++ } ++ ], ++ "declaration_id": "mcp.tool.provider_usage", ++ "discovery_text": "Return provider usage accounting diagnostics without estimating billing cost.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for provider_usage." ++ } ++ ], ++ "family_id": "mcp.tool.provider_usage", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:provider_usage", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "provider_usage" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/provider_usage" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.provider_usage_report", ++ "public_name": "provider_usage", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.provider_usage:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "provider_usage", ++ "object_kinds": [ ++ "usage-record" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.provider_usage_report", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.provider_usage_report", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "provider_usage" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "provider_usage", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.query_completions", ++ "canonical_projection": "single_object:root", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return shared query/action completion metadata for non-shell clients.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "kind" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.query_completions:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:query-vocabulary", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.query_completions" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.query_completions" ++ } ++ ], ++ "declaration_id": "mcp.tool.query_completions", ++ "discovery_text": "Return shared query/action completion metadata for non-shell clients.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "incomplete", ++ "d" ++ ], ++ [ ++ "kind", ++ "field" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for query_completions." ++ } ++ ], ++ "family_id": "mcp.tool.query_completions", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:query_completions", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "query_completions" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/query_completions" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.query_completions", ++ "public_name": "query_completions", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.query_completions:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "incomplete": "d", ++ "kind": "field" ++ }, ++ "minimum_role": "read", ++ "name": "query_completions", ++ "object_kinds": [ ++ "query-vocabulary" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.query_completions", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.query_completions", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "query_completions" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "query_completions", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "envelope:items,limit,offset,total,unit", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "Return terminal rows for explicit unit-source query expressions.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "expression" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.query_units:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:query,unit-row,result-set", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.query_units" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.query_units" ++ } ++ ], ++ "declaration_id": "mcp.tool.query_units", ++ "discovery_text": "Return terminal rows for explicit unit-source query expressions.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "expression", ++ "messages where text:hello" ++ ], ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for query_units." ++ } ++ ], ++ "family_id": "mcp.tool.query_units", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:query_units", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "query_units" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/query_units" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "query_units", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.query_units:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "expression": "messages where text:hello", ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "query_units", ++ "object_kinds": [ ++ "query", ++ "unit-row", ++ "result-set" ++ ], ++ "observed_use": "observed", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "limit", ++ "offset", ++ "total", ++ "unit" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "query_units" ++ }, ++ "resource_alternatives": [ ++ "polylogue://query/{id}", ++ "polylogue://result-set/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "query_units", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "canonical_projection": "envelope:raw_artifacts,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "cursor_or_offset", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "Raw artifacts.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.raw_artifacts:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:raw-artifact,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.raw_artifacts" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.raw_artifacts" ++ } ++ ], ++ "declaration_id": "mcp.tool.raw_artifacts", ++ "discovery_text": "Raw artifacts.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for raw_artifacts." ++ } ++ ], ++ "family_id": "mcp.tool.raw_artifacts", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:raw_artifacts", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "raw_artifacts" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/raw_artifacts" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "public_name": "raw_artifacts", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.raw_artifacts:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "raw_artifacts", ++ "object_kinds": [ ++ "raw-artifact", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "output_contract": { ++ "envelope_fields": [ ++ "raw_artifacts", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "raw_artifacts" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "raw_artifacts", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "source-authority-audit" ++ ] ++ }, ++ { ++ "canonical_plan": "project-archive-readiness", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Readiness check.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.readiness_check:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:readiness-report", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.readiness_check" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.readiness_check" ++ } ++ ], ++ "declaration_id": "mcp.tool.readiness_check", ++ "discovery_text": "Readiness check.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for readiness_check." ++ } ++ ], ++ "family_id": "mcp.tool.readiness_check", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:readiness_check", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "readiness_check" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/readiness_check" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "project-archive-readiness", ++ "public_name": "readiness_check", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.readiness_check:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "readiness_check", ++ "object_kinds": [ ++ "readiness-report" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "project-archive-readiness", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_read_tools", ++ "symbol": "readiness_check" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "readiness_check", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.rebuild_index", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Rebuild index.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.rebuild_index:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:index", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.rebuild_index" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.rebuild_index" ++ } ++ ], ++ "declaration_id": "mcp.tool.rebuild_index", ++ "discovery_text": "Rebuild index.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for rebuild_index." ++ } ++ ], ++ "family_id": "mcp.tool.rebuild_index", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:rebuild_index", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "rebuild_index" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/rebuild_index" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.rebuild_index", ++ "public_name": "rebuild_index", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.rebuild_index:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "admin", ++ "name": "rebuild_index", ++ "object_kinds": [ ++ "index" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.rebuild_index", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.rebuild_index", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "rebuild_index" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "rebuild_index", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "materialize-session-insights", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Rebuild session insights.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.rebuild_session_insights:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-insight,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.rebuild_session_insights" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.rebuild_session_insights" ++ } ++ ], ++ "declaration_id": "mcp.tool.rebuild_session_insights", ++ "discovery_text": "Rebuild session insights.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_ids", ++ [] ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for rebuild_session_insights." ++ } ++ ], ++ "family_id": "mcp.tool.rebuild_session_insights", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:rebuild_session_insights", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "rebuild_session_insights" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/rebuild_session_insights" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "materialize-session-insights", ++ "public_name": "rebuild_session_insights", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.rebuild_session_insights:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_ids": [] ++ }, ++ "minimum_role": "admin", ++ "name": "rebuild_session_insights", ++ "object_kinds": [ ++ "session-insight", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "materialize-session-insights", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.rebuild_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "rebuild_session_insights" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "rebuild_session_insights", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.record_correction", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Record a user correction targeting a derived insight.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "kind", ++ "payload" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.record_correction:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:correction,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.record_correction" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.record_correction" ++ } ++ ], ++ "declaration_id": "mcp.tool.record_correction", ++ "discovery_text": "Record a user correction targeting a derived insight.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "author_kind", ++ "agent" ++ ], ++ [ ++ "author_ref", ++ "agent:codex-session:correction" ++ ], ++ [ ++ "kind", ++ "summary_override" ++ ], ++ [ ++ "payload", ++ { ++ "summary": "Operator-authored summary." ++ } ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for record_correction." ++ } ++ ], ++ "family_id": "mcp.tool.record_correction", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:record_correction", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "record_correction" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/record_correction" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.record_correction", ++ "public_name": "record_correction", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.record_correction:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "author_kind": "agent", ++ "author_ref": "agent:codex-session:correction", ++ "kind": "summary_override", ++ "payload": { ++ "summary": "Operator-authored summary." ++ }, ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "record_correction", ++ "object_kinds": [ ++ "correction", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.record_correction", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.record_correction", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "record_correction" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "record_correction", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.remove_mark", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Remove mark.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "mark_type" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.remove_mark:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:mark,object-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.remove_mark" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.remove_mark" ++ } ++ ], ++ "declaration_id": "mcp.tool.remove_mark", ++ "discovery_text": "Remove mark.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "mark_type", ++ "star" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for remove_mark." ++ } ++ ], ++ "family_id": "mcp.tool.remove_mark", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:remove_mark", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "remove_mark" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/remove_mark" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.remove_mark", ++ "public_name": "remove_mark", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.remove_mark:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "mark_type": "star", ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "remove_mark", ++ "object_kinds": [ ++ "mark", ++ "object-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.remove_mark", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.remove_mark", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "remove_mark" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "remove_mark", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-remove-tag", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Remove tag.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "tag" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_mutation_tools.remove_tag:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tag,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.remove_tag" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.remove_tag" ++ } ++ ], ++ "declaration_id": "mcp.tool.remove_tag", ++ "discovery_text": "Remove tag.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ], ++ [ ++ "tag", ++ "review" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for remove_tag." ++ } ++ ], ++ "family_id": "mcp.tool.remove_tag", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_mutation_tools:remove_tag", ++ "owner_path": "polylogue/mcp/server_mutation_tools.py", ++ "surface": "mcp", ++ "symbol": "remove_tag" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/remove_tag" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-remove-tag", ++ "public_name": "remove_tag", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_mutation_tools.remove_tag:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-mutation", ++ "tag": "review" ++ }, ++ "minimum_role": "write", ++ "name": "remove_tag", ++ "object_kinds": [ ++ "tag", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-remove-tag", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.remove_tag", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_mutation_tools", ++ "registrar": "register_mutation_tools", ++ "symbol": "remove_tag" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "remove_tag", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.resolve_ref", ++ "canonical_projection": "single_object:root", ++ "capability": "read:explain", ++ "compatibility_route": "target:explain(ref-or-plan)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Resolve a public object/evidence ref into a bounded payload.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "ref" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.resolve_ref:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "explain:single_object:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:object-ref,evidence-ref", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.resolve_ref" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.resolve_ref" ++ } ++ ], ++ "declaration_id": "mcp.tool.resolve_ref", ++ "discovery_text": "Resolve a public object/evidence ref into a bounded payload.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "ref", ++ "session:test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for resolve_ref." ++ } ++ ], ++ "family_id": "mcp.tool.resolve_ref", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:resolve_ref", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "resolve_ref" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/resolve_ref" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.resolve_ref", ++ "public_name": "resolve_ref", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.resolve_ref:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "resolve_ref", ++ "object_kinds": [ ++ "object-ref", ++ "evidence-ref" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.resolve_ref", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.resolve_ref", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "resolve_ref" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "single_object", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "resolve_ref", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "explain", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.save_annotation", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Save annotation.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "annotation_id", ++ "session_id", ++ "note_text" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.save_annotation:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:annotation", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.save_annotation" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.save_annotation" ++ } ++ ], ++ "declaration_id": "mcp.tool.save_annotation", ++ "discovery_text": "Save annotation.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "annotation_id", ++ "ann-1" ++ ], ++ [ ++ "note_text", ++ "hello" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for save_annotation." ++ } ++ ], ++ "family_id": "mcp.tool.save_annotation", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:save_annotation", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "save_annotation" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/save_annotation" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.save_annotation", ++ "public_name": "save_annotation", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.save_annotation:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "annotation_id": "ann-1", ++ "note_text": "hello", ++ "session_id": "test:conv-mutation" ++ }, ++ "minimum_role": "write", ++ "name": "save_annotation", ++ "object_kinds": [ ++ "annotation" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.save_annotation", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.save_annotation", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "save_annotation" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "save_annotation", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.create_recall_pack", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Save recall pack.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "pack_id", ++ "label" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.save_recall_pack:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:recall-pack", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.save_recall_pack" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.save_recall_pack" ++ } ++ ], ++ "declaration_id": "mcp.tool.save_recall_pack", ++ "discovery_text": "Save recall pack.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "label", ++ "lbl" ++ ], ++ [ ++ "pack_id", ++ "pack-1" ++ ], ++ [ ++ "payload_json", ++ "{\"items\":[]}" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for save_recall_pack." ++ } ++ ], ++ "family_id": "mcp.tool.save_recall_pack", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:save_recall_pack", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "save_recall_pack" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/save_recall_pack" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.create_recall_pack", ++ "public_name": "save_recall_pack", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.save_recall_pack:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "label": "lbl", ++ "pack_id": "pack-1", ++ "payload_json": "{\"items\":[]}" ++ }, ++ "minimum_role": "write", ++ "name": "save_recall_pack", ++ "object_kinds": [ ++ "recall-pack" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.create_recall_pack", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.create_recall_pack", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "save_recall_pack" ++ }, ++ "resource_alternatives": [ ++ "polylogue://recall-pack/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "save_recall_pack", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.save_view", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Save saved view.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "name", ++ "query_json" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.save_saved_view:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:saved-query", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.save_saved_view" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.save_saved_view" ++ } ++ ], ++ "declaration_id": "mcp.tool.save_saved_view", ++ "discovery_text": "Save saved view.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "name", ++ "view-1" ++ ], ++ [ ++ "query_json", ++ "{}" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for save_saved_view." ++ } ++ ], ++ "family_id": "mcp.tool.save_saved_view", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:save_saved_view", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "save_saved_view" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/save_saved_view" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.save_view", ++ "public_name": "save_saved_view", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.save_saved_view:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "name": "view-1", ++ "query_json": "{}" ++ }, ++ "minimum_role": "write", ++ "name": "save_saved_view", ++ "object_kinds": [ ++ "saved-query" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.save_view", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.save_view", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "save_saved_view" ++ }, ++ "resource_alternatives": [ ++ "polylogue://saved-query/{id}" ++ ], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "save_saved_view", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.save_workspace", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Save workspace.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "workspace_id", ++ "name" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.save_workspace:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:reader-workspace", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.save_workspace" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.save_workspace" ++ } ++ ], ++ "declaration_id": "mcp.tool.save_workspace", ++ "discovery_text": "Save workspace.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "name", ++ "ws" ++ ], ++ [ ++ "workspace_id", ++ "ws-1" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for save_workspace." ++ } ++ ], ++ "family_id": "mcp.tool.save_workspace", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:save_workspace", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "save_workspace" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/save_workspace" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.save_workspace", ++ "public_name": "save_workspace", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.save_workspace:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "name": "ws", ++ "workspace_id": "ws-1" ++ }, ++ "minimum_role": "write", ++ "name": "save_workspace", ++ "object_kinds": [ ++ "reader-workspace" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.save_workspace", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.save_workspace", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "save_workspace" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "save_workspace", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "query-sessions", ++ "canonical_projection": "envelope:hits,total", ++ "capability": "read:query", ++ "compatibility_route": "target:query(plan, projection, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:query(plan, projection, page)", ++ "mode": "ranked_frontier", ++ "notes": "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists." ++ }, ++ "deprecation_state": "retained", ++ "description": "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "field_discovery": [ ++ "facets", ++ "query_completions" ++ ], ++ "grammar_discovery": [ ++ "query_completions", ++ "explain_query_expression" ++ ], ++ "incident_coverage": [ ++ "z9gh-workflow-incident" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "query" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "query:top_k:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session,query-result", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.search" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.search" ++ } ++ ], ++ "declaration_id": "mcp.tool.search", ++ "discovery_text": "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ], ++ [ ++ "query", ++ "hello" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for search." ++ } ++ ], ++ "family_id": "mcp.tool.search", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:search", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "search" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/search" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-sessions", ++ "public_name": "search", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.query_contracts.MCPSessionQueryRequest" ++ }, ++ "minimal_arguments": { ++ "limit": 1, ++ "query": "hello" ++ }, ++ "minimum_role": "read", ++ "name": "search", ++ "object_kinds": [ ++ "session", ++ "query-result" ++ ], ++ "observed_use": "observed", ++ "operation_owner": "query-sessions", ++ "output_contract": { ++ "envelope_fields": [ ++ "hits", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "search" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "top_k", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "search", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "query", ++ "workflow_coverage": [ ++ "phrase-search", ++ "structural-query", ++ "continuation-recovery" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_session_cost_insights", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List session costs from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionCostInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-cost", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_costs" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_costs" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_costs", ++ "discovery_text": "List session costs from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_costs." ++ } ++ ], ++ "family_id": "mcp.tool.session_costs", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_costs", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_costs" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_costs" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_session_cost_insights", ++ "public_name": "session_costs", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionCostInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "session_costs", ++ "object_kinds": [ ++ "session-cost" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_session_cost_insights", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [ ++ "cost_of" ++ ], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_session_cost_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_costs" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_costs", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get per-session latency profile by session ID.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.session_latency_profile:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:latency-profile", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_latency_profile" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_latency_profile" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_latency_profile", ++ "discovery_text": "Get per-session latency profile by session ID.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_latency_profile." ++ } ++ ], ++ "family_id": "mcp.tool.session_latency_profile", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_latency_profile", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_latency_profile" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_latency_profile" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ "public_name": "session_latency_profile", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.session_latency_profile:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "session_latency_profile", ++ "object_kinds": [ ++ "latency-profile" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_latency_profile" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_latency_profile", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-session-phases", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List session phases from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionPhaseInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-phase", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_phases" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_phases" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_phases", ++ "discovery_text": "List session phases from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_phases." ++ } ++ ], ++ "family_id": "mcp.tool.session_phases", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_phases", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_phases" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_phases" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-session-phases", ++ "public_name": "session_phases", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionPhaseInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "session_phases", ++ "object_kinds": [ ++ "session-phase" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-session-phases", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_session_phase_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_phases" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_phases", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session_profile_insight", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get a single session profile by session ID.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.session_profile:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-profile", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_profile" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_profile" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_profile", ++ "discovery_text": "Get a single session profile by session ID.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_profile." ++ } ++ ], ++ "family_id": "mcp.tool.session_profile", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_profile", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_profile" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_profile" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session_profile_insight", ++ "public_name": "session_profile", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.session_profile:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "session_profile", ++ "object_kinds": [ ++ "session-profile" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session_profile_insight", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session_profile_insight", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_profile" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_profile", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-session-profiles", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List session profiles from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionProfileInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-profile", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_profiles" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_profiles" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_profiles", ++ "discovery_text": "List session profiles from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_profiles." ++ } ++ ], ++ "family_id": "mcp.tool.session_profiles", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_profiles", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_profiles" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_profiles" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-session-profiles", ++ "public_name": "session_profiles", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionProfileInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "session_profiles", ++ "object_kinds": [ ++ "session-profile" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-session-profiles", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_session_profile_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_profiles" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_profiles", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-session-tag-rollups", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List session tag rollups from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionTagRollupQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tag-rollup", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_tag_rollups" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_tag_rollups" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_tag_rollups", ++ "discovery_text": "List session tag rollups from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_tag_rollups." ++ } ++ ], ++ "family_id": "mcp.tool.session_tag_rollups", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_tag_rollups", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_tag_rollups" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_tag_rollups" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-session-tag-rollups", ++ "public_name": "session_tag_rollups", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionTagRollupQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "session_tag_rollups", ++ "object_kinds": [ ++ "tag-rollup" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-session-tag-rollups", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_session_tag_rollup_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_tag_rollups" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_tag_rollups", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.get_session", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_context", ++ "notes": "This route is intentionally budgeted context/orientation, not an exhaustive archive relation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Get per-tool timing breakdown with evidence provenance.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.session_tool_timing:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:bounded_context:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tool-timing", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_tool_timing" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_tool_timing" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_tool_timing", ++ "discovery_text": "Get per-tool timing breakdown with evidence provenance.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_id", ++ "test:conv-discovery-nonexistent" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_tool_timing." ++ } ++ ], ++ "family_id": "mcp.tool.session_tool_timing", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_tool_timing", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_tool_timing" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_tool_timing" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.get_session", ++ "public_name": "session_tool_timing", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.session_tool_timing:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "minimum_role": "read", ++ "name": "session_tool_timing", ++ "object_kinds": [ ++ "tool-timing" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.get_session", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.get_session", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_tool_timing" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "bounded_context", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_tool_timing", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-session-work-events", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List work events from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionWorkEventInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:work-event", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.session_work_events" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.session_work_events" ++ } ++ ], ++ "declaration_id": "mcp.tool.session_work_events", ++ "discovery_text": "List work events from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for session_work_events." ++ } ++ ], ++ "family_id": "mcp.tool.session_work_events", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:session_work_events", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "session_work_events" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/session_work_events" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-session-work-events", ++ "public_name": "session_work_events", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionWorkEventInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "session_work_events", ++ "object_kinds": [ ++ "work-event" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-session-work-events", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_session_work_event_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "session_work_events" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "session_work_events", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "mutate-set-metadata", ++ "canonical_projection": "operation_result:root", ++ "capability": "write:write", ++ "compatibility_route": "target:write(operation, object-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Set metadata.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_id", ++ "key", ++ "value" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_personal_state_tools.set_metadata:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "write:mutation:operation_result", ++ "authority": "mcp-role:write", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:session-metadata", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.set_metadata" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.set_metadata" ++ } ++ ], ++ "declaration_id": "mcp.tool.set_metadata", ++ "discovery_text": "Set metadata.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "key", ++ "note" ++ ], ++ [ ++ "session_id", ++ "test:conv-mutation" ++ ], ++ [ ++ "value", ++ "v" ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for set_metadata." ++ } ++ ], ++ "family_id": "mcp.tool.set_metadata", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_personal_state_tools:set_metadata", ++ "owner_path": "polylogue/mcp/server_personal_state_tools.py", ++ "surface": "mcp", ++ "symbol": "set_metadata" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/set_metadata" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "mutate-set-metadata", ++ "public_name": "set_metadata", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:write", ++ "schema_ref": "polylogue.mcp.server_personal_state_tools.set_metadata:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "key": "note", ++ "session_id": "test:conv-mutation", ++ "value": "v" ++ }, ++ "minimum_role": "write", ++ "name": "set_metadata", ++ "object_kinds": [ ++ "session-metadata" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "mutate-set-metadata", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.set_metadata", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "registrar": "register_personal_state_tools", ++ "symbol": "set_metadata" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "mutation", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "set_metadata", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "write", ++ "workflow_coverage": [ ++ "user-state-mutation" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ "canonical_projection": "single_object:root", ++ "capability": "read:status", ++ "compatibility_route": "target:status(ref-or-domain)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Stats.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_tools.stats:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "status:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:archive-stats", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.stats" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.stats" ++ } ++ ], ++ "declaration_id": "mcp.tool.stats", ++ "discovery_text": "Stats.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for stats." ++ } ++ ], ++ "family_id": "mcp.tool.stats", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_tools:stats", ++ "owner_path": "polylogue/mcp/server_tools.py", ++ "surface": "mcp", ++ "symbol": "stats" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/stats" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ "public_name": "stats", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_tools.stats:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "stats", ++ "object_kinds": [ ++ "archive-stats" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": null, ++ "intentional_absence_authority": "polylogue-s1kr", ++ "reason": "The current MCP compatibility handler binds a lower-level owner or transport-only projection; polylogue-s1kr owns any public Python facade addition and docs parity." ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_tools", ++ "registrar": "register_query_tools", ++ "symbol": "stats" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "stats", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "status", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-threads", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": "result-set:{query-hash}:{cursor}", ++ "exhaustive_route": "target:read(ref, view, page)", ++ "mode": "bounded_page", ++ "notes": "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route." ++ }, ++ "deprecation_state": "retained", ++ "description": "List work threads from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ThreadInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:exhaustive_page:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:thread", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.threads" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.threads" ++ } ++ ], ++ "declaration_id": "mcp.tool.threads", ++ "discovery_text": "List work threads from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for threads." ++ } ++ ], ++ "family_id": "mcp.tool.threads", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:threads", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "threads" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/threads" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-threads", ++ "public_name": "threads", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ThreadInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "threads", ++ "object_kinds": [ ++ "thread" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-threads", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_thread_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "threads" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "exhaustive_page", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "threads", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.tool_call_latency_distribution", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Distribution of materialized per-session tool-call latency.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.tool_call_latency_distribution:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:latency-distribution", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.tool_call_latency_distribution" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.tool_call_latency_distribution" ++ } ++ ], ++ "declaration_id": "mcp.tool.tool_call_latency_distribution", ++ "discovery_text": "Distribution of materialized per-session tool-call latency.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for tool_call_latency_distribution." ++ } ++ ], ++ "family_id": "mcp.tool.tool_call_latency_distribution", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:tool_call_latency_distribution", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "tool_call_latency_distribution" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/tool_call_latency_distribution" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.tool_call_latency_distribution", ++ "public_name": "tool_call_latency_distribution", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.tool_call_latency_distribution:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "tool_call_latency_distribution", ++ "object_kinds": [ ++ "latency-distribution" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.tool_call_latency_distribution", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.tool_call_latency_distribution", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "tool_call_latency_distribution" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "tool_call_latency_distribution", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "query-tool-usage", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List tool usage from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ToolUsageInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:tool-usage", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.tool_usage" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.tool_usage" ++ } ++ ], ++ "declaration_id": "mcp.tool.tool_usage", ++ "discovery_text": "List tool usage from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for tool_usage." ++ } ++ ], ++ "family_id": "mcp.tool.tool_usage", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:tool_usage", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "tool_usage" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/tool_usage" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "query-tool-usage", ++ "public_name": "tool_usage", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ToolUsageInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "tool_usage", ++ "object_kinds": [ ++ "tool-usage" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "query-tool-usage", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_tool_usage_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "tool_usage" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "tool_usage", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.update_index", ++ "canonical_projection": "operation_result:root", ++ "capability": "admin:maintenance", ++ "compatibility_route": "target:maintenance(plan-or-operation-ref, authorization)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Update index.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [ ++ "session_ids" ++ ], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_maintenance_tools.update_index:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "maintenance:maintenance:operation_result", ++ "authority": "mcp-role:admin", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:index,session", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.update_index" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.update_index" ++ } ++ ], ++ "declaration_id": "mcp.tool.update_index", ++ "discovery_text": "Update index.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "session_ids", ++ [ ++ "test:conv-discovery-nonexistent" ++ ] ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for update_index." ++ } ++ ], ++ "family_id": "mcp.tool.update_index", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_maintenance_tools:update_index", ++ "owner_path": "polylogue/mcp/server_maintenance_tools.py", ++ "surface": "mcp", ++ "symbol": "update_index" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "operation_result", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/update_index" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.update_index", ++ "public_name": "update_index", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:admin", ++ "schema_ref": "polylogue.mcp.server_maintenance_tools.update_index:inspect.signature" ++ }, ++ "minimal_arguments": { ++ "session_ids": [ ++ "test:conv-discovery-nonexistent" ++ ] ++ }, ++ "minimum_role": "admin", ++ "name": "update_index", ++ "object_kinds": [ ++ "index", ++ "session" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.update_index", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "operation_result", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.update_index", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "registrar": "register_maintenance_tools", ++ "symbol": "update_index" ++ }, ++ "resource_alternatives": [ ++ "polylogue://session/{id}" ++ ], ++ "result_semantics": "maintenance", ++ "retirement_owner": "polylogue-t46.8.3", ++ "telemetry_key": "update_index", ++ "value_discovery": [ ++ "action_affordances" ++ ], ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance-lifecycle" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.list_usage_timeline_insights", ++ "canonical_projection": "envelope:items,total", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "List usage timeline from the archive.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(UsageTimelineInsightQuery)" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:envelope", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:usage-timeline", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.usage_timeline" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.usage_timeline" ++ } ++ ], ++ "declaration_id": "mcp.tool.usage_timeline", ++ "discovery_text": "List usage timeline from the archive.", ++ "examples": [ ++ { ++ "arguments": [ ++ [ ++ "limit", ++ 1 ++ ] ++ ], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for usage_timeline." ++ } ++ ], ++ "family_id": "mcp.tool.usage_timeline", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:usage_timeline", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "usage_timeline" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "envelope", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/usage_timeline" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.list_usage_timeline_insights", ++ "public_name": "usage_timeline", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(UsageTimelineInsightQuery)" ++ }, ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "minimum_role": "read", ++ "name": "usage_timeline", ++ "object_kinds": [ ++ "usage-timeline" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.list_usage_timeline_insights", ++ "output_contract": { ++ "envelope_fields": [ ++ "items", ++ "total" ++ ], ++ "kind": "envelope", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.list_usage_timeline_insights", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "usage_timeline" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "usage_timeline", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ }, ++ { ++ "canonical_plan": "polylogue.api.Polylogue.workflow_shape_distribution", ++ "canonical_projection": "single_object:root", ++ "capability": "read:read", ++ "compatibility_route": "target:read(ref, view, page)", ++ "continuation": { ++ "continuation_ref": null, ++ "exhaustive_route": null, ++ "mode": "none", ++ "notes": "The result is a single object, aggregate, or governed operation result with no row continuation." ++ }, ++ "deprecation_state": "retained", ++ "description": "Histogram session workflow shapes by week, origin, or project.", ++ "field_discovery": [ ++ "action_affordances" ++ ], ++ "grammar_discovery": [ ++ "action_affordances" ++ ], ++ "incident_coverage": [ ++ "mcp-contract-census" ++ ], ++ "input_contract": { ++ "required_arguments": [], ++ "schema_mode": "FastMCP derives inputSchema from the unchanged handler object/signature", ++ "schema_source": "polylogue.mcp.server_insight_tools.workflow_shape_distribution:inspect.signature" ++ }, ++ "kernel": { ++ "compatibility": { ++ "access_result_shape": "read:aggregate:single_object", ++ "authority": "mcp-role:read", ++ "durability": "transport-adapter; domain-owner-controls-durability", ++ "identity": "mcp-tool:workflow-shape", ++ "lifecycle": "registered-handler-retained" ++ }, ++ "completeness_edges": [ ++ { ++ "consumer": "tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ "kind": "discovery-name-equivalence", ++ "owner_path": "tests/infra/mcp.py", ++ "producer": "mcp.tool.workflow_shape_distribution" ++ }, ++ { ++ "consumer": "docs/generated/mcp-equivalence.json", ++ "kind": "generated-equivalence-row", ++ "owner_path": "devtools/render_mcp_equivalence.py", ++ "producer": "mcp.tool.workflow_shape_distribution" ++ } ++ ], ++ "declaration_id": "mcp.tool.workflow_shape_distribution", ++ "discovery_text": "Histogram session workflow shapes by week, origin, or project.", ++ "examples": [ ++ { ++ "arguments": [], ++ "name": "minimal-valid-call", ++ "summary": "Minimal compatibility invocation for workflow_shape_distribution." ++ } ++ ], ++ "family_id": "mcp.tool.workflow_shape_distribution", ++ "handlers": [ ++ { ++ "binding_key": "polylogue.mcp.server_insight_tools:workflow_shape_distribution", ++ "owner_path": "polylogue/mcp/server_insight_tools.py", ++ "surface": "mcp", ++ "symbol": "workflow_shape_distribution" ++ } ++ ], ++ "outputs": [ ++ { ++ "kind": "single_object", ++ "name": "runtime-contract", ++ "schema_ref": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ "target_path": "mcp://tool/workflow_shape_distribution" ++ }, ++ { ++ "kind": "generated-json", ++ "name": "equivalence-map", ++ "schema_ref": "polylogue.mcp.declarations.models.MCPToolDeclaration", ++ "target_path": "docs/generated/mcp-equivalence.json" ++ } ++ ], ++ "owner_path": "polylogue/mcp/declarations/registry.py", ++ "producer": "polylogue.api.Polylogue.workflow_shape_distribution", ++ "public_name": "workflow_shape_distribution", ++ "repair_command": "devtools render mcp-equivalence", ++ "role_gate": "mcp.role:read", ++ "schema_ref": "polylogue.mcp.server_insight_tools.workflow_shape_distribution:inspect.signature" ++ }, ++ "minimal_arguments": {}, ++ "minimum_role": "read", ++ "name": "workflow_shape_distribution", ++ "object_kinds": [ ++ "workflow-shape" ++ ], ++ "observed_use": "unknown", ++ "operation_owner": "polylogue.api.Polylogue.workflow_shape_distribution", ++ "output_contract": { ++ "envelope_fields": [], ++ "kind": "single_object", ++ "schema_source": "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ }, ++ "prompt_alternatives": [], ++ "python_parity": { ++ "binding": "polylogue.api.Polylogue.workflow_shape_distribution", ++ "intentional_absence_authority": null, ++ "reason": null ++ }, ++ "registration": { ++ "module": "polylogue.mcp.server_insight_tools", ++ "registrar": "register_insight_tools", ++ "symbol": "workflow_shape_distribution" ++ }, ++ "resource_alternatives": [], ++ "result_semantics": "aggregate", ++ "retirement_owner": "polylogue-t46.8.2", ++ "telemetry_key": "workflow_shape_distribution", ++ "value_discovery": [ ++ "query_completions" ++ ], ++ "verb": "read", ++ "workflow_coverage": [ ++ "archive-evidence-read" ++ ] ++ } ++ ] ++} +diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml +index f7f9c974216bc0c67829c306b658e87429bb1728..0aad63bb719ef1c9ec5e7673e657b53dae9af1a8 100644 +--- a/docs/plans/topology-target.yaml ++++ b/docs/plans/topology-target.yaml +@@ -1662,6 +1662,26 @@ files: + loc: 439 + target: polylogue/daemon/write_coordinator.py + owner: stable ++ - path: polylogue/declarations/__init__.py ++ loc: 42 ++ target: polylogue/declarations/__init__.py ++ owner: stable ++ - path: polylogue/declarations/derive.py ++ loc: 54 ++ target: polylogue/declarations/derive.py ++ owner: stable ++ - path: polylogue/declarations/models.py ++ loc: 123 ++ target: polylogue/declarations/models.py ++ owner: stable ++ - path: polylogue/declarations/registry.py ++ loc: 105 ++ target: polylogue/declarations/registry.py ++ owner: stable ++ - path: polylogue/declarations/validation.py ++ loc: 62 ++ target: polylogue/declarations/validation.py ++ owner: stable + - path: polylogue/demo/__init__.py + loc: 44 + target: polylogue/demo/__init__.py +@@ -2040,6 +2060,22 @@ files: + loc: 34 + target: polylogue/mcp/cli.py + owner: stable ++ - path: polylogue/mcp/declarations/__init__.py ++ loc: 57 ++ target: polylogue/mcp/declarations/__init__.py ++ owner: stable ++ - path: polylogue/mcp/declarations/adapter.py ++ loc: 175 ++ target: polylogue/mcp/declarations/adapter.py ++ owner: stable ++ - path: polylogue/mcp/declarations/models.py ++ loc: 225 ++ target: polylogue/mcp/declarations/models.py ++ owner: stable ++ - path: polylogue/mcp/declarations/registry.py ++ loc: 3525 ++ target: polylogue/mcp/declarations/registry.py ++ owner: stable + - path: polylogue/mcp/insight_tool_contracts.py + loc: 102 + target: polylogue/mcp/insight_tool_contracts.py +@@ -2057,27 +2093,27 @@ files: + target: polylogue/mcp/query_contracts.py + owner: stable + - path: polylogue/mcp/server.py +- loc: 93 ++ loc: 96 + target: polylogue/mcp/server.py + owner: stable + - path: polylogue/mcp/server_context_tools.py +- loc: 200 ++ loc: 199 + target: polylogue/mcp/server_context_tools.py + owner: stable + - path: polylogue/mcp/server_insight_tools.py +- loc: 791 ++ loc: 790 + target: polylogue/mcp/server_insight_tools.py + owner: stable + - path: polylogue/mcp/server_maintenance_tools.py +- loc: 304 ++ loc: 303 + target: polylogue/mcp/server_maintenance_tools.py + owner: stable + - path: polylogue/mcp/server_mutation_tools.py +- loc: 497 ++ loc: 496 + target: polylogue/mcp/server_mutation_tools.py + owner: stable + - path: polylogue/mcp/server_personal_state_tools.py +- loc: 675 ++ loc: 674 + target: polylogue/mcp/server_personal_state_tools.py + owner: stable + - path: polylogue/mcp/server_prompts.py +@@ -2093,7 +2129,7 @@ files: + target: polylogue/mcp/server_support.py + owner: stable + - path: polylogue/mcp/server_tools.py +- loc: 1325 ++ loc: 1302 + target: polylogue/mcp/server_tools.py + owner: stable + - path: polylogue/operations/__init__.py +@@ -3240,7 +3276,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_readiness.py +- loc: 818 ++ loc: 830 + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_views.py +@@ -3525,7 +3561,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_reconciler.py +- loc: 1411 ++ loc: 1422 + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_retention.py +diff --git a/docs/topology-status.md b/docs/topology-status.md +index d8e0e49740260b6a6a23dd76a91192d6d66d485e..102b0b9e6e441ac6d3e75acfae6b29d633929338 100644 +--- a/docs/topology-status.md ++++ b/docs/topology-status.md +@@ -28,12 +28,12 @@ Generated by `devtools render topology-status`. Reads `docs/plans/topology-targe + + ### Summary + +-- **Stable** (no move scoped): 843 ++- **Stable** (no move scoped): 852 + - **Kernel** (polylogue/ root): 7 + - **Primitives** (storage-root): 18 + - **TBD** (cell needs explicit assignment): 9 +-- **Total declared**: 1007 +-- **Realized polylogue/**/*.py**: 1007 files declared ++- **Total declared**: 1016 ++- **Realized polylogue/**/*.py**: 1016 files declared + + ### TBD cells (require explicit routing) + +diff --git a/polylogue/declarations/__init__.py b/polylogue/declarations/__init__.py +new file mode 100644 +index 0000000000000000000000000000000000000000..bf3aed643ac0307537da8881f53669b2b0af437f +--- /dev/null ++++ b/polylogue/declarations/__init__.py +@@ -0,0 +1,42 @@ ++"""Typed declaration/derivation kernel shared by domain registries.""" ++ ++from polylogue.declarations.derive import ( ++ DeclarationDeriver, ++ DerivationInput, ++ derivation_inputs, ++ normalized_derivation_bytes, ++) ++from polylogue.declarations.models import ( ++ CompatibilityKey, ++ CompletenessEdge, ++ DeclarationSpec, ++ ExampleSpec, ++ FamilySpec, ++ HandlerBinding, ++ JSONScalar, ++ JSONValue, ++ OutputSpec, ++) ++from polylogue.declarations.registry import DeclarationConflictError, DeclarationRegistry ++from polylogue.declarations.validation import Diagnostic, validate_declaration, validate_registry ++ ++__all__ = [ ++ "CompatibilityKey", ++ "CompletenessEdge", ++ "DeclarationConflictError", ++ "DeclarationDeriver", ++ "DeclarationRegistry", ++ "DeclarationSpec", ++ "DerivationInput", ++ "Diagnostic", ++ "ExampleSpec", ++ "FamilySpec", ++ "HandlerBinding", ++ "JSONScalar", ++ "JSONValue", ++ "OutputSpec", ++ "derivation_inputs", ++ "normalized_derivation_bytes", ++ "validate_declaration", ++ "validate_registry", ++] +diff --git a/polylogue/declarations/derive.py b/polylogue/declarations/derive.py +new file mode 100644 +index 0000000000000000000000000000000000000000..ac105c9388cca29b840b16fbc06784880bffeb92 +--- /dev/null ++++ b/polylogue/declarations/derive.py +@@ -0,0 +1,54 @@ ++"""Stable derivation inputs for declaration-owned generated artifacts.""" ++ ++from __future__ import annotations ++ ++import json ++from dataclasses import asdict, dataclass ++from typing import Protocol ++ ++from polylogue.declarations.models import DeclarationSpec ++from polylogue.declarations.registry import DeclarationRegistry ++ ++ ++@dataclass(frozen=True, slots=True) ++class DerivationInput: ++ """Normalized declaration input handed to a domain renderer.""" ++ ++ declaration_id: str ++ public_name: str ++ owner_path: str ++ normalized: dict[str, object] ++ ++ ++class DeclarationDeriver(Protocol): ++ """Protocol implemented by domain-specific artifact derivations.""" ++ ++ def derive(self, declarations: tuple[DerivationInput, ...]) -> bytes: ... ++ ++ ++def _normalized(declaration: DeclarationSpec) -> dict[str, object]: ++ return asdict(declaration) ++ ++ ++def derivation_inputs(registry: DeclarationRegistry) -> tuple[DerivationInput, ...]: ++ """Return stable typed inputs for names, contracts, docs, and completeness.""" ++ ++ return tuple( ++ DerivationInput( ++ declaration_id=declaration.declaration_id, ++ public_name=declaration.public_name, ++ owner_path=declaration.owner_path, ++ normalized=_normalized(declaration), ++ ) ++ for declaration in registry.declarations() ++ ) ++ ++ ++def normalized_derivation_bytes(registry: DeclarationRegistry) -> bytes: ++ """Serialize derivation inputs deterministically for drift checks.""" ++ ++ payload = [item.normalized for item in derivation_inputs(registry)] ++ return (json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode() ++ ++ ++__all__ = ["DeclarationDeriver", "DerivationInput", "derivation_inputs", "normalized_derivation_bytes"] +diff --git a/polylogue/declarations/models.py b/polylogue/declarations/models.py +new file mode 100644 +index 0000000000000000000000000000000000000000..6c01520c8bde853923edf0cb28026be4c95409d9 +--- /dev/null ++++ b/polylogue/declarations/models.py +@@ -0,0 +1,123 @@ ++"""Storage-free declaration authoring primitives. ++ ++The shared kernel deliberately knows nothing about MCP, origins, queries, ++storage, maintenance, or any other domain registry. Domain packages wrap ++these immutable records with their own semantic fields and dispatch rules. ++""" ++ ++from __future__ import annotations ++ ++from dataclasses import dataclass ++from typing import TypeAlias ++ ++JSONScalar: TypeAlias = str | int | float | bool | None ++JSONValue: TypeAlias = ( ++ JSONScalar ++ | list["JSONValue"] ++ | dict[str, "JSONValue"] ++ | tuple["JSONValue", ...] ++ | tuple[tuple[str, "JSONValue"], ...] ++) ++ ++ ++@dataclass(frozen=True, slots=True) ++class CompatibilityKey: ++ """Dimensions that must match before declarations share a family.""" ++ ++ identity: str ++ lifecycle: str ++ authority: str ++ access_result_shape: str ++ durability: str ++ ++ def differences(self, other: CompatibilityKey) -> tuple[tuple[str, str, str], ...]: ++ """Return every incompatible dimension in stable field order.""" ++ ++ fields = ("identity", "lifecycle", "authority", "access_result_shape", "durability") ++ return tuple( ++ (field, str(getattr(self, field)), str(getattr(other, field))) ++ for field in fields ++ if getattr(self, field) != getattr(other, field) ++ ) ++ ++ ++@dataclass(frozen=True, slots=True) ++class HandlerBinding: ++ """One executable handler expected to realize a declaration.""" ++ ++ surface: str ++ owner_path: str ++ symbol: str ++ binding_key: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class OutputSpec: ++ """One generated or runtime output owned by a declaration.""" ++ ++ name: str ++ kind: str ++ schema_ref: str ++ target_path: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class ExampleSpec: ++ """One deterministic example invocation or discovery example.""" ++ ++ name: str ++ summary: str ++ arguments: tuple[tuple[str, JSONValue], ...] = () ++ ++ ++@dataclass(frozen=True, slots=True) ++class CompletenessEdge: ++ """A producer/consumer edge whose absence makes a declaration incomplete.""" ++ ++ producer: str ++ consumer: str ++ kind: str ++ owner_path: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class DeclarationSpec: ++ """Shared declaration contract consumed by domain-specific registries.""" ++ ++ declaration_id: str ++ family_id: str ++ public_name: str ++ owner_path: str ++ compatibility: CompatibilityKey ++ producer: str ++ role_gate: str ++ schema_ref: str ++ discovery_text: str ++ repair_command: str ++ handlers: tuple[HandlerBinding, ...] ++ outputs: tuple[OutputSpec, ...] ++ examples: tuple[ExampleSpec, ...] ++ completeness_edges: tuple[CompletenessEdge, ...] ++ ++ ++@dataclass(frozen=True, slots=True) ++class FamilySpec: ++ """Normalized family projection built by :class:`DeclarationRegistry`.""" ++ ++ family_id: str ++ compatibility: CompatibilityKey ++ declaration_ids: tuple[str, ...] ++ owner_paths: tuple[str, ...] ++ ++ ++__all__ = [ ++ "CompatibilityKey", ++ "CompletenessEdge", ++ "DeclarationSpec", ++ "ExampleSpec", ++ "FamilySpec", ++ "HandlerBinding", ++ "JSONScalar", ++ "JSONValue", ++ "OutputSpec", ++] +diff --git a/polylogue/declarations/registry.py b/polylogue/declarations/registry.py +new file mode 100644 +index 0000000000000000000000000000000000000000..a429f592c37d9997befb0cb88fa03437c838ccbe +--- /dev/null ++++ b/polylogue/declarations/registry.py +@@ -0,0 +1,105 @@ ++"""Deterministic registry for shared declaration records.""" ++ ++from __future__ import annotations ++ ++from dataclasses import dataclass ++ ++from polylogue.declarations.models import DeclarationSpec, FamilySpec ++ ++ ++@dataclass(frozen=True, slots=True) ++class DeclarationConflictError(ValueError): ++ """Raised when a duplicate or incompatible declaration is registered.""" ++ ++ message: str ++ declaration_id: str ++ owner_path: str ++ differences: tuple[tuple[str, str, str], ...] = () ++ ++ def __str__(self) -> str: ++ if not self.differences: ++ return self.message ++ detail = ", ".join(f"{name}: {left!r} != {right!r}" for name, left, right in self.differences) ++ return f"{self.message} ({detail})" ++ ++ ++class DeclarationRegistry: ++ """Register declarations while preserving deterministic projections.""" ++ ++ def __init__(self) -> None: ++ self._by_id: dict[str, DeclarationSpec] = {} ++ self._by_public_name: dict[str, DeclarationSpec] = {} ++ self._family_compatibility: dict[str, DeclarationSpec] = {} ++ ++ def register(self, declaration: DeclarationSpec) -> DeclarationSpec: ++ """Register one declaration or raise a source-locatable conflict.""" ++ ++ existing = self._by_id.get(declaration.declaration_id) ++ if existing is not None: ++ raise DeclarationConflictError( ++ message=f"duplicate declaration id {declaration.declaration_id!r}", ++ declaration_id=declaration.declaration_id, ++ owner_path=declaration.owner_path, ++ ) ++ existing_name = self._by_public_name.get(declaration.public_name) ++ if existing_name is not None: ++ raise DeclarationConflictError( ++ message=( ++ f"public name {declaration.public_name!r} is already owned by " ++ f"{existing_name.declaration_id!r} at {existing_name.owner_path}" ++ ), ++ declaration_id=declaration.declaration_id, ++ owner_path=declaration.owner_path, ++ ) ++ family_member = self._family_compatibility.get(declaration.family_id) ++ if family_member is not None: ++ differences = family_member.compatibility.differences(declaration.compatibility) ++ if differences: ++ raise DeclarationConflictError( ++ message=( ++ f"declaration {declaration.declaration_id!r} cannot join family " ++ f"{declaration.family_id!r}; existing owner is {family_member.owner_path}" ++ ), ++ declaration_id=declaration.declaration_id, ++ owner_path=declaration.owner_path, ++ differences=differences, ++ ) ++ else: ++ self._family_compatibility[declaration.family_id] = declaration ++ ++ self._by_id[declaration.declaration_id] = declaration ++ self._by_public_name[declaration.public_name] = declaration ++ return declaration ++ ++ def get(self, declaration_id: str) -> DeclarationSpec: ++ return self._by_id[declaration_id] ++ ++ def by_public_name(self, public_name: str) -> DeclarationSpec: ++ return self._by_public_name[public_name] ++ ++ def declarations(self) -> tuple[DeclarationSpec, ...]: ++ """Return declarations in stable id order, independent of registration order.""" ++ ++ return tuple(self._by_id[key] for key in sorted(self._by_id)) ++ ++ def families(self) -> tuple[FamilySpec, ...]: ++ """Return deterministic family projections.""" ++ ++ grouped: dict[str, list[DeclarationSpec]] = {} ++ for declaration in self._by_id.values(): ++ grouped.setdefault(declaration.family_id, []).append(declaration) ++ return tuple( ++ FamilySpec( ++ family_id=family_id, ++ compatibility=members[0].compatibility, ++ declaration_ids=tuple(sorted(member.declaration_id for member in members)), ++ owner_paths=tuple(sorted({member.owner_path for member in members})), ++ ) ++ for family_id, members in sorted(grouped.items()) ++ ) ++ ++ def __len__(self) -> int: ++ return len(self._by_id) ++ ++ ++__all__ = ["DeclarationConflictError", "DeclarationRegistry"] +diff --git a/polylogue/declarations/validation.py b/polylogue/declarations/validation.py +new file mode 100644 +index 0000000000000000000000000000000000000000..10945133e567681501fb72a6ff85ef7588eca20f +--- /dev/null ++++ b/polylogue/declarations/validation.py +@@ -0,0 +1,62 @@ ++"""Actionable completeness diagnostics for declaration registries.""" ++ ++from __future__ import annotations ++ ++from dataclasses import dataclass ++ ++from polylogue.declarations.models import DeclarationSpec ++from polylogue.declarations.registry import DeclarationRegistry ++ ++ ++@dataclass(frozen=True, slots=True) ++class Diagnostic: ++ """One source-locatable declaration repair.""" ++ ++ code: str ++ message: str ++ declaration_id: str ++ owner_path: str ++ repair_command: str ++ ++ ++def _diagnostic(declaration: DeclarationSpec, code: str, missing: str) -> Diagnostic: ++ return Diagnostic( ++ code=code, ++ message=f"{declaration.declaration_id}: missing {missing}", ++ declaration_id=declaration.declaration_id, ++ owner_path=declaration.owner_path, ++ repair_command=declaration.repair_command, ++ ) ++ ++ ++def validate_declaration(declaration: DeclarationSpec) -> tuple[Diagnostic, ...]: ++ """Return every missing shared edge without coercing the declaration.""" ++ ++ diagnostics: list[Diagnostic] = [] ++ if not declaration.producer: ++ diagnostics.append(_diagnostic(declaration, "missing_producer", "producer")) ++ if not declaration.handlers: ++ diagnostics.append(_diagnostic(declaration, "missing_handler", "handler binding")) ++ if not declaration.role_gate: ++ diagnostics.append(_diagnostic(declaration, "missing_role_gate", "role gate")) ++ if not declaration.schema_ref: ++ diagnostics.append(_diagnostic(declaration, "missing_schema", "schema reference")) ++ if not declaration.examples: ++ diagnostics.append(_diagnostic(declaration, "missing_example", "example")) ++ if not declaration.outputs: ++ diagnostics.append(_diagnostic(declaration, "missing_output", "generated/runtime output")) ++ if not declaration.completeness_edges: ++ diagnostics.append(_diagnostic(declaration, "missing_consumer_edge", "consumer completeness edge")) ++ if not declaration.discovery_text: ++ diagnostics.append(_diagnostic(declaration, "missing_discovery", "discovery text")) ++ return tuple(diagnostics) ++ ++ ++def validate_registry(registry: DeclarationRegistry) -> tuple[Diagnostic, ...]: ++ """Return deterministic diagnostics for every declaration in the registry.""" ++ ++ diagnostics = [diagnostic for item in registry.declarations() for diagnostic in validate_declaration(item)] ++ return tuple(sorted(diagnostics, key=lambda item: (item.declaration_id, item.code))) ++ ++ ++__all__ = ["Diagnostic", "validate_declaration", "validate_registry"] +diff --git a/polylogue/mcp/declarations/__init__.py b/polylogue/mcp/declarations/__init__.py +new file mode 100644 +index 0000000000000000000000000000000000000000..502ae2a06a616cb743cef9c5e0a617fad8bad5f2 +--- /dev/null ++++ b/polylogue/mcp/declarations/__init__.py +@@ -0,0 +1,57 @@ ++"""Executable MCP declaration models and inventory. ++ ++The package initializer stays dependency-light so renderers and static checks can ++load the contract without importing the MCP runtime or archive services. ++""" ++ ++from polylogue.mcp.declarations.models import ( ++ MCPContinuationContract, ++ MCPDeprecationState, ++ MCPHandlerBinding, ++ MCPInputContract, ++ MCPOutputContract, ++ MCPPromptDeclaration, ++ MCPResourceDeclaration, ++ MCPResultSemantics, ++ MCPRole, ++ MCPToolDeclaration, ++ MCPTransactionDeclaration, ++ MCPVerb, ++ PythonParityExpectation, ++) ++from polylogue.mcp.declarations.registry import ( ++ MCP_KERNEL_REGISTRY, ++ MCP_TOOL_DECLARATION_BY_NAME, ++ MCP_TOOL_DECLARATIONS, ++ PRIVILEGED_ALGEBRA, ++ TARGET_DEFAULT_READ_ALGEBRA, ++ TARGET_PROMPTS, ++ TARGET_RESOURCES, ++ declaration_for_tool, ++ declared_tool_names, ++) ++ ++__all__ = [ ++ "MCPContinuationContract", ++ "MCPDeprecationState", ++ "MCPHandlerBinding", ++ "MCPInputContract", ++ "MCP_KERNEL_REGISTRY", ++ "MCPOutputContract", ++ "MCPPromptDeclaration", ++ "MCPResourceDeclaration", ++ "MCPResultSemantics", ++ "MCPRole", ++ "MCPToolDeclaration", ++ "MCPTransactionDeclaration", ++ "MCPVerb", ++ "MCP_TOOL_DECLARATIONS", ++ "MCP_TOOL_DECLARATION_BY_NAME", ++ "PRIVILEGED_ALGEBRA", ++ "PythonParityExpectation", ++ "TARGET_DEFAULT_READ_ALGEBRA", ++ "TARGET_PROMPTS", ++ "TARGET_RESOURCES", ++ "declaration_for_tool", ++ "declared_tool_names", ++] +diff --git a/polylogue/mcp/declarations/adapter.py b/polylogue/mcp/declarations/adapter.py +new file mode 100644 +index 0000000000000000000000000000000000000000..b33711f59cc63705dcac660d714591f26f8583a7 +--- /dev/null ++++ b/polylogue/mcp/declarations/adapter.py +@@ -0,0 +1,175 @@ ++"""FastMCP registration adapter backed by the executable declaration inventory. ++ ++The adapter validates the compatibility surface while deliberately passing the ++original handler object to FastMCP. It is not an executor and does not acquire ++runtime, authorization, storage, or workflow authority. ++""" ++ ++from __future__ import annotations ++ ++from collections.abc import Callable ++from typing import Any, Protocol, TypeVar, cast ++ ++from polylogue.mcp.declarations.models import MCPRole, MCPToolDeclaration, mcp_role_allows ++from polylogue.mcp.declarations.registry import declaration_for_tool, declared_tool_names ++ ++HandlerT = TypeVar("HandlerT", bound=Callable[..., Any]) ++AnyHandler = Callable[..., Any] ++ ++ ++class ToolRegistrar(Protocol): ++ """The FastMCP decorator shape used by current registration owners.""" ++ ++ def tool( ++ self, ++ name: str | None = None, ++ *, ++ description: str | None = None, ++ ) -> Callable[[AnyHandler], AnyHandler]: ... ++ ++ ++class MCPRegistrationError(RuntimeError): ++ """Actionable declaration/registration mismatch.""" ++ ++ def __init__( ++ self, ++ message: str, ++ *, ++ tool_name: str | None = None, ++ repair_command: str = "devtools render mcp-equivalence", ++ ) -> None: ++ self.message = message ++ self.tool_name = tool_name ++ self.repair_command = repair_command ++ super().__init__(f"{message}; repair with `{repair_command}`") ++ ++ ++class DeclaredToolRegistrar: ++ """Validate one role-filtered live tool surface against declarations. ++ ++ Attribute access other than :meth:`tool` is delegated to the wrapped ++ FastMCP instance so existing registration helpers remain usable. The ++ decorator returned by :meth:`tool` receives and forwards the exact handler ++ object; behavior, signatures, cancellation, and error handling therefore ++ remain owned by the existing production implementation. ++ """ ++ ++ def __init__(self, delegate: ToolRegistrar, *, role: MCPRole) -> None: ++ self._delegate = delegate ++ self._role = role ++ self._registered: dict[str, Callable[..., Any]] = {} ++ ++ @property ++ def role(self) -> MCPRole: ++ return self._role ++ ++ @property ++ def registered_names(self) -> frozenset[str]: ++ return frozenset(self._registered) ++ ++ def _declaration(self, name: str) -> MCPToolDeclaration: ++ try: ++ declaration = declaration_for_tool(name) ++ except KeyError as exc: ++ raise MCPRegistrationError( ++ f"live MCP handler {name!r} has no executable declaration", ++ tool_name=name, ++ ) from exc ++ if not mcp_role_allows(self._role, declaration.minimum_role): ++ raise MCPRegistrationError( ++ f"MCP role {self._role!r} registered {name!r}, which requires {declaration.minimum_role!r}", ++ tool_name=name, ++ ) ++ return declaration ++ ++ @staticmethod ++ def _resolved_name(handler: Callable[..., Any], explicit_name: str | None) -> str: ++ if explicit_name is not None: ++ if not explicit_name: ++ raise MCPRegistrationError("FastMCP tool name must be a non-empty string") ++ return explicit_name ++ name = getattr(handler, "__name__", None) ++ if not isinstance(name, str) or not name: ++ raise MCPRegistrationError("MCP handler has no stable __name__ for declaration lookup") ++ return name ++ ++ @staticmethod ++ def _validate_binding(declaration: MCPToolDeclaration, handler: Callable[..., Any]) -> None: ++ module = getattr(handler, "__module__", None) ++ if module != declaration.registration.module: ++ raise MCPRegistrationError( ++ f"MCP handler {declaration.name!r} is implemented by {module!r}, not declared module " ++ f"{declaration.registration.module!r}", ++ tool_name=declaration.name, ++ ) ++ ++ def tool( ++ self, ++ name: str | None = None, ++ *, ++ description: str | None = None, ++ ) -> Callable[[AnyHandler], AnyHandler]: ++ """Return the validating decorator used by every live tool registrar.""" ++ ++ def register(handler: AnyHandler) -> AnyHandler: ++ resolved_name = self._resolved_name(handler, name) ++ declaration = self._declaration(resolved_name) ++ self._validate_binding(declaration, handler) ++ if description is not None and description != declaration.description: ++ raise MCPRegistrationError( ++ f"MCP tool {resolved_name!r} discovery text differs from its declaration", ++ tool_name=resolved_name, ++ ) ++ if resolved_name in self._registered: ++ raise MCPRegistrationError( ++ f"MCP tool {resolved_name!r} was registered more than once", ++ tool_name=resolved_name, ++ ) ++ registered = self._delegate.tool(name=name, description=description)(handler) ++ self._registered[resolved_name] = handler ++ return registered ++ ++ return register ++ ++ def finalize(self) -> None: ++ """Require the exact role-visible declaration set after registration.""" ++ ++ expected = declared_tool_names(self._role) ++ actual = self.registered_names ++ if expected == actual: ++ return ++ missing = sorted(expected - actual) ++ extra = sorted(actual - expected) ++ parts: list[str] = [f"MCP {self._role!r} registration does not match executable declarations"] ++ if missing: ++ parts.append(f"missing={missing}") ++ if extra: ++ parts.append(f"extra={extra}") ++ raise MCPRegistrationError("; ".join(parts)) ++ ++ def __getattr__(self, name: str) -> object: ++ """Delegate non-registration behavior without creating a parallel server.""" ++ ++ return getattr(self._delegate, name) ++ ++ ++def register_declared_handler( ++ mcp: ToolRegistrar, ++ handler: HandlerT, ++ *, ++ name: str, ++) -> object: ++ """Register one compatibility handler using declaration-owned metadata.""" ++ ++ declaration = declaration_for_tool(name) ++ handler.__name__ = declaration.name ++ handler.__doc__ = declaration.description ++ return cast(HandlerT, mcp.tool()(handler)) ++ ++ ++__all__ = [ ++ "DeclaredToolRegistrar", ++ "MCPRegistrationError", ++ "ToolRegistrar", ++ "register_declared_handler", ++] +diff --git a/polylogue/mcp/declarations/models.py b/polylogue/mcp/declarations/models.py +new file mode 100644 +index 0000000000000000000000000000000000000000..ba3208b6e41cbd78af670ea84080c38d0e1343a8 +--- /dev/null ++++ b/polylogue/mcp/declarations/models.py +@@ -0,0 +1,225 @@ ++"""MCP-owned declaration semantics layered over the shared kernel.""" ++ ++from __future__ import annotations ++ ++from dataclasses import asdict, dataclass ++from enum import Enum ++from typing import Final, Literal, TypeAlias ++ ++from polylogue.declarations import DeclarationSpec, JSONValue ++ ++MCPRole = Literal["read", "write", "review", "admin"] ++MCP_ROLE_ORDER: Final[dict[MCPRole, int]] = {"read": 0, "write": 1, "review": 2, "admin": 3} ++ ++ ++def mcp_role_allows(role: MCPRole, required: MCPRole) -> bool: ++ """Return whether ``role`` includes the required MCP capability.""" ++ ++ return MCP_ROLE_ORDER[role] >= MCP_ROLE_ORDER[required] ++ ++ ++ObservedUse = Literal["observed", "not_observed", "unknown"] ++ ++ ++class MCPVerb(str, Enum): ++ QUERY = "query" ++ READ = "read" ++ GET = "get" ++ EXPLAIN = "explain" ++ CONTEXT = "context" ++ STATUS = "status" ++ WRITE = "write" ++ JUDGE = "judge" ++ RUN = "run" ++ MAINTENANCE = "maintenance" ++ ++ ++class MCPResultSemantics(str, Enum): ++ EXHAUSTIVE_PAGE = "exhaustive_page" ++ TOP_K = "top_k" ++ SAMPLE = "sample" ++ AGGREGATE = "aggregate" ++ SINGLE_OBJECT = "single_object" ++ BOUNDED_CONTEXT = "bounded_context" ++ RECURSIVE_GRAPH = "recursive_graph" ++ MUTATION = "mutation" ++ MAINTENANCE = "maintenance" ++ ++ ++class MCPDeprecationState(str, Enum): ++ RETAINED = "retained" ++ COMPATIBILITY = "compatibility" ++ TARGET_RESOURCE = "target_resource" ++ TARGET_PROMPT = "target_prompt" ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPHandlerBinding: ++ """Where the live FastMCP handler is registered and implemented.""" ++ ++ module: str ++ symbol: str ++ registrar: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPInputContract: ++ """Source of the FastMCP input schema and its compatibility invariant.""" ++ ++ schema_source: str ++ schema_mode: str ++ required_arguments: tuple[str, ...] ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPOutputContract: ++ """Semantic output classification independent from Python return ``str``.""" ++ ++ kind: str ++ envelope_fields: tuple[str, ...] = () ++ schema_source: str = "tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT" ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPContinuationContract: ++ """Logical-completeness and continuation behavior of one route.""" ++ ++ mode: str ++ continuation_ref: str | None ++ exhaustive_route: str | None ++ notes: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class PythonParityExpectation: ++ """Public Python binding or an explicitly governed intentional absence.""" ++ ++ binding: str | None = None ++ intentional_absence_authority: str | None = None ++ reason: str | None = None ++ ++ def __post_init__(self) -> None: ++ bound = self.binding is not None ++ absent = self.intentional_absence_authority is not None and self.reason is not None ++ if bound == absent: ++ raise ValueError("Python parity must declare exactly one binding or intentional absence") ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPToolDeclaration: ++ """Executable inventory row for one legacy MCP tool.""" ++ ++ kernel: DeclarationSpec ++ name: str ++ description: str ++ verb: MCPVerb ++ object_kinds: tuple[str, ...] ++ minimum_role: MCPRole ++ capability: str ++ result_semantics: MCPResultSemantics ++ canonical_plan: str ++ canonical_projection: str ++ input_contract: MCPInputContract ++ output_contract: MCPOutputContract ++ minimal_arguments: tuple[tuple[str, JSONValue], ...] ++ grammar_discovery: tuple[str, ...] ++ field_discovery: tuple[str, ...] ++ value_discovery: tuple[str, ...] ++ continuation: MCPContinuationContract ++ resource_alternatives: tuple[str, ...] ++ prompt_alternatives: tuple[str, ...] ++ compatibility_route: str ++ workflow_coverage: tuple[str, ...] ++ incident_coverage: tuple[str, ...] ++ observed_use: ObservedUse ++ telemetry_key: str ++ deprecation_state: MCPDeprecationState ++ retirement_owner: str ++ registration: MCPHandlerBinding ++ operation_owner: str ++ python_parity: PythonParityExpectation ++ ++ def __post_init__(self) -> None: ++ if self.name != self.kernel.public_name: ++ raise ValueError(f"MCP declaration name {self.name!r} != kernel public name {self.kernel.public_name!r}") ++ if self.telemetry_key != self.name: ++ raise ValueError(f"MCP telemetry key must remain the discovery name for {self.name!r}") ++ if not self.object_kinds: ++ raise ValueError(f"MCP declaration {self.name!r} has no object/ref kind") ++ if not self.workflow_coverage: ++ raise ValueError(f"MCP declaration {self.name!r} has no workflow coverage") ++ ++ @property ++ def declaration_id(self) -> str: ++ return self.kernel.declaration_id ++ ++ def minimal_arguments_dict(self) -> dict[str, JSONValue]: ++ return dict(self.minimal_arguments) ++ ++ def to_dict(self) -> dict[str, object]: ++ """Return a stable JSON-compatible projection for generated artifacts.""" ++ ++ payload = asdict(self) ++ payload["verb"] = self.verb.value ++ payload["result_semantics"] = self.result_semantics.value ++ payload["deprecation_state"] = self.deprecation_state.value ++ payload["minimal_arguments"] = dict(self.minimal_arguments) ++ return payload ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPTransactionDeclaration: ++ """One target protocol-native transaction in the bounded discovery algebra.""" ++ ++ name: str ++ verb: MCPVerb ++ minimum_role: MCPRole ++ object_kinds: tuple[str, ...] ++ result_semantics: tuple[MCPResultSemantics, ...] ++ purpose: str ++ migration_owner: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPResourceDeclaration: ++ """Target URI resource class for stable archive identities.""" ++ ++ uri_template: str ++ object_kinds: tuple[str, ...] ++ minimum_role: MCPRole ++ authority: str ++ migration_owner: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPPromptDeclaration: ++ """Target prompt class for parameterized workflows without authority.""" ++ ++ name: str ++ workflow: str ++ minimum_role: MCPRole ++ mutation_authority: Literal["none"] ++ migration_owner: str ++ ++ ++MCPDeclarationMap: TypeAlias = dict[str, MCPToolDeclaration] ++ ++__all__ = [ ++ "MCPContinuationContract", ++ "MCPDeclarationMap", ++ "MCPDeprecationState", ++ "MCPHandlerBinding", ++ "MCPInputContract", ++ "MCPOutputContract", ++ "MCPPromptDeclaration", ++ "MCPResourceDeclaration", ++ "MCPResultSemantics", ++ "MCPRole", ++ "MCP_ROLE_ORDER", ++ "MCPToolDeclaration", ++ "MCPTransactionDeclaration", ++ "MCPVerb", ++ "ObservedUse", ++ "PythonParityExpectation", ++ "mcp_role_allows", ++] +diff --git a/polylogue/mcp/declarations/registry.py b/polylogue/mcp/declarations/registry.py +new file mode 100644 +index 0000000000000000000000000000000000000000..436a162c97db21306e6ed11c4a35d22e9060c941 +--- /dev/null ++++ b/polylogue/mcp/declarations/registry.py +@@ -0,0 +1,3525 @@ ++"""Executable MCP tool algebra inventory and equivalence ownership. ++ ++Every live tool appears exactly once. This slice retains all compatibility ++handlers; t46.8.2 and t46.8.3 own the disjoint retirement groups. ++""" ++ ++from __future__ import annotations ++ ++from collections.abc import Mapping ++from dataclasses import dataclass ++from typing import Final ++ ++from polylogue.declarations import ( ++ CompatibilityKey, ++ CompletenessEdge, ++ DeclarationRegistry, ++ DeclarationSpec, ++ ExampleSpec, ++ HandlerBinding, ++ JSONValue, ++ OutputSpec, ++ validate_registry, ++) ++from polylogue.mcp.declarations.models import ( ++ MCPContinuationContract, ++ MCPDeprecationState, ++ MCPHandlerBinding, ++ MCPInputContract, ++ MCPOutputContract, ++ MCPPromptDeclaration, ++ MCPResourceDeclaration, ++ MCPResultSemantics, ++ MCPRole, ++ MCPToolDeclaration, ++ MCPTransactionDeclaration, ++ MCPVerb, ++ ObservedUse, ++ PythonParityExpectation, ++ mcp_role_allows, ++) ++ ++_REPAIR_COMMAND = "devtools render mcp-equivalence" ++ ++ ++@dataclass(frozen=True, slots=True) ++class _ToolRow: ++ name: str ++ description: str ++ module: str ++ registrar: str ++ minimum_role: MCPRole ++ verb: MCPVerb ++ object_kinds: tuple[str, ...] ++ result_semantics: MCPResultSemantics ++ schema_source: str ++ required_arguments: tuple[str, ...] ++ minimal_arguments: tuple[tuple[str, JSONValue], ...] ++ output_kind: str ++ envelope_fields: tuple[str, ...] ++ operation_owner: str ++ python_binding: str | None ++ ++ ++_TOOL_ROWS: Final[tuple[_ToolRow, ...]] = ( ++ _ToolRow( ++ "action_affordances", ++ "Return shared query-action affordance metadata for agents.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("action-affordance",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.action_affordances:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.operations.action_contracts.action_affordance_list_payload", ++ None, ++ ), ++ _ToolRow( ++ "add_mark", ++ "Add mark.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("mark", "object-ref"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.add_mark:inspect.signature", ++ ("session_id", "mark_type"), ++ (("mark_type", "star"), ("session_id", "test:conv-mutation")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.add_mark", ++ "polylogue.api.Polylogue.add_mark", ++ ), ++ _ToolRow( ++ "add_tag", ++ "Add tag.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("tag", "session"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.add_tag:inspect.signature", ++ ("session_id", "tag"), ++ ( ++ ("author_kind", "agent"), ++ ("author_ref", "agent:codex-session:tool-contract"), ++ ("session_id", "test:conv-mutation"), ++ ("tag", "review"), ++ ), ++ "operation_result", ++ (), ++ "mutate-add-tag", ++ "polylogue.api.Polylogue.add_tag", ++ ), ++ _ToolRow( ++ "agent_coordination", ++ "Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("coordination-envelope",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.agent_coordination:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.coordination.build_coordination_envelope", ++ None, ++ ), ++ _ToolRow( ++ "aggregate_sessions", ++ "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-aggregate",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.aggregate_sessions:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.aggregate_sessions", ++ "polylogue.api.Polylogue.aggregate_sessions", ++ ), ++ _ToolRow( ++ "archive_coverage", ++ "List archive coverage from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("coverage-rollup",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ArchiveCoverageInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-archive-coverage", ++ "polylogue.api.Polylogue.list_archive_coverage_insights", ++ ), ++ _ToolRow( ++ "archive_debt", ++ "Return the unified archive debt list used by CLI and daemon surfaces.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("archive-debt",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.archive_debt:inspect.signature", ++ (), ++ (("limit", 1),), ++ "typed_envelope", ++ (), ++ "query-archive-debt", ++ "polylogue.api.Polylogue.archive_debt", ++ ), ++ _ToolRow( ++ "archive_get_session", ++ "Read a full session by exact id or prefix.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.READ, ++ ("session",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.archive_get_session:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.archive_get_session", ++ "polylogue.api.Polylogue.archive_get_session", ++ ), ++ _ToolRow( ++ "archive_list_sessions", ++ "List sessions from the split archive store.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session", "result-set"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.archive_list_sessions:inspect.signature", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "limit", "offset", "total"), ++ "query-sessions", ++ "polylogue.api.Polylogue.archive_list_sessions", ++ ), ++ _ToolRow( ++ "archive_search_sessions", ++ "Search session block text.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session", "result-set"), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_tools.archive_search_sessions:inspect.signature", ++ ("query",), ++ (("limit", 1), ("query", "hello")), ++ "envelope", ++ ("items", "limit", "query", "total"), ++ "query-sessions", ++ "polylogue.api.Polylogue.archive_search_sessions", ++ ), ++ _ToolRow( ++ "blackboard_list", ++ "List persistent agent blackboard notes, newest first (#1697).", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("blackboard-note",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.blackboard_list:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_blackboard_notes", ++ "polylogue.api.Polylogue.list_blackboard_notes", ++ ), ++ _ToolRow( ++ "blackboard_post", ++ "Post a note to the persistent agent blackboard (#1697).", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("blackboard-note",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.blackboard_post:inspect.signature", ++ ("kind", "title", "content"), ++ (("content", "c"), ("kind", "finding"), ("title", "t")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.post_blackboard_note", ++ "polylogue.api.Polylogue.post_blackboard_note", ++ ), ++ _ToolRow( ++ "build_context_image", ++ "Build a bounded context image for agent analysis.", ++ "polylogue.mcp.server_context_tools", ++ "register_context_tools", ++ "read", ++ MCPVerb.CONTEXT, ++ ("context-image", "session"), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_context_tools.build_context_image:inspect.signature", ++ (), ++ (("project_repo", "test"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.context_image_payload", ++ "polylogue.api.Polylogue.context_image_payload", ++ ), ++ _ToolRow( ++ "bulk_tag_sessions", ++ "Bulk tag sessions.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("tag", "session"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.bulk_tag_sessions:inspect.signature", ++ ("session_ids", "tags"), ++ (("session_ids", ["test:conv-mutation"]), ("tags", ["review"])), ++ "operation_result", ++ (), ++ "mutate-bulk-tag-sessions", ++ "polylogue.api.Polylogue.bulk_tag_sessions", ++ ), ++ _ToolRow( ++ "capture_assertion_candidate", ++ "Capture a terminal-equivalent assertion candidate for later judgment.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("assertion-candidate",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.capture_assertion_candidate:inspect.signature", ++ ("body_text", "author_ref"), ++ (("author_ref", "agent:codex-session:tool-contract"), ("body_text", "capture this"), ("kind", "lesson")), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.capture_assertion_candidate", ++ "polylogue.api.Polylogue.capture_assertion_candidate", ++ ), ++ _ToolRow( ++ "clear_corrections", ++ "Delete one or all corrections for a session.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("correction", "session"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.clear_corrections:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-mutation"),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.clear_corrections", ++ "polylogue.api.Polylogue.clear_corrections", ++ ), ++ _ToolRow( ++ "compare_sessions", ++ "Compare multiple session profiles side-by-side.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-comparison",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.compare_sessions:inspect.signature", ++ (), ++ (("session_ids", "test:a,test:b"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.compare_sessions", ++ "polylogue.api.Polylogue.compare_sessions", ++ ), ++ _ToolRow( ++ "compile_context", ++ "Compile a bounded context image from query/ref seeds and read views.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.CONTEXT, ++ ("context-snapshot", "object-ref"), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_tools.compile_context:inspect.signature", ++ (), ++ (("read_views", "messages"), ("seed_ref", "session:test:conv-discovery-nonexistent")), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.compile_context", ++ "polylogue.api.Polylogue.compile_context", ++ ), ++ _ToolRow( ++ "compose_context_preamble", ++ "Compose a ContextPreamble for SessionStart context injection (#1494).", ++ "polylogue.mcp.server_context_tools", ++ "register_context_tools", ++ "read", ++ MCPVerb.CONTEXT, ++ ("context-preamble", "workspace"), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_context_tools.compose_context_preamble:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.context.continuity.build_context_preamble", ++ None, ++ ), ++ _ToolRow( ++ "correlate_session", ++ "Link a session to git commits and GitHub issue/PR references (#1690).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("repository-correlation",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.correlate_session:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_session", ++ "polylogue.api.Polylogue.get_session", ++ ), ++ _ToolRow( ++ "correlate_sessions", ++ "Compute Pearson correlation between two numeric session metrics.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("correlation",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.correlate_sessions:inspect.signature", ++ ("metric_x", "metric_y"), ++ (("metric_x", "message_count"), ("metric_y", "word_count")), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.correlate_sessions", ++ "polylogue.api.Polylogue.correlate_sessions", ++ ), ++ _ToolRow( ++ "cost_outlook", ++ "Project the current billing cycle for a subscription plan (#1138).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("cost-outlook",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.cost_outlook:inspect.signature", ++ ("plan",), ++ (("plan", "claude-pro"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.cost_outlook", ++ "polylogue.api.Polylogue.cost_outlook", ++ ), ++ _ToolRow( ++ "cost_rollups", ++ "List cost rollups from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("cost-rollup",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(CostRollupInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_cost_rollup_insights", ++ "polylogue.api.Polylogue.list_cost_rollup_insights", ++ ), ++ _ToolRow( ++ "delete_annotation", ++ "Delete annotation.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("annotation",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.delete_annotation:inspect.signature", ++ ("annotation_id",), ++ (("annotation_id", "ann-1"),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.delete_annotation", ++ "polylogue.api.Polylogue.delete_annotation", ++ ), ++ _ToolRow( ++ "delete_metadata", ++ "Delete metadata.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("session-metadata",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.delete_metadata:inspect.signature", ++ ("session_id", "key"), ++ (("key", "note"), ("session_id", "test:conv-mutation")), ++ "operation_result", ++ (), ++ "mutate-delete-metadata", ++ "polylogue.api.Polylogue.delete_metadata", ++ ), ++ _ToolRow( ++ "delete_recall_pack", ++ "Delete recall pack.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("recall-pack",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.delete_recall_pack:inspect.signature", ++ ("pack_id",), ++ (("pack_id", "pack-1"),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.delete_recall_pack", ++ "polylogue.api.Polylogue.delete_recall_pack", ++ ), ++ _ToolRow( ++ "delete_saved_view", ++ "Delete saved view.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("saved-query",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.delete_saved_view:inspect.signature", ++ ("view_id",), ++ (("view_id", "view-1"),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.delete_view", ++ "polylogue.api.Polylogue.delete_view", ++ ), ++ _ToolRow( ++ "delete_session", ++ "Delete session.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("session",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.delete_session:inspect.signature", ++ ("session_id",), ++ (("confirm", True), ("session_id", "test:conv-mutation")), ++ "operation_result", ++ (), ++ "mutate-delete-session", ++ "polylogue.api.Polylogue.delete_session_safe", ++ ), ++ _ToolRow( ++ "delete_workspace", ++ "Delete workspace.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("reader-workspace",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.delete_workspace:inspect.signature", ++ ("workspace_id",), ++ (("workspace_id", "ws-1"),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.delete_workspace", ++ "polylogue.api.Polylogue.delete_workspace", ++ ), ++ _ToolRow( ++ "embedding_preflight", ++ "Embedding preflight.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("embedding-plan",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.embedding_preflight:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "query-embedding-status", ++ None, ++ ), ++ _ToolRow( ++ "embedding_status", ++ "Embedding status.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("embedding-status",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.embedding_status:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "query-embedding-status", ++ None, ++ ), ++ _ToolRow( ++ "explain_import", ++ "Explain import detection/parsing from a local path or archived raw evidence.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("raw-artifact", "import-record"), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.explain_import:inspect.signature", ++ (), ++ (("limit", 1), ("raw_ref", "raw:discovery-nonexistent")), ++ "typed_envelope", ++ (), ++ "polylogue.api.Polylogue.explain_import", ++ "polylogue.api.Polylogue.explain_import", ++ ), ++ _ToolRow( ++ "explain_query_expression", ++ "Explain query DSL parsing and lowering through the shared grammar.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("query-plan",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.explain_query_expression:inspect.signature", ++ ("expression",), ++ (("expression", "repo:polylogue"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.explain_query_expression", ++ "polylogue.api.Polylogue.explain_query_expression", ++ ), ++ _ToolRow( ++ "facets", ++ "Compute scoped and global facet aggregates over the archive. Filter parameters mirror ``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries ``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency weights for each value.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("facet", "session-query"), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.query_contracts.MCPSessionQueryRequest", ++ (), ++ (("limit", 1),), ++ "single_object", ++ (), ++ "query-sessions", ++ None, ++ ), ++ _ToolRow( ++ "find_abandoned_sessions", ++ "Find sessions whose terminal state indicates dangling work.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session",), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_insight_tools.find_abandoned_sessions:inspect.signature", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.find_abandoned_sessions", ++ "polylogue.api.Polylogue.find_abandoned_sessions", ++ ), ++ _ToolRow( ++ "find_resume_candidates", ++ "Rank logical sessions likely to match the operator's current context.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session",), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_insight_tools.find_resume_candidates:inspect.signature", ++ ("repo_path",), ++ (("repo_path", "/tmp/nonexistent"),), ++ "envelope", ++ ("candidates", "total"), ++ "polylogue.api.Polylogue.find_resume_candidates", ++ "polylogue.api.Polylogue.find_resume_candidates", ++ ), ++ _ToolRow( ++ "find_similar_sessions", ++ "Find sessions similar to a given session.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session",), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_insight_tools.find_similar_sessions:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"), ("limit", 1)), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ "polylogue.api.Polylogue.find_similar_sessions_by_metadata", ++ ), ++ _ToolRow( ++ "find_stuck_sessions", ++ "Find sessions with origin tool calls bounded as stuck.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session",), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_insight_tools.find_stuck_sessions:inspect.signature", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ "polylogue.api.Polylogue.find_stuck_session_latency_profile_insights", ++ ), ++ _ToolRow( ++ "get_context_delivery", ++ "Read the exact persisted context image for its recorded recipient.", ++ "polylogue.mcp.server_context_tools", ++ "register_context_tools", ++ "read", ++ MCPVerb.CONTEXT, ++ ("context-delivery", "context-snapshot", "agent-ref"), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_context_tools.get_context_delivery:inspect.signature", ++ ("snapshot_ref", "recipient_ref"), ++ ( ++ ("recipient_ref", "agent:discovery"), ++ ("snapshot_ref", "context-snapshot:test:conv-discovery-nonexistent:explicit-recall"), ++ ), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_context_delivery", ++ "polylogue.api.Polylogue.get_context_delivery", ++ ), ++ _ToolRow( ++ "get_logical_session", ++ "Return compact logical-session lineage for read-pull consumers.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.GET, ++ ("session", "thread"), ++ MCPResultSemantics.RECURSIVE_GRAPH, ++ "polylogue.mcp.server_tools.get_logical_session:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "envelope", ++ ("cycle_detected", "descendants", "root_id", "session_id", "siblings", "thread"), ++ "polylogue.api.Polylogue.get_logical_session", ++ "polylogue.api.Polylogue.get_logical_session", ++ ), ++ _ToolRow( ++ "get_messages", ++ "Return a filtered message page.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("message", "session"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.get_messages:inspect.signature", ++ ("session_id",), ++ (("limit", 1), ("session_id", "test:conv-discovery-nonexistent")), ++ "envelope", ++ ("messages", "total"), ++ "polylogue.mcp.archive_support.archive_messages_payload", ++ None, ++ ), ++ _ToolRow( ++ "get_metadata", ++ "Get metadata.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("session-metadata",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_personal_state_tools.get_metadata:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-mutation"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_metadata", ++ "polylogue.api.Polylogue.get_metadata", ++ ), ++ _ToolRow( ++ "get_pathologies", ++ "Agent-workflow pathology distribution over a matched scope (#2383).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("pathology-report",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.get_pathologies:inspect.signature", ++ (), ++ (("limit", 1),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.pathology_report", ++ "polylogue.api.Polylogue.pathology_report", ++ ), ++ _ToolRow( ++ "get_postmortem_bundle", ++ "Distilled postmortem bundle over a matched session scope (#2380).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("postmortem-bundle",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.get_postmortem_bundle:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.postmortem_bundle", ++ "polylogue.api.Polylogue.postmortem_bundle", ++ ), ++ _ToolRow( ++ "get_resume_brief", ++ "Get a typed resume brief for one archived session.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("resume-brief",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.get_resume_brief:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.resume_brief", ++ "polylogue.api.Polylogue.resume_brief", ++ ), ++ _ToolRow( ++ "get_session_summary", ++ "Get session summary.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("session",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.get_session_summary:inspect.signature", ++ ("id",), ++ (("id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.get_summary", ++ None, ++ ), ++ _ToolRow( ++ "get_session_topology", ++ "Return typed lineage topology (ancestors/descendants/siblings/thread).", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.GET, ++ ("session", "topology-edge"), ++ MCPResultSemantics.RECURSIVE_GRAPH, ++ "polylogue.mcp.server_tools.get_session_topology:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "envelope", ++ ("ancestors", "descendants", "edges", "nodes", "root_id", "siblings", "target_id", "thread"), ++ "polylogue.api.Polylogue.get_session_topology", ++ "polylogue.api.Polylogue.get_session_topology", ++ ), ++ _ToolRow( ++ "get_session_tree", ++ "Get session tree.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.GET, ++ ("session", "lineage-edge"), ++ MCPResultSemantics.RECURSIVE_GRAPH, ++ "polylogue.mcp.server_tools.get_session_tree:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.get_session_tree", ++ "polylogue.api.Polylogue.get_session_tree", ++ ), ++ _ToolRow( ++ "get_stats_by", ++ "Session counts grouped by a calendar/origin dimension.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("archive-stats",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_tools.get_stats_by:inspect.signature", ++ (), ++ (("group_by", "origin"),), ++ "stats_map", ++ (), ++ "polylogue.api.Polylogue.get_stats_by", ++ "polylogue.api.Polylogue.get_stats_by", ++ ), ++ _ToolRow( ++ "import_annotation_batch", ++ "Import bounded JSONL labels as provenance-stamped candidates.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("annotation-batch", "assertion"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.import_annotation_batch:inspect.signature", ++ ( ++ "jsonl", ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "target_ref", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref", ++ ), ++ ( ++ ("actor_ref", "agent:contract"), ++ ("batch_id", "contract-batch"), ++ ("jsonl", '{"row_key":"r1","value":{},"evidence_refs":[]}'), ++ ("model_ref", "agent:model"), ++ ("prompt_ref", "block:contract:0"), ++ ("schema_id", "delegation.discourse"), ++ ("schema_version", 1), ++ ("source_result_ref", "result-set:contract"), ++ ("target_ref", "delegation:contract-target"), ++ ), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.import_annotation_batch", ++ "polylogue.api.Polylogue.import_annotation_batch", ++ ), ++ _ToolRow( ++ "insight_rigor_audit", ++ "Per-product rigor profile across materialized insights (#1275).", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("rigor-report",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.insight_rigor_audit:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.insight_rigor_audit", ++ "polylogue.api.Polylogue.insight_rigor_audit", ++ ), ++ _ToolRow( ++ "join_typed_annotations", ++ "Join typed annotation labels to exact structural targets.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("annotation", "object-ref"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.join_typed_annotations:inspect.signature", ++ ("schema_id", "schema_version", "statuses"), ++ (("limit", 1), ("schema_id", "discovery"), ("schema_version", 1), ("statuses", ["candidate"])), ++ "envelope", ++ ("groups", "joined_count", "rows", "selected_annotation_count"), ++ "polylogue.api.Polylogue.join_typed_annotations", ++ "polylogue.api.Polylogue.join_typed_annotations", ++ ), ++ _ToolRow( ++ "judge_assertion_candidate", ++ "Judge one candidate; caller text is provenance, not authorization.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_assertion_review_tools", ++ "review", ++ MCPVerb.JUDGE, ++ ("assertion-candidate", "judgment"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.judge_assertion_candidate:inspect.signature", ++ ("candidate_ref", "decision"), ++ (("candidate_ref", "assertion:contract-candidate"), ("decision", "accept")), ++ "envelope", ++ ("applied_count", "failed_count", "items"), ++ "polylogue.api.Polylogue.judge_assertion_candidates", ++ "polylogue.api.Polylogue.judge_assertion_candidates", ++ ), ++ _ToolRow( ++ "judge_assertion_candidates", ++ "Bulk judge candidates with independently reported partial success.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_assertion_review_tools", ++ "review", ++ MCPVerb.JUDGE, ++ ("assertion-candidate", "judgment"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.judge_assertion_candidates:inspect.signature", ++ ("items",), ++ (("items", [{"candidate_ref": "assertion:contract-candidate", "decision": "accept"}]),), ++ "envelope", ++ ("applied_count", "failed_count", "items"), ++ "polylogue.api.Polylogue.judge_assertion_candidates", ++ "polylogue.api.Polylogue.judge_assertion_candidates", ++ ), ++ _ToolRow( ++ "list_annotations", ++ "List annotations.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("annotation",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_personal_state_tools.list_annotations:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_annotations", ++ "polylogue.api.Polylogue.list_annotations", ++ ), ++ _ToolRow( ++ "list_assertion_candidate_reviews", ++ "List durable candidate review history separately from active claims.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("assertion-review",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.list_assertion_candidate_reviews:inspect.signature", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "limit", "total"), ++ "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ "polylogue.api.Polylogue.list_assertion_candidate_reviews", ++ ), ++ _ToolRow( ++ "list_assertion_candidates", ++ "List pending assertion candidates without promoting them.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("assertion-candidate",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.list_assertion_candidates:inspect.signature", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "limit", "total"), ++ "polylogue.api.Polylogue.list_assertion_candidates", ++ "polylogue.api.Polylogue.list_assertion_candidates", ++ ), ++ _ToolRow( ++ "list_assertion_claims", ++ "List assertion-backed lifecycle claims.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("assertion",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.list_assertion_claims:inspect.signature", ++ (), ++ (("limit", 1), ("target_ref", "session:test:conv-discovery-nonexistent")), ++ "envelope", ++ ("items", "limit", "total"), ++ "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ "polylogue.api.Polylogue.list_assertion_claim_payloads", ++ ), ++ _ToolRow( ++ "list_corrections", ++ "List stored learning corrections.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("correction",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_personal_state_tools.list_corrections:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("corrections", "total"), ++ "polylogue.api.Polylogue.list_corrections", ++ "polylogue.api.Polylogue.list_corrections", ++ ), ++ _ToolRow( ++ "list_marks", ++ "List marks.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("mark",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_mutation_tools.list_marks:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_marks", ++ "polylogue.api.Polylogue.list_marks", ++ ), ++ _ToolRow( ++ "list_read_view_profiles", ++ "List executable read-view profile metadata for agents.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("read-view-profile",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.list_read_view_profiles:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("read_views", "total"), ++ "polylogue.api.Polylogue.list_read_view_profiles", ++ "polylogue.api.Polylogue.list_read_view_profiles", ++ ), ++ _ToolRow( ++ "list_recall_packs", ++ "List recall packs.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("recall-pack",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_personal_state_tools.list_recall_packs:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_recall_packs", ++ "polylogue.api.Polylogue.list_recall_packs", ++ ), ++ _ToolRow( ++ "list_saved_views", ++ "List saved views.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("saved-query",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_personal_state_tools.list_saved_views:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_views", ++ "polylogue.api.Polylogue.list_views", ++ ), ++ _ToolRow( ++ "list_sessions", ++ "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session", "result-set"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.query_contracts.MCPSessionQueryRequest", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-sessions", ++ None, ++ ), ++ _ToolRow( ++ "list_tags", ++ "List deterministic tag-count pages without expanding a large tag map.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("tag",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_mutation_tools.list_tags:inspect.signature", ++ (), ++ (), ++ "stats_map", ++ (), ++ "polylogue.api.Polylogue.list_tags", ++ "polylogue.api.Polylogue.list_tags", ++ ), ++ _ToolRow( ++ "list_workspaces", ++ "List workspaces.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("reader-workspace",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_personal_state_tools.list_workspaces:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_workspaces", ++ "polylogue.api.Polylogue.list_workspaces", ++ ), ++ _ToolRow( ++ "maintenance_execute", ++ "Run (or dry-run) maintenance backfill targets.", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("maintenance-operation",), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.maintenance_execute:inspect.signature", ++ (), ++ (("dry_run", True),), ++ "operation_result", ++ (), ++ "polylogue.maintenance.planner.execute_backfill", ++ None, ++ ), ++ _ToolRow( ++ "maintenance_list", ++ "List every persisted maintenance operation snapshot (#1197).", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("maintenance-operation",), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.maintenance_list:inspect.signature", ++ (), ++ (), ++ "envelope", ++ ("items", "total"), ++ "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ None, ++ ), ++ _ToolRow( ++ "maintenance_preview", ++ "Dry-run summary of maintenance backfill targets.", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("maintenance-plan",), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.maintenance_preview:inspect.signature", ++ (), ++ (), ++ "operation_result", ++ (), ++ "polylogue.maintenance.planner.preview_backfill", ++ None, ++ ), ++ _ToolRow( ++ "maintenance_status", ++ "Return one persisted maintenance operation snapshot by id (#1197).", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("maintenance-operation",), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.maintenance_status:inspect.signature", ++ ("operation_id",), ++ (("operation_id", "operation:missing"),), ++ "operation_result", ++ (), ++ "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ None, ++ ), ++ _ToolRow( ++ "named_source_freshness", ++ "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight convergence.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("source", "freshness-receipt"), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.named_source_freshness:inspect.signature", ++ ("source_path",), ++ (("source_path", "/tmp/polylogue-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.archive.query.source_freshness.project_named_source_freshness", ++ None, ++ ), ++ _ToolRow( ++ "neighbor_candidates", ++ "Neighbor candidates.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session", "embedding-neighbor"), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.server_tools.neighbor_candidates:inspect.signature", ++ (), ++ (("query", "test"),), ++ "envelope", ++ ("items", "limit", "total"), ++ "polylogue.api.Polylogue.neighbor_candidates", ++ "polylogue.api.Polylogue.neighbor_candidates", ++ ), ++ _ToolRow( ++ "provider_usage", ++ "Return provider usage accounting diagnostics without estimating billing cost.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("usage-record",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_tools.provider_usage:inspect.signature", ++ (), ++ (("limit", 1),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.provider_usage_report", ++ "polylogue.api.Polylogue.provider_usage_report", ++ ), ++ _ToolRow( ++ "query_completions", ++ "Return shared query/action completion metadata for non-shell clients.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("query-vocabulary",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.query_completions:inspect.signature", ++ ("kind",), ++ (("incomplete", "d"), ("kind", "field")), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.query_completions", ++ "polylogue.api.Polylogue.query_completions", ++ ), ++ _ToolRow( ++ "query_units", ++ "Return terminal rows for explicit unit-source query expressions.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("query", "unit-row", "result-set"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.query_units:inspect.signature", ++ ("expression",), ++ (("expression", "messages where text:hello"), ("limit", 1)), ++ "envelope", ++ ("items", "limit", "offset", "total", "unit"), ++ "query-sessions", ++ None, ++ ), ++ _ToolRow( ++ "raw_artifacts", ++ "Raw artifacts.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.READ, ++ ("raw-artifact", "session"), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.server_tools.raw_artifacts:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "envelope", ++ ("raw_artifacts", "total"), ++ "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ ), ++ _ToolRow( ++ "readiness_check", ++ "Readiness check.", ++ "polylogue.mcp.server_tools", ++ "register_read_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("readiness-report",), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.readiness_check:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "project-archive-readiness", ++ None, ++ ), ++ _ToolRow( ++ "rebuild_index", ++ "Rebuild index.", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("index",), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.rebuild_index:inspect.signature", ++ (), ++ (), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.rebuild_index", ++ "polylogue.api.Polylogue.rebuild_index", ++ ), ++ _ToolRow( ++ "rebuild_session_insights", ++ "Rebuild session insights.", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("session-insight", "session"), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.rebuild_session_insights:inspect.signature", ++ (), ++ (("session_ids", []),), ++ "operation_result", ++ (), ++ "materialize-session-insights", ++ "polylogue.api.Polylogue.rebuild_insights", ++ ), ++ _ToolRow( ++ "record_correction", ++ "Record a user correction targeting a derived insight.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("correction", "session"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.record_correction:inspect.signature", ++ ("session_id", "kind", "payload"), ++ ( ++ ("author_kind", "agent"), ++ ("author_ref", "agent:codex-session:correction"), ++ ("kind", "summary_override"), ++ ("payload", {"summary": "Operator-authored summary."}), ++ ("session_id", "test:conv-mutation"), ++ ), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.record_correction", ++ "polylogue.api.Polylogue.record_correction", ++ ), ++ _ToolRow( ++ "remove_mark", ++ "Remove mark.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("mark", "object-ref"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.remove_mark:inspect.signature", ++ ("session_id", "mark_type"), ++ (("mark_type", "star"), ("session_id", "test:conv-mutation")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.remove_mark", ++ "polylogue.api.Polylogue.remove_mark", ++ ), ++ _ToolRow( ++ "remove_tag", ++ "Remove tag.", ++ "polylogue.mcp.server_mutation_tools", ++ "register_mutation_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("tag", "session"), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_mutation_tools.remove_tag:inspect.signature", ++ ("session_id", "tag"), ++ (("session_id", "test:conv-mutation"), ("tag", "review")), ++ "operation_result", ++ (), ++ "mutate-remove-tag", ++ "polylogue.api.Polylogue.remove_tag", ++ ), ++ _ToolRow( ++ "resolve_ref", ++ "Resolve a public object/evidence ref into a bounded payload.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.EXPLAIN, ++ ("object-ref", "evidence-ref"), ++ MCPResultSemantics.SINGLE_OBJECT, ++ "polylogue.mcp.server_tools.resolve_ref:inspect.signature", ++ ("ref",), ++ (("ref", "session:test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.resolve_ref", ++ "polylogue.api.Polylogue.resolve_ref", ++ ), ++ _ToolRow( ++ "save_annotation", ++ "Save annotation.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("annotation",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.save_annotation:inspect.signature", ++ ("annotation_id", "session_id", "note_text"), ++ (("annotation_id", "ann-1"), ("note_text", "hello"), ("session_id", "test:conv-mutation")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.save_annotation", ++ "polylogue.api.Polylogue.save_annotation", ++ ), ++ _ToolRow( ++ "save_recall_pack", ++ "Save recall pack.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("recall-pack",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.save_recall_pack:inspect.signature", ++ ("pack_id", "label"), ++ (("label", "lbl"), ("pack_id", "pack-1"), ("payload_json", '{"items":[]}')), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.create_recall_pack", ++ "polylogue.api.Polylogue.create_recall_pack", ++ ), ++ _ToolRow( ++ "save_saved_view", ++ "Save saved view.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("saved-query",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.save_saved_view:inspect.signature", ++ ("name", "query_json"), ++ (("name", "view-1"), ("query_json", "{}")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.save_view", ++ "polylogue.api.Polylogue.save_view", ++ ), ++ _ToolRow( ++ "save_workspace", ++ "Save workspace.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("reader-workspace",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.save_workspace:inspect.signature", ++ ("workspace_id", "name"), ++ (("name", "ws"), ("workspace_id", "ws-1")), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.save_workspace", ++ "polylogue.api.Polylogue.save_workspace", ++ ), ++ _ToolRow( ++ "search", ++ "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.QUERY, ++ ("session", "query-result"), ++ MCPResultSemantics.TOP_K, ++ "polylogue.mcp.query_contracts.MCPSessionQueryRequest", ++ ("query",), ++ (("limit", 1), ("query", "hello")), ++ "envelope", ++ ("hits", "total"), ++ "query-sessions", ++ None, ++ ), ++ _ToolRow( ++ "session_costs", ++ "List session costs from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-cost",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionCostInsightQuery)", ++ (), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_session_cost_insights", ++ "polylogue.api.Polylogue.list_session_cost_insights", ++ ), ++ _ToolRow( ++ "session_latency_profile", ++ "Get per-session latency profile by session ID.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("latency-profile",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.session_latency_profile:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ "polylogue.api.Polylogue.get_session_latency_profile_insight", ++ ), ++ _ToolRow( ++ "session_phases", ++ "List session phases from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-phase",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionPhaseInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-session-phases", ++ "polylogue.api.Polylogue.list_session_phase_insights", ++ ), ++ _ToolRow( ++ "session_profile", ++ "Get a single session profile by session ID.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-profile",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.session_profile:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_session_profile_insight", ++ "polylogue.api.Polylogue.get_session_profile_insight", ++ ), ++ _ToolRow( ++ "session_profiles", ++ "List session profiles from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("session-profile",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionProfileInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-session-profiles", ++ "polylogue.api.Polylogue.list_session_profile_insights", ++ ), ++ _ToolRow( ++ "session_tag_rollups", ++ "List session tag rollups from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("tag-rollup",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionTagRollupQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-session-tag-rollups", ++ "polylogue.api.Polylogue.list_session_tag_rollup_insights", ++ ), ++ _ToolRow( ++ "session_tool_timing", ++ "Get per-tool timing breakdown with evidence provenance.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("tool-timing",), ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ "polylogue.mcp.server_insight_tools.session_tool_timing:inspect.signature", ++ ("session_id",), ++ (("session_id", "test:conv-discovery-nonexistent"),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.get_session", ++ "polylogue.api.Polylogue.get_session", ++ ), ++ _ToolRow( ++ "session_work_events", ++ "List work events from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("work-event",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(SessionWorkEventInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-session-work-events", ++ "polylogue.api.Polylogue.list_session_work_event_insights", ++ ), ++ _ToolRow( ++ "set_metadata", ++ "Set metadata.", ++ "polylogue.mcp.server_personal_state_tools", ++ "register_personal_state_tools", ++ "write", ++ MCPVerb.WRITE, ++ ("session-metadata",), ++ MCPResultSemantics.MUTATION, ++ "polylogue.mcp.server_personal_state_tools.set_metadata:inspect.signature", ++ ("session_id", "key", "value"), ++ (("key", "note"), ("session_id", "test:conv-mutation"), ("value", "v")), ++ "operation_result", ++ (), ++ "mutate-set-metadata", ++ "polylogue.api.Polylogue.set_metadata", ++ ), ++ _ToolRow( ++ "stats", ++ "Stats.", ++ "polylogue.mcp.server_tools", ++ "register_query_tools", ++ "read", ++ MCPVerb.STATUS, ++ ("archive-stats",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_tools.stats:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ None, ++ ), ++ _ToolRow( ++ "threads", ++ "List work threads from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("thread",), ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ThreadInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-threads", ++ "polylogue.api.Polylogue.list_thread_insights", ++ ), ++ _ToolRow( ++ "tool_call_latency_distribution", ++ "Distribution of materialized per-session tool-call latency.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("latency-distribution",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.tool_call_latency_distribution:inspect.signature", ++ (), ++ (("limit", 1),), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.tool_call_latency_distribution", ++ "polylogue.api.Polylogue.tool_call_latency_distribution", ++ ), ++ _ToolRow( ++ "tool_usage", ++ "List tool usage from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("tool-usage",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(ToolUsageInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "query-tool-usage", ++ "polylogue.api.Polylogue.list_tool_usage_insights", ++ ), ++ _ToolRow( ++ "update_index", ++ "Update index.", ++ "polylogue.mcp.server_maintenance_tools", ++ "register_maintenance_tools", ++ "admin", ++ MCPVerb.MAINTENANCE, ++ ("index", "session"), ++ MCPResultSemantics.MAINTENANCE, ++ "polylogue.mcp.server_maintenance_tools.update_index:inspect.signature", ++ ("session_ids",), ++ (("session_ids", ["test:conv-discovery-nonexistent"]),), ++ "operation_result", ++ (), ++ "polylogue.api.Polylogue.update_index", ++ "polylogue.api.Polylogue.update_index", ++ ), ++ _ToolRow( ++ "usage_timeline", ++ "List usage timeline from the archive.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("usage-timeline",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.insight_tool_contracts.InsightListToolSpec(UsageTimelineInsightQuery)", ++ (), ++ (("limit", 1),), ++ "envelope", ++ ("items", "total"), ++ "polylogue.api.Polylogue.list_usage_timeline_insights", ++ "polylogue.api.Polylogue.list_usage_timeline_insights", ++ ), ++ _ToolRow( ++ "workflow_shape_distribution", ++ "Histogram session workflow shapes by week, origin, or project.", ++ "polylogue.mcp.server_insight_tools", ++ "register_insight_tools", ++ "read", ++ MCPVerb.READ, ++ ("workflow-shape",), ++ MCPResultSemantics.AGGREGATE, ++ "polylogue.mcp.server_insight_tools.workflow_shape_distribution:inspect.signature", ++ (), ++ (), ++ "single_object", ++ (), ++ "polylogue.api.Polylogue.workflow_shape_distribution", ++ "polylogue.api.Polylogue.workflow_shape_distribution", ++ ), ++) ++ ++_WORKFLOW_COVERAGE: Final[dict[str, tuple[str, ...]]] = { ++ "action_affordances": ("archive-evidence-read",), ++ "add_mark": ("user-state-mutation",), ++ "add_tag": ("user-state-mutation",), ++ "agent_coordination": ("multi-agent-coordination",), ++ "aggregate_sessions": ("archive-evidence-read",), ++ "archive_coverage": ("archive-evidence-read",), ++ "archive_debt": ("archive-evidence-read",), ++ "archive_get_session": ("archive-evidence-read",), ++ "archive_list_sessions": ("phrase-search", "structural-query", "continuation-recovery"), ++ "archive_search_sessions": ("phrase-search", "structural-query", "continuation-recovery"), ++ "blackboard_list": ("multi-agent-coordination",), ++ "blackboard_post": ("user-state-mutation",), ++ "build_context_image": ("continuity-handoff", "bounded-context-delivery"), ++ "bulk_tag_sessions": ("user-state-mutation",), ++ "capture_assertion_candidate": ("user-state-mutation",), ++ "clear_corrections": ("user-state-mutation",), ++ "compare_sessions": ("archive-evidence-read",), ++ "compile_context": ("continuity-handoff", "bounded-context-delivery"), ++ "compose_context_preamble": ("continuity-handoff", "bounded-context-delivery"), ++ "correlate_session": ("archive-evidence-read",), ++ "correlate_sessions": ("archive-evidence-read",), ++ "cost_outlook": ("archive-evidence-read",), ++ "cost_rollups": ("archive-evidence-read",), ++ "delete_annotation": ("user-state-mutation",), ++ "delete_metadata": ("user-state-mutation",), ++ "delete_recall_pack": ("user-state-mutation",), ++ "delete_saved_view": ("user-state-mutation",), ++ "delete_session": ("user-state-mutation",), ++ "delete_workspace": ("user-state-mutation",), ++ "embedding_preflight": ("archive-evidence-read",), ++ "embedding_status": ("archive-evidence-read",), ++ "explain_import": ("source-authority-audit",), ++ "explain_query_expression": ("phrase-search", "structural-query", "continuation-recovery"), ++ "facets": ("phrase-search", "structural-query", "continuation-recovery"), ++ "find_abandoned_sessions": ("archive-evidence-read",), ++ "find_resume_candidates": ("resume-context",), ++ "find_similar_sessions": ("archive-evidence-read",), ++ "find_stuck_sessions": ("archive-evidence-read",), ++ "get_context_delivery": ("continuity-handoff", "bounded-context-delivery"), ++ "get_logical_session": ("topology-reconstruction", "lineage-navigation"), ++ "get_messages": ("archive-evidence-read",), ++ "get_metadata": ("user-state-mutation",), ++ "get_pathologies": ("failure-postmortem",), ++ "get_postmortem_bundle": ("failure-postmortem",), ++ "get_resume_brief": ("resume-context",), ++ "get_session_summary": ("archive-evidence-read",), ++ "get_session_topology": ("topology-reconstruction", "lineage-navigation"), ++ "get_session_tree": ("topology-reconstruction", "lineage-navigation"), ++ "get_stats_by": ("archive-evidence-read",), ++ "import_annotation_batch": ("user-state-mutation",), ++ "insight_rigor_audit": ("archive-evidence-read",), ++ "join_typed_annotations": ("archive-evidence-read",), ++ "judge_assertion_candidate": ("assertion-review",), ++ "judge_assertion_candidates": ("assertion-review",), ++ "list_annotations": ("user-state-mutation",), ++ "list_assertion_candidate_reviews": ("archive-evidence-read",), ++ "list_assertion_candidates": ("archive-evidence-read",), ++ "list_assertion_claims": ("archive-evidence-read",), ++ "list_corrections": ("user-state-mutation",), ++ "list_marks": ("user-state-mutation",), ++ "list_read_view_profiles": ("archive-evidence-read",), ++ "list_recall_packs": ("user-state-mutation",), ++ "list_saved_views": ("user-state-mutation",), ++ "list_sessions": ("phrase-search", "structural-query", "continuation-recovery"), ++ "list_tags": ("user-state-mutation",), ++ "list_workspaces": ("user-state-mutation",), ++ "maintenance_execute": ("maintenance-lifecycle",), ++ "maintenance_list": ("maintenance-lifecycle",), ++ "maintenance_preview": ("maintenance-lifecycle",), ++ "maintenance_status": ("maintenance-lifecycle",), ++ "named_source_freshness": ("source-authority-audit",), ++ "neighbor_candidates": ("archive-evidence-read",), ++ "provider_usage": ("archive-evidence-read",), ++ "query_completions": ("phrase-search", "structural-query", "continuation-recovery"), ++ "query_units": ("phrase-search", "structural-query", "continuation-recovery"), ++ "raw_artifacts": ("source-authority-audit",), ++ "readiness_check": ("archive-evidence-read",), ++ "rebuild_index": ("maintenance-lifecycle",), ++ "rebuild_session_insights": ("maintenance-lifecycle",), ++ "record_correction": ("user-state-mutation",), ++ "remove_mark": ("user-state-mutation",), ++ "remove_tag": ("user-state-mutation",), ++ "resolve_ref": ("archive-evidence-read",), ++ "save_annotation": ("user-state-mutation",), ++ "save_recall_pack": ("user-state-mutation",), ++ "save_saved_view": ("user-state-mutation",), ++ "save_workspace": ("user-state-mutation",), ++ "search": ("phrase-search", "structural-query", "continuation-recovery"), ++ "session_costs": ("archive-evidence-read",), ++ "session_latency_profile": ("archive-evidence-read",), ++ "session_phases": ("archive-evidence-read",), ++ "session_profile": ("archive-evidence-read",), ++ "session_profiles": ("archive-evidence-read",), ++ "session_tag_rollups": ("archive-evidence-read",), ++ "session_tool_timing": ("archive-evidence-read",), ++ "session_work_events": ("archive-evidence-read",), ++ "set_metadata": ("user-state-mutation",), ++ "stats": ("archive-evidence-read",), ++ "threads": ("archive-evidence-read",), ++ "tool_call_latency_distribution": ("archive-evidence-read",), ++ "tool_usage": ("archive-evidence-read",), ++ "update_index": ("maintenance-lifecycle",), ++ "usage_timeline": ("archive-evidence-read",), ++ "workflow_shape_distribution": ("archive-evidence-read",), ++} ++ ++_PROMPT_ALTERNATIVES: Final[dict[str, tuple[str, ...]]] = { ++ "action_affordances": (), ++ "add_mark": (), ++ "add_tag": (), ++ "agent_coordination": ("agent_coordination_brief",), ++ "aggregate_sessions": (), ++ "archive_coverage": (), ++ "archive_debt": (), ++ "archive_get_session": (), ++ "archive_list_sessions": (), ++ "archive_search_sessions": (), ++ "blackboard_list": (), ++ "blackboard_post": (), ++ "build_context_image": (), ++ "bulk_tag_sessions": (), ++ "capture_assertion_candidate": (), ++ "clear_corrections": (), ++ "compare_sessions": ("compare_sessions",), ++ "compile_context": (), ++ "compose_context_preamble": (), ++ "correlate_session": ("sessions_touching_file",), ++ "correlate_sessions": (), ++ "cost_outlook": ("cost_of",), ++ "cost_rollups": (), ++ "delete_annotation": (), ++ "delete_metadata": (), ++ "delete_recall_pack": (), ++ "delete_saved_view": (), ++ "delete_session": (), ++ "delete_workspace": (), ++ "embedding_preflight": (), ++ "embedding_status": (), ++ "explain_import": (), ++ "explain_query_expression": (), ++ "facets": (), ++ "find_abandoned_sessions": (), ++ "find_resume_candidates": ("resume_context",), ++ "find_similar_sessions": (), ++ "find_stuck_sessions": (), ++ "get_context_delivery": (), ++ "get_logical_session": (), ++ "get_messages": (), ++ "get_metadata": (), ++ "get_pathologies": ("postmortem_last",), ++ "get_postmortem_bundle": ("postmortem_last",), ++ "get_resume_brief": ("resume_context",), ++ "get_session_summary": (), ++ "get_session_topology": (), ++ "get_session_tree": (), ++ "get_stats_by": (), ++ "import_annotation_batch": (), ++ "insight_rigor_audit": (), ++ "join_typed_annotations": (), ++ "judge_assertion_candidate": (), ++ "judge_assertion_candidates": (), ++ "list_annotations": (), ++ "list_assertion_candidate_reviews": (), ++ "list_assertion_candidates": (), ++ "list_assertion_claims": (), ++ "list_corrections": (), ++ "list_marks": (), ++ "list_read_view_profiles": (), ++ "list_recall_packs": (), ++ "list_saved_views": (), ++ "list_sessions": (), ++ "list_tags": (), ++ "list_workspaces": (), ++ "maintenance_execute": (), ++ "maintenance_list": (), ++ "maintenance_preview": (), ++ "maintenance_status": (), ++ "named_source_freshness": (), ++ "neighbor_candidates": (), ++ "provider_usage": (), ++ "query_completions": (), ++ "query_units": (), ++ "raw_artifacts": (), ++ "readiness_check": (), ++ "rebuild_index": (), ++ "rebuild_session_insights": (), ++ "record_correction": (), ++ "remove_mark": (), ++ "remove_tag": (), ++ "resolve_ref": (), ++ "save_annotation": (), ++ "save_recall_pack": (), ++ "save_saved_view": (), ++ "save_workspace": (), ++ "search": (), ++ "session_costs": ("cost_of",), ++ "session_latency_profile": (), ++ "session_phases": (), ++ "session_profile": (), ++ "session_profiles": (), ++ "session_tag_rollups": (), ++ "session_tool_timing": (), ++ "session_work_events": (), ++ "set_metadata": (), ++ "stats": (), ++ "threads": (), ++ "tool_call_latency_distribution": (), ++ "tool_usage": (), ++ "update_index": (), ++ "usage_timeline": (), ++ "workflow_shape_distribution": (), ++} ++ ++_RESOURCE_ALTERNATIVES: Final[dict[str, tuple[str, ...]]] = { ++ "action_affordances": (), ++ "add_mark": (), ++ "add_tag": ("polylogue://session/{id}",), ++ "agent_coordination": (), ++ "aggregate_sessions": (), ++ "archive_coverage": (), ++ "archive_debt": (), ++ "archive_get_session": ("polylogue://session/{id}",), ++ "archive_list_sessions": ("polylogue://session/{id}", "polylogue://result-set/{id}"), ++ "archive_search_sessions": ("polylogue://session/{id}", "polylogue://result-set/{id}"), ++ "blackboard_list": (), ++ "blackboard_post": (), ++ "build_context_image": ("polylogue://session/{id}",), ++ "bulk_tag_sessions": ("polylogue://session/{id}",), ++ "capture_assertion_candidate": (), ++ "clear_corrections": ("polylogue://session/{id}",), ++ "compare_sessions": (), ++ "compile_context": ("polylogue://context-snapshot/{id}",), ++ "compose_context_preamble": (), ++ "correlate_session": (), ++ "correlate_sessions": (), ++ "cost_outlook": (), ++ "cost_rollups": (), ++ "delete_annotation": (), ++ "delete_metadata": (), ++ "delete_recall_pack": ("polylogue://recall-pack/{id}",), ++ "delete_saved_view": ("polylogue://saved-query/{id}",), ++ "delete_session": ("polylogue://session/{id}",), ++ "delete_workspace": (), ++ "embedding_preflight": (), ++ "embedding_status": (), ++ "explain_import": (), ++ "explain_query_expression": (), ++ "facets": (), ++ "find_abandoned_sessions": ("polylogue://session/{id}",), ++ "find_resume_candidates": ("polylogue://session/{id}",), ++ "find_similar_sessions": ("polylogue://session/{id}",), ++ "find_stuck_sessions": ("polylogue://session/{id}",), ++ "get_context_delivery": ("polylogue://context-snapshot/{id}",), ++ "get_logical_session": ("polylogue://session/{id}",), ++ "get_messages": ("polylogue://message/{id}", "polylogue://session/{id}"), ++ "get_metadata": (), ++ "get_pathologies": (), ++ "get_postmortem_bundle": (), ++ "get_resume_brief": (), ++ "get_session_summary": ("polylogue://session/{id}",), ++ "get_session_topology": ("polylogue://session/{id}",), ++ "get_session_tree": ("polylogue://session/{id}",), ++ "get_stats_by": (), ++ "import_annotation_batch": (), ++ "insight_rigor_audit": (), ++ "join_typed_annotations": (), ++ "judge_assertion_candidate": (), ++ "judge_assertion_candidates": (), ++ "list_annotations": (), ++ "list_assertion_candidate_reviews": (), ++ "list_assertion_candidates": (), ++ "list_assertion_claims": (), ++ "list_corrections": (), ++ "list_marks": (), ++ "list_read_view_profiles": (), ++ "list_recall_packs": ("polylogue://recall-pack/{id}",), ++ "list_saved_views": ("polylogue://saved-query/{id}",), ++ "list_sessions": ("polylogue://session/{id}", "polylogue://result-set/{id}"), ++ "list_tags": (), ++ "list_workspaces": (), ++ "maintenance_execute": ("polylogue://maintenance-operation/{id}",), ++ "maintenance_list": ("polylogue://maintenance-operation/{id}",), ++ "maintenance_preview": (), ++ "maintenance_status": ("polylogue://maintenance-operation/{id}",), ++ "named_source_freshness": (), ++ "neighbor_candidates": ("polylogue://session/{id}",), ++ "provider_usage": (), ++ "query_completions": (), ++ "query_units": ("polylogue://query/{id}", "polylogue://result-set/{id}"), ++ "raw_artifacts": ("polylogue://session/{id}",), ++ "readiness_check": (), ++ "rebuild_index": (), ++ "rebuild_session_insights": ("polylogue://session/{id}",), ++ "record_correction": ("polylogue://session/{id}",), ++ "remove_mark": (), ++ "remove_tag": ("polylogue://session/{id}",), ++ "resolve_ref": (), ++ "save_annotation": (), ++ "save_recall_pack": ("polylogue://recall-pack/{id}",), ++ "save_saved_view": ("polylogue://saved-query/{id}",), ++ "save_workspace": (), ++ "search": ("polylogue://session/{id}",), ++ "session_costs": (), ++ "session_latency_profile": (), ++ "session_phases": (), ++ "session_profile": (), ++ "session_profiles": (), ++ "session_tag_rollups": (), ++ "session_tool_timing": (), ++ "session_work_events": (), ++ "set_metadata": (), ++ "stats": (), ++ "threads": (), ++ "tool_call_latency_distribution": (), ++ "tool_usage": (), ++ "update_index": ("polylogue://session/{id}",), ++ "usage_timeline": (), ++ "workflow_shape_distribution": (), ++} ++ ++_DISCOVERY: Final[dict[str, tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]]]] = { ++ "action_affordances": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "add_mark": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "add_tag": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "agent_coordination": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "aggregate_sessions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "archive_coverage": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "archive_debt": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "archive_get_session": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "archive_list_sessions": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "archive_search_sessions": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "blackboard_list": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "blackboard_post": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "build_context_image": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "bulk_tag_sessions": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "capture_assertion_candidate": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "clear_corrections": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "compare_sessions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "compile_context": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "compose_context_preamble": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "correlate_session": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "correlate_sessions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "cost_outlook": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "cost_rollups": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "delete_annotation": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "delete_metadata": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "delete_recall_pack": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "delete_saved_view": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "delete_session": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "delete_workspace": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "embedding_preflight": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "embedding_status": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "explain_import": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "explain_query_expression": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "facets": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "find_abandoned_sessions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "find_resume_candidates": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "find_similar_sessions": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "find_stuck_sessions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_context_delivery": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_logical_session": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_messages": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_metadata": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "get_pathologies": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_postmortem_bundle": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_resume_brief": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_session_summary": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_session_topology": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_session_tree": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "get_stats_by": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "import_annotation_batch": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "insight_rigor_audit": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "join_typed_annotations": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "judge_assertion_candidate": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "judge_assertion_candidates": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_annotations": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_assertion_candidate_reviews": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "list_assertion_candidates": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "list_assertion_claims": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "list_corrections": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_marks": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_read_view_profiles": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "list_recall_packs": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_saved_views": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_sessions": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "list_tags": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "list_workspaces": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "maintenance_execute": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "maintenance_list": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "maintenance_preview": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "maintenance_status": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "named_source_freshness": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "neighbor_candidates": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "provider_usage": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "query_completions": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "query_units": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "raw_artifacts": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "readiness_check": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "rebuild_index": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "rebuild_session_insights": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "record_correction": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "remove_mark": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "remove_tag": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "resolve_ref": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "save_annotation": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "save_recall_pack": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "save_saved_view": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "save_workspace": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "search": ( ++ ("query_completions", "explain_query_expression"), ++ ("facets", "query_completions"), ++ ("query_completions",), ++ ), ++ "session_costs": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_latency_profile": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_phases": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_profile": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_profiles": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_tag_rollups": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_tool_timing": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "session_work_events": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "set_metadata": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "stats": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "threads": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "tool_call_latency_distribution": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "tool_usage": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "update_index": (("action_affordances",), ("action_affordances",), ("action_affordances",)), ++ "usage_timeline": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++ "workflow_shape_distribution": (("action_affordances",), ("action_affordances",), ("query_completions",)), ++} ++ ++_CONTINUATION: Final[dict[str, tuple[str, str | None, str | None, str]]] = { ++ "action_affordances": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "add_mark": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "add_tag": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "agent_coordination": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "aggregate_sessions": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "archive_coverage": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "archive_debt": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "archive_get_session": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "archive_list_sessions": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:query(plan, projection, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "archive_search_sessions": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "blackboard_list": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "blackboard_post": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "build_context_image": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "bulk_tag_sessions": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "capture_assertion_candidate": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "clear_corrections": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "compare_sessions": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "compile_context": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "compose_context_preamble": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "correlate_session": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "correlate_sessions": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "cost_outlook": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "cost_rollups": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_annotation": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_metadata": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_recall_pack": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_saved_view": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_session": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "delete_workspace": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "embedding_preflight": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "embedding_status": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "explain_import": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "explain_query_expression": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "facets": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "find_abandoned_sessions": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "find_resume_candidates": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "find_similar_sessions": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "find_stuck_sessions": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "get_context_delivery": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "get_logical_session": ( ++ "recursive_page", ++ "result-set:{graph-hash}:{cursor}", ++ "target:graph(ref, direction, page)", ++ "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation.", ++ ), ++ "get_messages": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "get_metadata": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "get_pathologies": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "get_postmortem_bundle": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "get_resume_brief": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "get_session_summary": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "get_session_topology": ( ++ "recursive_page", ++ "result-set:{graph-hash}:{cursor}", ++ "target:graph(ref, direction, page)", ++ "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation.", ++ ), ++ "get_session_tree": ( ++ "recursive_page", ++ "result-set:{graph-hash}:{cursor}", ++ "target:graph(ref, direction, page)", ++ "The compatibility route may return a bounded graph orientation; the target graph transaction owns recursive continuation.", ++ ), ++ "get_stats_by": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "import_annotation_batch": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "insight_rigor_audit": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "join_typed_annotations": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "judge_assertion_candidate": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "judge_assertion_candidates": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "list_annotations": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_assertion_candidate_reviews": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_assertion_candidates": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_assertion_claims": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_corrections": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_marks": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_read_view_profiles": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:explain(ref-or-plan)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_recall_packs": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_saved_views": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_sessions": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:query(plan, projection, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_tags": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "list_workspaces": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:write(operation, object-ref, authorization)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "maintenance_execute": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "maintenance_list": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "maintenance_preview": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "maintenance_status": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "named_source_freshness": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "neighbor_candidates": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "provider_usage": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "query_completions": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "query_units": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:query(plan, projection, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "raw_artifacts": ( ++ "cursor_or_offset", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "readiness_check": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "rebuild_index": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "rebuild_session_insights": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "record_correction": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "remove_mark": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "remove_tag": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "resolve_ref": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "save_annotation": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "save_recall_pack": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "save_saved_view": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "save_workspace": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "search": ( ++ "ranked_frontier", ++ None, ++ "target:query(plan, projection, page)", ++ "Ranking is not an exhaustive relation; the declaration names the exhaustive query route when one exists.", ++ ), ++ "session_costs": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "session_latency_profile": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "session_phases": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "session_profile": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "session_profiles": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "session_tag_rollups": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "session_tool_timing": ( ++ "bounded_context", ++ None, ++ "target:read(ref, view, page)", ++ "This route is intentionally budgeted context/orientation, not an exhaustive archive relation.", ++ ), ++ "session_work_events": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "set_metadata": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "stats": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "threads": ( ++ "bounded_page", ++ "result-set:{query-hash}:{cursor}", ++ "target:read(ref, view, page)", ++ "Physical pages are bounded; logical completeness requires following the returned cursor/offset or the target query route.", ++ ), ++ "tool_call_latency_distribution": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "tool_usage": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "update_index": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "usage_timeline": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++ "workflow_shape_distribution": ( ++ "none", ++ None, ++ None, ++ "The result is a single object, aggregate, or governed operation result with no row continuation.", ++ ), ++} ++ ++ ++_TARGET_ROUTE: Final[dict[str, str]] = { ++ "action_affordances": "target:explain(ref-or-plan)", ++ "add_mark": "target:write(operation, object-ref, authorization)", ++ "add_tag": "target:write(operation, object-ref, authorization)", ++ "agent_coordination": "target:status(ref-or-domain)", ++ "aggregate_sessions": "target:read(ref, view, page)", ++ "archive_coverage": "target:read(ref, view, page)", ++ "archive_debt": "target:status(ref-or-domain)", ++ "archive_get_session": "target:read(ref, view, page)", ++ "archive_list_sessions": "target:query(plan, projection, page)", ++ "archive_search_sessions": "target:query(plan, projection, page)", ++ "blackboard_list": "target:read(ref, view, page)", ++ "blackboard_post": "target:write(operation, object-ref, authorization)", ++ "build_context_image": "target:context(seed, policy, budget)", ++ "bulk_tag_sessions": "target:write(operation, object-ref, authorization)", ++ "capture_assertion_candidate": "target:write(operation, object-ref, authorization)", ++ "clear_corrections": "target:write(operation, object-ref, authorization)", ++ "compare_sessions": "target:read(ref, view, page)", ++ "compile_context": "target:context(seed, policy, budget)", ++ "compose_context_preamble": "target:context(seed, policy, budget)", ++ "correlate_session": "target:read(ref, view, page)", ++ "correlate_sessions": "target:read(ref, view, page)", ++ "cost_outlook": "target:read(ref, view, page)", ++ "cost_rollups": "target:read(ref, view, page)", ++ "delete_annotation": "target:write(operation, object-ref, authorization)", ++ "delete_metadata": "target:write(operation, object-ref, authorization)", ++ "delete_recall_pack": "target:write(operation, object-ref, authorization)", ++ "delete_saved_view": "target:write(operation, object-ref, authorization)", ++ "delete_session": "target:write(operation, object-ref, authorization)", ++ "delete_workspace": "target:write(operation, object-ref, authorization)", ++ "embedding_preflight": "target:status(ref-or-domain)", ++ "embedding_status": "target:status(ref-or-domain)", ++ "explain_import": "target:explain(ref-or-plan)", ++ "explain_query_expression": "target:explain(ref-or-plan)", ++ "facets": "target:query(plan, projection, page)", ++ "find_abandoned_sessions": "target:read(ref, view, page)", ++ "find_resume_candidates": "target:read(ref, view, page)", ++ "find_similar_sessions": "target:query(plan, projection, page)", ++ "find_stuck_sessions": "target:read(ref, view, page)", ++ "get_context_delivery": "target:context(seed, policy, budget)", ++ "get_logical_session": "target:graph(ref, direction, page)", ++ "get_messages": "target:read(ref, view, page)", ++ "get_metadata": "target:write(operation, object-ref, authorization)", ++ "get_pathologies": "target:read(ref, view, page)", ++ "get_postmortem_bundle": "target:read(ref, view, page)", ++ "get_resume_brief": "target:read(ref, view, page)", ++ "get_session_summary": "target:read(ref, view, page)", ++ "get_session_topology": "target:graph(ref, direction, page)", ++ "get_session_tree": "target:graph(ref, direction, page)", ++ "get_stats_by": "target:status(ref-or-domain)", ++ "import_annotation_batch": "target:write(operation, object-ref, authorization)", ++ "insight_rigor_audit": "target:status(ref-or-domain)", ++ "join_typed_annotations": "target:read(ref, view, page)", ++ "judge_assertion_candidate": "target:judge(candidate-ref, decision, authorization)", ++ "judge_assertion_candidates": "target:judge(candidate-ref, decision, authorization)", ++ "list_annotations": "target:write(operation, object-ref, authorization)", ++ "list_assertion_candidate_reviews": "target:read(ref, view, page)", ++ "list_assertion_candidates": "target:read(ref, view, page)", ++ "list_assertion_claims": "target:read(ref, view, page)", ++ "list_corrections": "target:write(operation, object-ref, authorization)", ++ "list_marks": "target:write(operation, object-ref, authorization)", ++ "list_read_view_profiles": "target:explain(ref-or-plan)", ++ "list_recall_packs": "target:write(operation, object-ref, authorization)", ++ "list_saved_views": "target:write(operation, object-ref, authorization)", ++ "list_sessions": "target:query(plan, projection, page)", ++ "list_tags": "target:write(operation, object-ref, authorization)", ++ "list_workspaces": "target:write(operation, object-ref, authorization)", ++ "maintenance_execute": "target:maintenance(plan-or-operation-ref, authorization)", ++ "maintenance_list": "target:maintenance(plan-or-operation-ref, authorization)", ++ "maintenance_preview": "target:maintenance(plan-or-operation-ref, authorization)", ++ "maintenance_status": "target:maintenance(plan-or-operation-ref, authorization)", ++ "named_source_freshness": "target:status(ref-or-domain)", ++ "neighbor_candidates": "target:query(plan, projection, page)", ++ "provider_usage": "target:status(ref-or-domain)", ++ "query_completions": "target:explain(ref-or-plan)", ++ "query_units": "target:query(plan, projection, page)", ++ "raw_artifacts": "target:read(ref, view, page)", ++ "readiness_check": "target:status(ref-or-domain)", ++ "rebuild_index": "target:maintenance(plan-or-operation-ref, authorization)", ++ "rebuild_session_insights": "target:maintenance(plan-or-operation-ref, authorization)", ++ "record_correction": "target:write(operation, object-ref, authorization)", ++ "remove_mark": "target:write(operation, object-ref, authorization)", ++ "remove_tag": "target:write(operation, object-ref, authorization)", ++ "resolve_ref": "target:explain(ref-or-plan)", ++ "save_annotation": "target:write(operation, object-ref, authorization)", ++ "save_recall_pack": "target:write(operation, object-ref, authorization)", ++ "save_saved_view": "target:write(operation, object-ref, authorization)", ++ "save_workspace": "target:write(operation, object-ref, authorization)", ++ "search": "target:query(plan, projection, page)", ++ "session_costs": "target:read(ref, view, page)", ++ "session_latency_profile": "target:read(ref, view, page)", ++ "session_phases": "target:read(ref, view, page)", ++ "session_profile": "target:read(ref, view, page)", ++ "session_profiles": "target:read(ref, view, page)", ++ "session_tag_rollups": "target:read(ref, view, page)", ++ "session_tool_timing": "target:read(ref, view, page)", ++ "session_work_events": "target:read(ref, view, page)", ++ "set_metadata": "target:write(operation, object-ref, authorization)", ++ "stats": "target:status(ref-or-domain)", ++ "threads": "target:read(ref, view, page)", ++ "tool_call_latency_distribution": "target:read(ref, view, page)", ++ "tool_usage": "target:read(ref, view, page)", ++ "update_index": "target:maintenance(plan-or-operation-ref, authorization)", ++ "usage_timeline": "target:read(ref, view, page)", ++ "workflow_shape_distribution": "target:read(ref, view, page)", ++} ++ ++_OBSERVED_USE: Final[dict[str, ObservedUse]] = { ++ "action_affordances": "unknown", ++ "add_mark": "unknown", ++ "add_tag": "unknown", ++ "agent_coordination": "unknown", ++ "aggregate_sessions": "unknown", ++ "archive_coverage": "unknown", ++ "archive_debt": "unknown", ++ "archive_get_session": "unknown", ++ "archive_list_sessions": "not_observed", ++ "archive_search_sessions": "not_observed", ++ "blackboard_list": "unknown", ++ "blackboard_post": "unknown", ++ "build_context_image": "unknown", ++ "bulk_tag_sessions": "unknown", ++ "capture_assertion_candidate": "unknown", ++ "clear_corrections": "unknown", ++ "compare_sessions": "unknown", ++ "compile_context": "unknown", ++ "compose_context_preamble": "unknown", ++ "correlate_session": "unknown", ++ "correlate_sessions": "unknown", ++ "cost_outlook": "unknown", ++ "cost_rollups": "unknown", ++ "delete_annotation": "unknown", ++ "delete_metadata": "unknown", ++ "delete_recall_pack": "unknown", ++ "delete_saved_view": "unknown", ++ "delete_session": "unknown", ++ "delete_workspace": "unknown", ++ "embedding_preflight": "unknown", ++ "embedding_status": "unknown", ++ "explain_import": "unknown", ++ "explain_query_expression": "unknown", ++ "facets": "unknown", ++ "find_abandoned_sessions": "unknown", ++ "find_resume_candidates": "unknown", ++ "find_similar_sessions": "unknown", ++ "find_stuck_sessions": "unknown", ++ "get_context_delivery": "unknown", ++ "get_logical_session": "unknown", ++ "get_messages": "unknown", ++ "get_metadata": "unknown", ++ "get_pathologies": "unknown", ++ "get_postmortem_bundle": "unknown", ++ "get_resume_brief": "unknown", ++ "get_session_summary": "unknown", ++ "get_session_topology": "unknown", ++ "get_session_tree": "unknown", ++ "get_stats_by": "unknown", ++ "import_annotation_batch": "unknown", ++ "insight_rigor_audit": "unknown", ++ "join_typed_annotations": "unknown", ++ "judge_assertion_candidate": "unknown", ++ "judge_assertion_candidates": "unknown", ++ "list_annotations": "unknown", ++ "list_assertion_candidate_reviews": "unknown", ++ "list_assertion_candidates": "unknown", ++ "list_assertion_claims": "unknown", ++ "list_corrections": "unknown", ++ "list_marks": "unknown", ++ "list_read_view_profiles": "unknown", ++ "list_recall_packs": "unknown", ++ "list_saved_views": "unknown", ++ "list_sessions": "observed", ++ "list_tags": "unknown", ++ "list_workspaces": "unknown", ++ "maintenance_execute": "unknown", ++ "maintenance_list": "unknown", ++ "maintenance_preview": "unknown", ++ "maintenance_status": "unknown", ++ "named_source_freshness": "unknown", ++ "neighbor_candidates": "unknown", ++ "provider_usage": "unknown", ++ "query_completions": "unknown", ++ "query_units": "observed", ++ "raw_artifacts": "unknown", ++ "readiness_check": "unknown", ++ "rebuild_index": "unknown", ++ "rebuild_session_insights": "unknown", ++ "record_correction": "unknown", ++ "remove_mark": "unknown", ++ "remove_tag": "unknown", ++ "resolve_ref": "unknown", ++ "save_annotation": "unknown", ++ "save_recall_pack": "unknown", ++ "save_saved_view": "unknown", ++ "save_workspace": "unknown", ++ "search": "observed", ++ "session_costs": "unknown", ++ "session_latency_profile": "unknown", ++ "session_phases": "unknown", ++ "session_profile": "unknown", ++ "session_profiles": "unknown", ++ "session_tag_rollups": "unknown", ++ "session_tool_timing": "unknown", ++ "session_work_events": "unknown", ++ "set_metadata": "unknown", ++ "stats": "unknown", ++ "threads": "unknown", ++ "tool_call_latency_distribution": "unknown", ++ "tool_usage": "unknown", ++ "update_index": "unknown", ++ "usage_timeline": "unknown", ++ "workflow_shape_distribution": "unknown", ++} ++ ++ ++def _validate_inventory_tables() -> None: ++ names = {row.name for row in _TOOL_ROWS} ++ tables: tuple[tuple[str, Mapping[str, object]], ...] = ( ++ ("discovery", _DISCOVERY), ++ ("continuation", _CONTINUATION), ++ ("resource alternatives", _RESOURCE_ALTERNATIVES), ++ ("prompt alternatives", _PROMPT_ALTERNATIVES), ++ ("target routes", _TARGET_ROUTE), ++ ("workflow coverage", _WORKFLOW_COVERAGE), ++ ("observed use", _OBSERVED_USE), ++ ) ++ for label, table in tables: ++ missing = sorted(names - set(table)) ++ extra = sorted(set(table) - names) ++ if missing or extra: ++ raise RuntimeError( ++ f"MCP declaration {label} table drift: missing={missing}, extra={extra}; " ++ f"repair with `{_REPAIR_COMMAND}`" ++ ) ++ ++ ++_validate_inventory_tables() ++ ++ ++def _compatibility(row: _ToolRow) -> CompatibilityKey: ++ return CompatibilityKey( ++ identity="mcp-tool:" + ",".join(row.object_kinds), ++ lifecycle="registered-handler-retained", ++ authority=f"mcp-role:{row.minimum_role}", ++ access_result_shape=f"{row.verb.value}:{row.result_semantics.value}:{row.output_kind}", ++ durability="transport-adapter; domain-owner-controls-durability", ++ ) ++ ++ ++def _family_id(row: _ToolRow) -> str: ++ # Each live compatibility tool is its own family in this slice. Shared ++ # derivation families are introduced only when every compatibility ++ # dimension is intentionally identical; grouping by implementation shape ++ # alone would erase distinct ObjectRef and operation semantics. ++ return f"mcp.tool.{row.name}" ++ ++ ++def _python_parity(row: _ToolRow) -> PythonParityExpectation: ++ if row.python_binding is not None: ++ return PythonParityExpectation(binding=row.python_binding) ++ return PythonParityExpectation( ++ intentional_absence_authority="polylogue-s1kr", ++ reason=( ++ "The current MCP compatibility handler binds a lower-level owner or transport-only projection; " ++ "polylogue-s1kr owns any public Python facade addition and docs parity." ++ ), ++ ) ++ ++ ++def _build_declaration(row: _ToolRow) -> MCPToolDeclaration: ++ grammar, fields, values = _DISCOVERY[row.name] ++ continuation_mode, continuation_ref, exhaustive_route, continuation_notes = _CONTINUATION[row.name] ++ retirement_owner = "polylogue-t46.8.2" if row.minimum_role == "read" else "polylogue-t46.8.3" ++ deprecation = ( ++ MCPDeprecationState.COMPATIBILITY ++ if row.name in {"archive_list_sessions", "archive_search_sessions"} ++ else MCPDeprecationState.RETAINED ++ ) ++ kernel = DeclarationSpec( ++ declaration_id=f"mcp.tool.{row.name}", ++ family_id=_family_id(row), ++ public_name=row.name, ++ owner_path="polylogue/mcp/declarations/registry.py", ++ compatibility=_compatibility(row), ++ producer=row.operation_owner, ++ role_gate=f"mcp.role:{row.minimum_role}", ++ schema_ref=row.schema_source, ++ discovery_text=row.description, ++ repair_command=_REPAIR_COMMAND, ++ handlers=( ++ HandlerBinding( ++ surface="mcp", ++ owner_path=row.module.replace(".", "/") + ".py", ++ symbol=row.name, ++ binding_key=f"{row.module}:{row.name}", ++ ), ++ ), ++ outputs=( ++ OutputSpec( ++ name="runtime-contract", ++ kind=row.output_kind, ++ schema_ref="tests/unit/mcp/test_envelope_contracts.py::TOOL_CONTRACT", ++ target_path=f"mcp://tool/{row.name}", ++ ), ++ OutputSpec( ++ name="equivalence-map", ++ kind="generated-json", ++ schema_ref="polylogue.mcp.declarations.models.MCPToolDeclaration", ++ target_path="docs/generated/mcp-equivalence.json", ++ ), ++ ), ++ examples=( ++ ExampleSpec( ++ name="minimal-valid-call", ++ summary=f"Minimal compatibility invocation for {row.name}.", ++ arguments=row.minimal_arguments, ++ ), ++ ), ++ completeness_edges=( ++ CompletenessEdge( ++ producer=f"mcp.tool.{row.name}", ++ consumer="tests.infra.mcp.EXPECTED_TOOL_NAMES", ++ kind="discovery-name-equivalence", ++ owner_path="tests/infra/mcp.py", ++ ), ++ CompletenessEdge( ++ producer=f"mcp.tool.{row.name}", ++ consumer="docs/generated/mcp-equivalence.json", ++ kind="generated-equivalence-row", ++ owner_path="devtools/render_mcp_equivalence.py", ++ ), ++ ), ++ ) ++ return MCPToolDeclaration( ++ kernel=kernel, ++ name=row.name, ++ description=row.description, ++ verb=row.verb, ++ object_kinds=row.object_kinds, ++ minimum_role=row.minimum_role, ++ capability=f"{row.minimum_role}:{row.verb.value}", ++ result_semantics=row.result_semantics, ++ canonical_plan=row.operation_owner, ++ canonical_projection=f"{row.output_kind}:{','.join(row.envelope_fields) or 'root'}", ++ input_contract=MCPInputContract( ++ schema_source=row.schema_source, ++ schema_mode="FastMCP derives inputSchema from the unchanged handler object/signature", ++ required_arguments=row.required_arguments, ++ ), ++ output_contract=MCPOutputContract(kind=row.output_kind, envelope_fields=row.envelope_fields), ++ minimal_arguments=row.minimal_arguments, ++ grammar_discovery=grammar, ++ field_discovery=fields, ++ value_discovery=values, ++ continuation=MCPContinuationContract( ++ mode=continuation_mode, ++ continuation_ref=continuation_ref, ++ exhaustive_route=exhaustive_route, ++ notes=continuation_notes, ++ ), ++ resource_alternatives=_RESOURCE_ALTERNATIVES[row.name], ++ prompt_alternatives=_PROMPT_ALTERNATIVES[row.name], ++ compatibility_route=_TARGET_ROUTE[row.name], ++ workflow_coverage=_WORKFLOW_COVERAGE[row.name], ++ incident_coverage=( ++ "z9gh-workflow-incident" ++ if row.name ++ in { ++ "search", ++ "query_units", ++ "list_sessions", ++ "archive_list_sessions", ++ "archive_search_sessions", ++ "query_completions", ++ "explain_query_expression", ++ } ++ else "mcp-contract-census", ++ ), ++ observed_use=_OBSERVED_USE[row.name], ++ telemetry_key=row.name, ++ deprecation_state=deprecation, ++ retirement_owner=retirement_owner, ++ registration=MCPHandlerBinding(module=row.module, symbol=row.name, registrar=row.registrar), ++ operation_owner=row.operation_owner, ++ python_parity=_python_parity(row), ++ ) ++ ++ ++MCP_TOOL_DECLARATIONS: Final[tuple[MCPToolDeclaration, ...]] = tuple(_build_declaration(row) for row in _TOOL_ROWS) ++MCP_TOOL_DECLARATION_BY_NAME: Final[dict[str, MCPToolDeclaration]] = { ++ declaration.name: declaration for declaration in MCP_TOOL_DECLARATIONS ++} ++if len(MCP_TOOL_DECLARATION_BY_NAME) != len(MCP_TOOL_DECLARATIONS): ++ raise RuntimeError("duplicate MCP declaration name") ++ ++MCP_KERNEL_REGISTRY = DeclarationRegistry() ++for _declaration in MCP_TOOL_DECLARATIONS: ++ MCP_KERNEL_REGISTRY.register(_declaration.kernel) ++_MCP_DIAGNOSTICS = validate_registry(MCP_KERNEL_REGISTRY) ++if _MCP_DIAGNOSTICS: ++ raise RuntimeError("incomplete MCP declaration registry: " + "; ".join(item.message for item in _MCP_DIAGNOSTICS)) ++ ++ ++def declaration_for_tool(name: str) -> MCPToolDeclaration: ++ try: ++ return MCP_TOOL_DECLARATION_BY_NAME[name] ++ except KeyError as exc: ++ raise KeyError( ++ f"MCP tool {name!r} has no declaration; add mcp.tool.{name} in polylogue/mcp/declarations/registry.py " ++ f"and run {_REPAIR_COMMAND}" ++ ) from exc ++ ++ ++def declared_tool_names(role: MCPRole = "admin") -> frozenset[str]: ++ return frozenset( ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if mcp_role_allows(role, declaration.minimum_role) ++ ) ++ ++ ++TARGET_DEFAULT_READ_ALGEBRA: Final[tuple[MCPTransactionDeclaration, ...]] = ( ++ MCPTransactionDeclaration( ++ name="query", ++ verb=MCPVerb.QUERY, ++ minimum_role="read", ++ object_kinds=("query", "result-set"), ++ result_semantics=( ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ MCPResultSemantics.TOP_K, ++ MCPResultSemantics.SAMPLE, ++ MCPResultSemantics.AGGREGATE, ++ ), ++ purpose="Execute a declared DSL or typed plan with explicit result semantics and continuation.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++ MCPTransactionDeclaration( ++ name="read", ++ verb=MCPVerb.READ, ++ minimum_role="read", ++ object_kinds=("object-ref", "evidence-ref"), ++ result_semantics=( ++ MCPResultSemantics.SINGLE_OBJECT, ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ MCPResultSemantics.BOUNDED_CONTEXT, ++ ), ++ purpose="Read any stable archive ref through a declared projection/view.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++ MCPTransactionDeclaration( ++ name="get", ++ verb=MCPVerb.GET, ++ minimum_role="read", ++ object_kinds=("object-ref",), ++ result_semantics=(MCPResultSemantics.SINGLE_OBJECT,), ++ purpose="Resolve one exact object identity when a generic read would add ambiguity.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++ MCPTransactionDeclaration( ++ name="graph", ++ verb=MCPVerb.GET, ++ minimum_role="read", ++ object_kinds=("session", "topology-edge", "thread"), ++ result_semantics=(MCPResultSemantics.RECURSIVE_GRAPH,), ++ purpose="Traverse lineage/topology with an explicit recursive continuation contract.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++ MCPTransactionDeclaration( ++ name="explain", ++ verb=MCPVerb.EXPLAIN, ++ minimum_role="read", ++ object_kinds=("query", "object-ref", "capability"), ++ result_semantics=(MCPResultSemantics.SINGLE_OBJECT,), ++ purpose="Discover grammar, fields, values, plans, authority, and recovery routes.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++ MCPTransactionDeclaration( ++ name="context", ++ verb=MCPVerb.CONTEXT, ++ minimum_role="read", ++ object_kinds=("context-snapshot", "context-delivery"), ++ result_semantics=(MCPResultSemantics.BOUNDED_CONTEXT,), ++ purpose="Compile and retrieve policy-gated bounded context plus receipts.", ++ migration_owner="polylogue-t46.8.3", ++ ), ++ MCPTransactionDeclaration( ++ name="status", ++ verb=MCPVerb.STATUS, ++ minimum_role="read", ++ object_kinds=("status", "receipt"), ++ result_semantics=(MCPResultSemantics.SINGLE_OBJECT, MCPResultSemantics.AGGREGATE), ++ purpose="Read archive, source, embedding, coordination, and operation status.", ++ migration_owner="polylogue-t46.8.2", ++ ), ++) ++ ++PRIVILEGED_ALGEBRA: Final[tuple[MCPTransactionDeclaration, ...]] = ( ++ MCPTransactionDeclaration( ++ name="write", ++ verb=MCPVerb.WRITE, ++ minimum_role="write", ++ object_kinds=("object-ref", "assertion"), ++ result_semantics=(MCPResultSemantics.MUTATION,), ++ purpose="Apply a declaration-owned mutation after shared authorization.", ++ migration_owner="polylogue-t46.8.3", ++ ), ++ MCPTransactionDeclaration( ++ name="judge", ++ verb=MCPVerb.JUDGE, ++ minimum_role="review", ++ object_kinds=("assertion-candidate", "judgment"), ++ result_semantics=(MCPResultSemantics.MUTATION,), ++ purpose="Accept, reject, defer, or supersede candidates without collapsing candidate state.", ++ migration_owner="polylogue-t46.8.3", ++ ), ++ MCPTransactionDeclaration( ++ name="run", ++ verb=MCPVerb.RUN, ++ minimum_role="write", ++ object_kinds=("saved-query", "recipe", "result-set"), ++ result_semantics=(MCPResultSemantics.EXHAUSTIVE_PAGE, MCPResultSemantics.MUTATION), ++ purpose="Execute a saved query or governed recipe ref.", ++ migration_owner="polylogue-t46.8.3", ++ ), ++ MCPTransactionDeclaration( ++ name="maintenance", ++ verb=MCPVerb.MAINTENANCE, ++ minimum_role="admin", ++ object_kinds=("maintenance-plan", "maintenance-operation"), ++ result_semantics=(MCPResultSemantics.MAINTENANCE,), ++ purpose="Preview, authorize, execute, inspect, and reconcile maintenance operations.", ++ migration_owner="polylogue-t46.8.3", ++ ), ++) ++ ++TARGET_RESOURCES: Final[tuple[MCPResourceDeclaration, ...]] = tuple( ++ MCPResourceDeclaration( ++ uri_template=f"polylogue://{kind}/{{id}}", ++ object_kinds=(kind,), ++ minimum_role="read", ++ authority="read-only object projection; resources never acquire instruction or mutation authority", ++ migration_owner="polylogue-t46.8.2" if kind != "recall-pack" else "polylogue-t46.8.3", ++ ) ++ for kind in ("session", "message", "block", "action", "file", "query", "result-set", "recall-pack") ++) ++ ++TARGET_PROMPTS: Final[tuple[MCPPromptDeclaration, ...]] = ( ++ MCPPromptDeclaration("resume_context", "resume", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("postmortem_last", "postmortem", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("decisions_about", "decision-recovery", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("unacknowledged_failures", "failure-recovery", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("sessions_touching_file", "file-touch", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("cost_of", "cost-analysis", "read", "none", "polylogue-t46.8.2"), ++ MCPPromptDeclaration("agent_coordination_brief", "coordination", "read", "none", "polylogue-t46.8.3"), ++) ++ ++if len(TARGET_DEFAULT_READ_ALGEBRA) > 15: ++ raise RuntimeError("target default MCP read algebra exceeds the 15-transaction discovery budget") ++ ++ ++__all__ = [ ++ "MCP_KERNEL_REGISTRY", ++ "MCP_TOOL_DECLARATIONS", ++ "MCP_TOOL_DECLARATION_BY_NAME", ++ "PRIVILEGED_ALGEBRA", ++ "TARGET_DEFAULT_READ_ALGEBRA", ++ "TARGET_PROMPTS", ++ "TARGET_RESOURCES", ++ "declaration_for_tool", ++ "declared_tool_names", ++] +diff --git a/polylogue/mcp/server.py b/polylogue/mcp/server.py +index f1eeb183a99c01e8ee765d7f35b7aaa11fdf9119..18c7932b0ae161c655f4388df79875687f0f45d5 100644 +--- a/polylogue/mcp/server.py ++++ b/polylogue/mcp/server.py +@@ -4,6 +4,7 @@ from __future__ import annotations + + from typing import TYPE_CHECKING + ++from polylogue.mcp.declarations.adapter import DeclaredToolRegistrar + from polylogue.mcp.server_prompts import register_prompts + from polylogue.mcp.server_resources import register_resources + from polylogue.mcp.server_support import ( +@@ -62,7 +63,9 @@ def build_server(*, role: MCPRole = "read") -> FastMCP: + response_context=_response_context, + role=role, + ) +- register_tools(mcp, hooks) ++ declared_mcp = DeclaredToolRegistrar(mcp, role=role) ++ register_tools(declared_mcp, hooks) ++ declared_mcp.finalize() + register_resources(mcp, hooks) + register_prompts(mcp, hooks) + return mcp +diff --git a/polylogue/mcp/server_context_tools.py b/polylogue/mcp/server_context_tools.py +index e0a49bb5e3fb15a04e01aab474a95356c4534005..79f8bf7a8e44c1f0a7f5ee98d116f534cd35e2b9 100644 +--- a/polylogue/mcp/server_context_tools.py ++++ b/polylogue/mcp/server_context_tools.py +@@ -12,8 +12,7 @@ from __future__ import annotations + from typing import TYPE_CHECKING + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + _DEFAULT_MAX_SESSIONS = 5 +@@ -30,7 +29,7 @@ def _detail_includes_messages(detail_level: str) -> bool: + return _DETAIL_INCLUDES_MESSAGES.get(detail_level, True) + + +-def register_context_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_context_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + @mcp.tool() + async def get_context_delivery(snapshot_ref: str, recipient_ref: str) -> str: + """Read the exact persisted context image for its recorded recipient. +diff --git a/polylogue/mcp/server_insight_tools.py b/polylogue/mcp/server_insight_tools.py +index 92adf394a7f15b2f9c4341383dc3a188687fcaa0..b5ee2b82a38164273ad27d1ddc4518269aaa3a74 100644 +--- a/polylogue/mcp/server_insight_tools.py ++++ b/polylogue/mcp/server_insight_tools.py +@@ -25,8 +25,7 @@ from polylogue.mcp.payloads import MCPRootPayload + from polylogue.mcp.query_contracts import MCPSessionQueryRequest + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -46,7 +45,7 @@ def _object_int(value: object) -> int: + + + def _register_list_tool( +- mcp: FastMCP, ++ mcp: ToolRegistrar, + hooks: ServerCallbacks, + pt: InsightType, + ) -> None: +@@ -99,7 +98,7 @@ def _register_list_tool( + mcp.tool()(wrapper) + + +-def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_insight_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + """Register all insight-type list tools plus special tools.""" + + # Register generic list tools from the registry +diff --git a/polylogue/mcp/server_maintenance_tools.py b/polylogue/mcp/server_maintenance_tools.py +index 36674caa31b83e9409ce37b1daac6c66a983fd8e..bbdd21c4ea0e602e250a61c96180abd4704b93d6 100644 +--- a/polylogue/mcp/server_maintenance_tools.py ++++ b/polylogue/mcp/server_maintenance_tools.py +@@ -11,8 +11,7 @@ from polylogue.maintenance.scope import MaintenanceScopeFilter + from polylogue.mcp.payloads import MCPMutationStatusPayload, MCPRootPayload + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -53,7 +52,7 @@ def _build_mcp_scope_filter( + ) + + +-def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_maintenance_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + @mcp.tool() + async def maintenance_preview( + targets: list[str] | None = None, +diff --git a/polylogue/mcp/server_mutation_tools.py b/polylogue/mcp/server_mutation_tools.py +index ac8a2f903548eb20ae722233424654cd0332571c..748cc0d61808a92e60a6f53da5bad13c677c42d9 100644 +--- a/polylogue/mcp/server_mutation_tools.py ++++ b/polylogue/mcp/server_mutation_tools.py +@@ -24,8 +24,7 @@ from polylogue.mcp.query_contracts import MCPToolLimit, MCPToolOffset + from polylogue.mcp.server_personal_state_tools import register_personal_state_tools + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -39,7 +38,7 @@ def _none_if_empty(value: str | None) -> str | None: + return value if value else None + + +-def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_mutation_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + @mcp.tool() + async def capture_assertion_candidate( + body_text: str, +@@ -411,7 +410,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + return await hooks.async_safe_call("delete_session", run, session_id=session_id) + + +-def register_assertion_review_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_assertion_review_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + """Register promotion tools only for authenticated review capability.""" + + @mcp.tool() +diff --git a/polylogue/mcp/server_personal_state_tools.py b/polylogue/mcp/server_personal_state_tools.py +index 9888418833447224559a28efd0ebe9727d15220c..58c1636fc44a6a9cba85efdf027226f8a5d7220d 100644 +--- a/polylogue/mcp/server_personal_state_tools.py ++++ b/polylogue/mcp/server_personal_state_tools.py +@@ -30,8 +30,7 @@ from polylogue.mcp.payloads import ( + from polylogue.mcp.query_contracts import MCPCharacterLimit, MCPToolLimit, MCPToolOffset + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -146,7 +145,7 @@ def _annotation_payload(row: dict[str, str]) -> MCPUserAnnotationPayload: + ) + + +-def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_personal_state_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + """Register annotations, views, recall packs, workspaces, metadata and corrections.""" + + @mcp.tool() +diff --git a/polylogue/mcp/server_support.py b/polylogue/mcp/server_support.py +index 379235fe57de7d1c3a407eb686591ad49e6f2c20..4879bc60c8167fc1ba3294a68017a2db5b525288 100644 +--- a/polylogue/mcp/server_support.py ++++ b/polylogue/mcp/server_support.py +@@ -15,7 +15,7 @@ from contextlib import AbstractContextManager, contextmanager + from contextvars import ContextVar + from dataclasses import dataclass + from datetime import UTC, datetime +-from typing import TYPE_CHECKING, Literal, Protocol, TypeVar, overload ++from typing import TYPE_CHECKING, Protocol, TypeVar, overload + + from pydantic import BaseModel + +@@ -33,6 +33,7 @@ from polylogue.core.errors import ( + SchemaVersionMismatchError, + ) + from polylogue.logging import get_logger ++from polylogue.mcp.declarations.models import MCPRole, mcp_role_allows + from polylogue.mcp.payloads import MCPErrorPayload, MCPFencedCodeBlock + from polylogue.services import RuntimeServices, build_runtime_services + from polylogue.surfaces.payloads import serialize_surface_payload +@@ -44,7 +45,6 @@ if TYPE_CHECKING: + logger = get_logger(__name__) + _runtime_services: RuntimeServices | None = None + TResult = TypeVar("TResult") +-MCPRole = Literal["read", "write", "review", "admin"] + MCP_RESPONSE_BUDGET_BYTES = 25_000 + _QUERY_ERROR_VALID_VALUES: dict[str, tuple[str, ...]] = { + "sort": ("date", "tokens", "messages", "words", "longest", "random"), +@@ -124,8 +124,8 @@ class ServerCallbacks: + + def role_allows(role: MCPRole, required: MCPRole) -> bool: + """Return whether an MCP server role allows the required capability.""" +- order: dict[MCPRole, int] = {"read": 0, "write": 1, "review": 2, "admin": 3} +- return order[role] >= order[required] ++ ++ return mcp_role_allows(role, required) + + + def _extract_fenced_code(text: str, language: str = "") -> list[MCPFencedCodeBlock]: +diff --git a/polylogue/mcp/server_tools.py b/polylogue/mcp/server_tools.py +index 5c08d927da843bda6c12ca39da18cf748f8440dc..6362a118a34816da3568a94e0c701c519bdee96b 100644 +--- a/polylogue/mcp/server_tools.py ++++ b/polylogue/mcp/server_tools.py +@@ -23,6 +23,7 @@ from polylogue.mcp.archive_support import ( + blackboard_note_payload, + mcp_archive_root, + ) ++from polylogue.mcp.declarations.adapter import register_declared_handler + from polylogue.mcp.payloads import ( + MCPArchiveSearchHitPayload, + MCPArchiveSearchPayload, +@@ -61,9 +62,8 @@ from polylogue.mcp.server_support import role_allows + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- + from polylogue.config import Config ++ from polylogue.mcp.declarations.adapter import ToolRegistrar + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -133,29 +133,6 @@ def _stats_embedding_overrides(config: Config) -> dict[str, object]: + } + + +-@dataclass(frozen=True) +-class _MCPReadToolSpec: +- name: str +- description: str +- linked_read_view: str | None = None +- output_model: str | None = None +- +- +-_MCP_READ_TOOL_SPECS: dict[str, _MCPReadToolSpec] = { +- "list_read_view_profiles": _MCPReadToolSpec( +- name="list_read_view_profiles", +- description="List executable read-view profile metadata for agents.", +- output_model="MCPRootPayload", +- ), +-} +- +- +-def _register_mcp_read_tool(mcp: FastMCP, handler: Any, spec: _MCPReadToolSpec) -> None: +- handler.__name__ = spec.name +- handler.__doc__ = spec.description +- mcp.tool()(handler) +- +- + def _split_archive_csv(value: str | None) -> tuple[str, ...]: + if value is None: + return () +@@ -206,7 +183,7 @@ def _session_summary_payload( + return MCPRootPayload(root=cast(dict[str, object], base)) + + +-def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_query_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + async def _search(**kwargs: object) -> str: + if "query" not in kwargs: + raise TypeError("search() missing required keyword argument: 'query'") +@@ -795,7 +772,7 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + return await hooks.async_safe_call("provider_usage", run) + + +-def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_read_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + # Exact source freshness is a read-only diagnostic. Configuration chooses + # the archive; callers can supply only the exact source path and cannot + # turn this into an arbitrary local-file inspection route. +@@ -1222,7 +1199,7 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_read_view_profiles", run) + +- _register_mcp_read_tool(mcp, list_read_view_profiles, _MCP_READ_TOOL_SPECS["list_read_view_profiles"]) ++ register_declared_handler(mcp, list_read_view_profiles, name="list_read_view_profiles") + + @mcp.tool() + async def explain_query_expression(expression: str) -> str: +@@ -1309,7 +1286,7 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + return await hooks.async_safe_call("action_affordances", run) + + +-def register_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_tools(mcp: ToolRegistrar, hooks: ServerCallbacks) -> None: + register_query_tools(mcp, hooks) + register_read_tools(mcp, hooks) + register_context_tools(mcp, hooks) +diff --git a/tests/infra/mcp.py b/tests/infra/mcp.py +index 258ab5e84f088709969191f6152a1c96033cded5..cfbedb10e3498f2b8f3a9893fd2ddd0e824ce01c 100644 +--- a/tests/infra/mcp.py ++++ b/tests/infra/mcp.py +@@ -10,114 +10,122 @@ from unittest.mock import AsyncMock, MagicMock + + from polylogue.archive.models import Session + from polylogue.core.enums import Provider ++from polylogue.mcp.declarations.registry import declared_tool_names + from tests.infra.builders import make_conv, make_msg + +-EXPECTED_TOOL_NAMES = { +- "search", +- "query_units", +- "resolve_ref", +- "explain_import", +- "list_sessions", +- "compile_context", +- "get_context_delivery", +- "build_context_image", +- "blackboard_list", +- "blackboard_post", +- "capture_assertion_candidate", +- "import_annotation_batch", +- "join_typed_annotations", +- "list_assertion_claims", +- "list_assertion_candidates", +- "list_assertion_candidate_reviews", +- "judge_assertion_candidate", +- "judge_assertion_candidates", +- "neighbor_candidates", +- "stats", +- "embedding_status", +- "embedding_preflight", +- "facets", +- "agent_coordination", +- "add_tag", +- "remove_tag", +- "bulk_tag_sessions", +- "list_tags", +- "list_marks", +- "add_mark", +- "remove_mark", +- "list_annotations", +- "save_annotation", +- "delete_annotation", +- "list_saved_views", +- "save_saved_view", +- "delete_saved_view", +- "get_metadata", +- "set_metadata", +- "delete_metadata", +- "delete_session", +- "get_session_summary", +- "get_session_tree", +- "get_session_topology", +- "get_logical_session", +- "get_stats_by", +- "list_read_view_profiles", +- "explain_query_expression", +- "query_completions", +- "readiness_check", +- "named_source_freshness", +- "rebuild_index", +- "update_index", +- "get_postmortem_bundle", +- "get_pathologies", +- "rebuild_session_insights", +- "maintenance_preview", +- "maintenance_execute", +- "maintenance_status", +- "maintenance_list", +- "session_profile", +- "session_latency_profile", +- "tool_call_latency_distribution", +- "find_stuck_sessions", +- "workflow_shape_distribution", +- "find_abandoned_sessions", +- "get_resume_brief", +- "find_resume_candidates", +- "aggregate_sessions", +- "compare_sessions", +- "find_similar_sessions", +- "correlate_session", +- "correlate_sessions", +- "session_tool_timing", +- "cost_outlook", +- "archive_debt", +- "tool_usage", +- "session_costs", +- "cost_rollups", +- "usage_timeline", +- "session_profiles", +- "session_work_events", +- "session_phases", +- "session_tag_rollups", +- "threads", +- "archive_coverage", +- "action_affordances", +- "archive_get_session", +- "archive_list_sessions", +- "archive_search_sessions", +- "clear_corrections", +- "compose_context_preamble", +- "delete_recall_pack", +- "delete_workspace", +- "get_messages", +- "insight_rigor_audit", +- "list_corrections", +- "list_recall_packs", +- "list_workspaces", +- "provider_usage", +- "raw_artifacts", +- "record_correction", +- "save_recall_pack", +- "save_workspace", +-} ++MCP_TOOL_NAME_BASELINE = frozenset( ++ { ++ "search", ++ "query_units", ++ "resolve_ref", ++ "explain_import", ++ "list_sessions", ++ "compile_context", ++ "get_context_delivery", ++ "build_context_image", ++ "blackboard_list", ++ "blackboard_post", ++ "capture_assertion_candidate", ++ "import_annotation_batch", ++ "join_typed_annotations", ++ "list_assertion_claims", ++ "list_assertion_candidates", ++ "list_assertion_candidate_reviews", ++ "judge_assertion_candidate", ++ "judge_assertion_candidates", ++ "neighbor_candidates", ++ "stats", ++ "embedding_status", ++ "embedding_preflight", ++ "facets", ++ "agent_coordination", ++ "add_tag", ++ "remove_tag", ++ "bulk_tag_sessions", ++ "list_tags", ++ "list_marks", ++ "add_mark", ++ "remove_mark", ++ "list_annotations", ++ "save_annotation", ++ "delete_annotation", ++ "list_saved_views", ++ "save_saved_view", ++ "delete_saved_view", ++ "get_metadata", ++ "set_metadata", ++ "delete_metadata", ++ "delete_session", ++ "get_session_summary", ++ "get_session_tree", ++ "get_session_topology", ++ "get_logical_session", ++ "get_stats_by", ++ "list_read_view_profiles", ++ "explain_query_expression", ++ "query_completions", ++ "readiness_check", ++ "named_source_freshness", ++ "rebuild_index", ++ "update_index", ++ "get_postmortem_bundle", ++ "get_pathologies", ++ "rebuild_session_insights", ++ "maintenance_preview", ++ "maintenance_execute", ++ "maintenance_status", ++ "maintenance_list", ++ "session_profile", ++ "session_latency_profile", ++ "tool_call_latency_distribution", ++ "find_stuck_sessions", ++ "workflow_shape_distribution", ++ "find_abandoned_sessions", ++ "get_resume_brief", ++ "find_resume_candidates", ++ "aggregate_sessions", ++ "compare_sessions", ++ "find_similar_sessions", ++ "correlate_session", ++ "correlate_sessions", ++ "session_tool_timing", ++ "cost_outlook", ++ "archive_debt", ++ "tool_usage", ++ "session_costs", ++ "cost_rollups", ++ "usage_timeline", ++ "session_profiles", ++ "session_work_events", ++ "session_phases", ++ "session_tag_rollups", ++ "threads", ++ "archive_coverage", ++ "action_affordances", ++ "archive_get_session", ++ "archive_list_sessions", ++ "archive_search_sessions", ++ "clear_corrections", ++ "compose_context_preamble", ++ "delete_recall_pack", ++ "delete_workspace", ++ "get_messages", ++ "insight_rigor_audit", ++ "list_corrections", ++ "list_recall_packs", ++ "list_workspaces", ++ "provider_usage", ++ "raw_artifacts", ++ "record_correction", ++ "save_recall_pack", ++ "save_workspace", ++ } ++) ++ ++# Production discovery expectations are declaration-derived. The frozen baseline ++# remains an independent compatibility oracle so deleting a handler and its ++# declaration in the same change cannot make the test surface self-authorize. ++EXPECTED_TOOL_NAMES = set(declared_tool_names("admin")) + + EXPECTED_RESOURCE_URIS = { + "polylogue://stats", +diff --git a/tests/unit/declarations/test_registry.py b/tests/unit/declarations/test_registry.py +new file mode 100644 +index 0000000000000000000000000000000000000000..7a874a565927aea053f15e9ccb07c93f9a5ddfe1 +--- /dev/null ++++ b/tests/unit/declarations/test_registry.py +@@ -0,0 +1,171 @@ ++"""Contracts for the storage-free shared declaration kernel.""" ++ ++from __future__ import annotations ++ ++import ast ++from pathlib import Path ++ ++import pytest ++ ++from polylogue.declarations import ( ++ CompatibilityKey, ++ CompletenessEdge, ++ DeclarationConflictError, ++ DeclarationRegistry, ++ DeclarationSpec, ++ ExampleSpec, ++ HandlerBinding, ++ OutputSpec, ++ normalized_derivation_bytes, ++ validate_declaration, ++) ++ ++ ++def _declaration( ++ name: str, ++ *, ++ family: str = "family", ++ compatibility: CompatibilityKey | None = None, ++) -> DeclarationSpec: ++ return DeclarationSpec( ++ declaration_id=f"test.{name}", ++ family_id=family, ++ public_name=name, ++ owner_path=f"tests/{name}.py", ++ compatibility=compatibility ++ or CompatibilityKey( ++ identity="stable-id", ++ lifecycle="retained", ++ authority="read", ++ access_result_shape="single-object", ++ durability="derived", ++ ), ++ producer="test.operation", ++ role_gate="test.role:read", ++ schema_ref="test.schema", ++ discovery_text=f"Discover {name}.", ++ repair_command="devtools render test-contract", ++ handlers=(HandlerBinding("test", f"tests/{name}.py", name, f"test:{name}"),), ++ outputs=(OutputSpec("runtime", "single-object", "test.schema", f"test://{name}"),), ++ examples=(ExampleSpec("minimal", "Minimal example."),), ++ completeness_edges=(CompletenessEdge(f"test.{name}", "test.consumer", "coverage", "tests/test_consumer.py"),), ++ ) ++ ++ ++def test_registry_derivation_is_independent_of_registration_order() -> None: ++ """Production dependency: DeclarationRegistry stable ordering and derivation normalization. ++ ++ Anti-vacuity mutation: returning insertion order from ``declarations()`` makes ++ the normalized byte comparison fail. ++ """ ++ ++ forward = DeclarationRegistry() ++ reverse = DeclarationRegistry() ++ declarations = (_declaration("alpha", family="alpha"), _declaration("beta", family="beta")) ++ for declaration in declarations: ++ forward.register(declaration) ++ for declaration in reversed(declarations): ++ reverse.register(declaration) ++ ++ assert normalized_derivation_bytes(forward) == normalized_derivation_bytes(reverse) ++ assert tuple(item.declaration_id for item in forward.declarations()) == ("test.alpha", "test.beta") ++ ++ ++def test_family_rejects_every_incompatible_semantic_dimension() -> None: ++ """Production dependency: compatibility-key enforcement in DeclarationRegistry. ++ ++ Anti-vacuity mutation: dropping any field from ``CompatibilityKey.differences`` ++ removes that field from the asserted diagnostic. ++ """ ++ ++ registry = DeclarationRegistry() ++ registry.register(_declaration("first")) ++ conflicting = CompatibilityKey( ++ identity="other-id", ++ lifecycle="deprecated", ++ authority="write", ++ access_result_shape="page", ++ durability="durable", ++ ) ++ ++ with pytest.raises(DeclarationConflictError) as caught: ++ registry.register(_declaration("second", compatibility=conflicting)) ++ ++ assert [field for field, _, _ in caught.value.differences] == [ ++ "identity", ++ "lifecycle", ++ "authority", ++ "access_result_shape", ++ "durability", ++ ] ++ assert caught.value.owner_path == "tests/second.py" ++ ++ ++def test_validation_reports_source_and_exact_repair_command() -> None: ++ """Production dependency: declaration completeness diagnostics. ++ ++ Anti-vacuity mutation: changing the diagnostic owner or repair command makes ++ the source-locatable assertions fail. ++ """ ++ ++ complete = _declaration("complete") ++ incomplete = DeclarationSpec( ++ declaration_id=complete.declaration_id, ++ family_id=complete.family_id, ++ public_name=complete.public_name, ++ owner_path=complete.owner_path, ++ compatibility=complete.compatibility, ++ producer="", ++ role_gate="", ++ schema_ref="", ++ discovery_text="", ++ repair_command=complete.repair_command, ++ handlers=(), ++ outputs=(), ++ examples=(), ++ completeness_edges=(), ++ ) ++ ++ diagnostics = validate_declaration(incomplete) ++ ++ assert {item.code for item in diagnostics} == { ++ "missing_consumer_edge", ++ "missing_discovery", ++ "missing_example", ++ "missing_handler", ++ "missing_output", ++ "missing_producer", ++ "missing_role_gate", ++ "missing_schema", ++ } ++ assert {item.owner_path for item in diagnostics} == {"tests/complete.py"} ++ assert {item.repair_command for item in diagnostics} == {"devtools render test-contract"} ++ ++ ++def test_shared_kernel_has_no_domain_or_runtime_imports() -> None: ++ """Production dependency: the shared declaration package import boundary. ++ ++ Anti-vacuity mutation: importing MCP, storage, operations, or configuration ++ into the kernel adds a forbidden import collected by this AST check. ++ """ ++ ++ forbidden = ( ++ "polylogue.api", ++ "polylogue.archive", ++ "polylogue.config", ++ "polylogue.insights", ++ "polylogue.mcp", ++ "polylogue.operations", ++ "polylogue.storage", ++ ) ++ imported: list[tuple[Path, str]] = [] ++ for path in sorted(Path("polylogue/declarations").glob("*.py")): ++ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) ++ for node in ast.walk(tree): ++ if isinstance(node, ast.Import): ++ imported.extend((path, alias.name) for alias in node.names) ++ elif isinstance(node, ast.ImportFrom) and node.module is not None: ++ imported.append((path, node.module)) ++ ++ violations = [(path, module) for path, module in imported if module.startswith(forbidden)] ++ assert violations == [] +diff --git a/tests/unit/mcp/test_per_tool_contracts.py b/tests/unit/mcp/test_per_tool_contracts.py +index 7323932193a4707c88d96289c2beb36a8c924e13..50bf0cb34068bdc7300c00dd0e990714086449f1 100644 +--- a/tests/unit/mcp/test_per_tool_contracts.py ++++ b/tests/unit/mcp/test_per_tool_contracts.py +@@ -35,6 +35,7 @@ from unittest.mock import AsyncMock, MagicMock, patch + import jsonschema + import pytest + ++from polylogue.mcp.declarations.adapter import MCPRegistrationError + from polylogue.operations import build_runtime_operation_catalog + from polylogue.surfaces.payloads import ( + BulkTagMutationResult, +@@ -371,11 +372,14 @@ class TestMutationToolDiscovery: + } + assert personal_names + +- with patch("polylogue.mcp.server_mutation_tools.register_personal_state_tools") as registrar: +- suppressed = cast(MCPServerUnderTest, build_server(role="admin")) ++ with ( ++ patch("polylogue.mcp.server_mutation_tools.register_personal_state_tools") as registrar, ++ pytest.raises(MCPRegistrationError) as exc_info, ++ ): ++ build_server(role="admin") + + registrar.assert_called_once() +- assert personal_names.isdisjoint(suppressed._tool_manager._tools), sorted(personal_names) ++ assert all(name in str(exc_info.value) for name in personal_names), sorted(personal_names) + + def test_every_mutation_tool_is_in_matrix(self) -> None: + missing = MUTATION_TOOL_NAMES - set(TOOL_MATRIX.keys()) +diff --git a/tests/unit/mcp/test_server_runtime.py b/tests/unit/mcp/test_server_runtime.py +index 8d282341bda47cbdc9b60fd86b659c74e99596c1..17f1cd6c29d9278de1659f5bb8c2668ffc739b94 100644 +--- a/tests/unit/mcp/test_server_runtime.py ++++ b/tests/unit/mcp/test_server_runtime.py +@@ -11,9 +11,11 @@ from polylogue.mcp import server as server_module + def test_build_server_registers_tools_resources_and_prompts() -> None: + fake_mcp = MagicMock() + fake_fast_mcp = MagicMock(return_value=fake_mcp) ++ declared_mcp = MagicMock() + + with ( + patch("mcp.server.fastmcp.FastMCP", fake_fast_mcp), ++ patch("polylogue.mcp.server.DeclaredToolRegistrar", return_value=declared_mcp) as registrar_type, + patch("polylogue.mcp.server.register_tools") as mock_tools, + patch("polylogue.mcp.server.register_resources") as mock_resources, + patch("polylogue.mcp.server.register_prompts") as mock_prompts, +@@ -22,9 +24,12 @@ def test_build_server_registers_tools_resources_and_prompts() -> None: + + assert built is fake_mcp + fake_fast_mcp.assert_called_once() ++ registrar_type.assert_called_once_with(fake_mcp, role="read") + hooks = mock_tools.call_args.args[1] ++ assert mock_tools.call_args.args[0] is declared_mcp + assert callable(hooks.json_payload) + assert callable(hooks.clamp_limit) ++ declared_mcp.finalize.assert_called_once_with() + mock_resources.assert_called_once_with(fake_mcp, hooks) + mock_prompts.assert_called_once_with(fake_mcp, hooks) + +diff --git a/tests/unit/mcp/test_tool_contracts.py b/tests/unit/mcp/test_tool_contracts.py +index f3f07e96f2c3a9c6ffb82b463e20ad631a1f7237..566e518e93703951332dd7d149e5689880ca86f4 100644 +--- a/tests/unit/mcp/test_tool_contracts.py ++++ b/tests/unit/mcp/test_tool_contracts.py +@@ -61,9 +61,9 @@ from polylogue.mcp.archive_support import ( + archive_search_payload, + archive_session_list_payload, + ) ++from polylogue.mcp.declarations.registry import MCP_TOOL_DECLARATION_BY_NAME + from polylogue.mcp.payloads import MCPRootPayload + from polylogue.mcp.server_support import MCP_RESPONSE_BUDGET_BYTES, _json_payload +-from polylogue.mcp.server_tools import _MCP_READ_TOOL_SPECS + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + from polylogue.surfaces.payloads import TagMutationResult +@@ -1371,11 +1371,12 @@ class TestGetSessionSummaryTool: + + + class TestReadViewProfilesTool: +- def test_read_tools_use_registered_specs(self, mcp_server: MCPServerUnderTest) -> None: +- for name, spec in _MCP_READ_TOOL_SPECS.items(): +- tool = mcp_server._tool_manager._tools[name] +- assert tool.fn.__name__ == spec.name +- assert tool.fn.__doc__ == spec.description ++ def test_read_tool_uses_declared_registration_metadata(self, mcp_server: MCPServerUnderTest) -> None: ++ declaration = MCP_TOOL_DECLARATION_BY_NAME["list_read_view_profiles"] ++ tool = mcp_server._tool_manager._tools[declaration.name] ++ ++ assert tool.fn.__name__ == declaration.name ++ assert tool.fn.__doc__ == declaration.description + + def test_list_read_view_profiles_uses_facade_contract(self, mcp_server: MCPServerUnderTest) -> None: + profiles = read_view_profile_payloads() +diff --git a/tests/unit/mcp/test_tool_declarations.py b/tests/unit/mcp/test_tool_declarations.py +new file mode 100644 +index 0000000000000000000000000000000000000000..ce6e7a73f6320c7d2a9a05e5254d2093236adad3 +--- /dev/null ++++ b/tests/unit/mcp/test_tool_declarations.py +@@ -0,0 +1,333 @@ ++"""Independent equivalence checks for the executable MCP declaration algebra.""" ++ ++from __future__ import annotations ++ ++import ast ++import asyncio ++import json ++from collections.abc import Callable ++from pathlib import Path ++from typing import Any, cast ++ ++import pytest ++ ++from devtools.render_mcp_equivalence import render_output ++from polylogue.mcp.declarations.adapter import ( ++ DeclaredToolRegistrar, ++ MCPRegistrationError, ++ register_declared_handler, ++) ++from polylogue.mcp.declarations.models import MCPResultSemantics, MCPVerb, mcp_role_allows ++from polylogue.mcp.declarations.registry import ( ++ MCP_TOOL_DECLARATION_BY_NAME, ++ MCP_TOOL_DECLARATIONS, ++ PRIVILEGED_ALGEBRA, ++ TARGET_DEFAULT_READ_ALGEBRA, ++ TARGET_PROMPTS, ++ TARGET_RESOURCES, ++ declared_tool_names, ++) ++ ++ ++class _RecordingToolRegistrar: ++ def __init__(self) -> None: ++ self.handlers: list[Callable[..., Any]] = [] ++ self.kwargs: list[dict[str, object]] = [] ++ ++ def tool( ++ self, ++ name: str | None = None, ++ *, ++ description: str | None = None, ++ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ++ self.kwargs.append({"name": name, "description": description}) ++ ++ def register(handler: Callable[..., Any]) -> Callable[..., Any]: ++ self.handlers.append(handler) ++ return handler ++ ++ return register ++ ++ ++def _assignment_value(path: str, name: str) -> ast.expr: ++ tree = ast.parse(Path(path).read_text(encoding="utf-8"), filename=path) ++ for node in tree.body: ++ if isinstance(node, ast.Assign) and any( ++ isinstance(target, ast.Name) and target.id == name for target in node.targets ++ ): ++ return node.value ++ if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id == name: ++ assert node.value is not None ++ return node.value ++ raise AssertionError(f"{path} has no assignment for {name}") ++ ++ ++def _frozen_name_baseline() -> frozenset[str]: ++ value = _assignment_value("tests/infra/mcp.py", "MCP_TOOL_NAME_BASELINE") ++ assert isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "frozenset" ++ assert len(value.args) == 1 ++ names = ast.literal_eval(value.args[0]) ++ assert isinstance(names, set) ++ return frozenset(cast(set[str], names)) ++ ++ ++def _contract_value(node: ast.expr) -> str | tuple[str, frozenset[str]]: ++ if isinstance(node, ast.Constant) and isinstance(node.value, str): ++ return node.value ++ assert isinstance(node, ast.Tuple) and len(node.elts) == 2 ++ kind = ast.literal_eval(node.elts[0]) ++ field_call = node.elts[1] ++ assert isinstance(field_call, ast.Call) and isinstance(field_call.func, ast.Name) ++ assert field_call.func.id == "frozenset" and len(field_call.args) == 1 ++ fields = ast.literal_eval(field_call.args[0]) ++ assert isinstance(kind, str) and isinstance(fields, set) ++ return kind, frozenset(cast(set[str], fields)) ++ ++ ++def _frozen_output_contract() -> dict[str, str | tuple[str, frozenset[str]]]: ++ value = _assignment_value("tests/unit/mcp/test_envelope_contracts.py", "TOOL_CONTRACT") ++ assert isinstance(value, ast.Dict) ++ result: dict[str, str | tuple[str, frozenset[str]]] = {} ++ for key_node, value_node in zip(value.keys, value.values, strict=True): ++ assert key_node is not None ++ name = ast.literal_eval(key_node) ++ assert isinstance(name, str) ++ result[name] = _contract_value(value_node) ++ return result ++ ++ ++def _handler_for(name: str) -> Callable[[], None]: ++ declaration = MCP_TOOL_DECLARATION_BY_NAME[name] ++ ++ def handler() -> None: ++ return None ++ ++ handler.__name__ = name ++ handler.__qualname__ = name ++ handler.__module__ = declaration.registration.module ++ return handler ++ ++ ++def test_every_frozen_discovery_name_has_exactly_one_declaration() -> None: ++ """Production dependency: MCP_TOOL_DECLARATIONS and role-derived names. ++ ++ Anti-vacuity mutation: deleting the same tool from production registration ++ and the declaration still fails against the independently parsed baseline. ++ """ ++ ++ baseline = _frozen_name_baseline() ++ assert len(baseline) == 104 ++ assert len(MCP_TOOL_DECLARATIONS) == 104 ++ assert len(MCP_TOOL_DECLARATION_BY_NAME) == 104 ++ assert declared_tool_names("admin") == baseline ++ ++ ++def test_output_contracts_match_the_independent_envelope_matrix() -> None: ++ """Production dependency: declaration output kind and envelope fields. ++ ++ Anti-vacuity mutation: changing a declaration and generated map together ++ still fails against ``TOOL_CONTRACT`` in the existing runtime tests. ++ """ ++ ++ baseline = _frozen_output_contract() ++ assert set(baseline) == set(MCP_TOOL_DECLARATION_BY_NAME) ++ for name, expected in baseline.items(): ++ declaration = MCP_TOOL_DECLARATION_BY_NAME[name] ++ expected_kind = expected[0] if isinstance(expected, tuple) else expected ++ expected_fields = expected[1] if isinstance(expected, tuple) else frozenset() ++ assert declaration.output_contract.kind == expected_kind, name ++ assert frozenset(declaration.output_contract.envelope_fields) == expected_fields, name ++ ++ ++def test_role_gates_and_migration_groups_are_exhaustive_and_disjoint() -> None: ++ """Production dependency: role lattice and t46.8.2/t46.8.3 ownership. ++ ++ Anti-vacuity mutation: widening one role or assigning one tool to both ++ migration groups changes the exact sets and counts asserted here. ++ """ ++ ++ role_counts = { ++ role: sum(declaration.minimum_role == role for declaration in MCP_TOOL_DECLARATIONS) ++ for role in ("read", "write", "review", "admin") ++ } ++ assert role_counts == {"read": 66, "write": 29, "review": 2, "admin": 7} ++ assert len(declared_tool_names("read")) == 66 ++ assert len(declared_tool_names("write")) == 95 ++ assert len(declared_tool_names("review")) == 97 ++ assert len(declared_tool_names("admin")) == 104 ++ ++ read_group = { ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.retirement_owner == "polylogue-t46.8.2" ++ } ++ privileged_group = { ++ declaration.name for declaration in MCP_TOOL_DECLARATIONS if declaration.retirement_owner == "polylogue-t46.8.3" ++ } ++ assert read_group == set(declared_tool_names("read")) ++ assert read_group.isdisjoint(privileged_group) ++ assert read_group | privileged_group == set(declared_tool_names("admin")) ++ ++ ++def test_target_algebra_preserves_semantic_dimensions_without_self_authorizing() -> None: ++ """Production dependency: bounded target transaction/resource/prompt records. ++ ++ Anti-vacuity mutation: removing query result semantics, resource authority, ++ or prompt non-authority violates these explicit target constraints. ++ """ ++ ++ assert len(TARGET_DEFAULT_READ_ALGEBRA) == 7 ++ assert {item.name for item in TARGET_DEFAULT_READ_ALGEBRA} == { ++ "query", ++ "read", ++ "get", ++ "graph", ++ "explain", ++ "context", ++ "status", ++ } ++ query = next(item for item in TARGET_DEFAULT_READ_ALGEBRA if item.name == "query") ++ assert set(query.result_semantics) == { ++ MCPResultSemantics.EXHAUSTIVE_PAGE, ++ MCPResultSemantics.TOP_K, ++ MCPResultSemantics.SAMPLE, ++ MCPResultSemantics.AGGREGATE, ++ } ++ assert {item.name for item in PRIVILEGED_ALGEBRA} == {"write", "judge", "run", "maintenance"} ++ assert {item.verb for item in PRIVILEGED_ALGEBRA} == { ++ MCPVerb.WRITE, ++ MCPVerb.JUDGE, ++ MCPVerb.RUN, ++ MCPVerb.MAINTENANCE, ++ } ++ assert len(TARGET_RESOURCES) == 8 ++ assert all("read-only" in item.authority and "never" in item.authority for item in TARGET_RESOURCES) ++ assert TARGET_PROMPTS and all(item.mutation_authority == "none" for item in TARGET_PROMPTS) ++ ++ ++def test_registration_owners_are_source_locatable() -> None: ++ """Production dependency: declaration module/registrar ownership. ++ ++ Anti-vacuity mutation: moving a registrar or mistyping an owner module in ++ the inventory fails without importing the dependency-heavy MCP runtime. ++ """ ++ ++ for declaration in MCP_TOOL_DECLARATIONS: ++ module_path = Path(*declaration.registration.module.split(".")).with_suffix(".py") ++ assert module_path.is_file(), declaration.name ++ tree = ast.parse(module_path.read_text(encoding="utf-8"), filename=str(module_path)) ++ top_level_functions = { ++ node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) ++ } ++ assert declaration.registration.registrar in top_level_functions, declaration.name ++ assert declaration.kernel.handlers[0].owner_path == module_path.as_posix(), declaration.name ++ ++ ++def test_python_parity_and_operation_ownership_are_explicit_for_every_tool() -> None: ++ """Production dependency: operation_owner and PythonParityExpectation. ++ ++ Anti-vacuity mutation: clearing an owner or representing an absence without ++ Bead authority breaks the exact-one branch assertions. ++ """ ++ ++ for declaration in MCP_TOOL_DECLARATIONS: ++ assert declaration.operation_owner ++ parity = declaration.python_parity ++ assert (parity.binding is not None) != (parity.intentional_absence_authority is not None) ++ if parity.binding is None: ++ assert parity.intentional_absence_authority == "polylogue-s1kr" ++ assert parity.reason ++ ++ ++def test_declared_registrar_forwards_original_handlers_and_enforces_role_surface() -> None: ++ """Production dependency: DeclaredToolRegistrar decorator/finalize path. ++ ++ Anti-vacuity mutation: wrapping handlers, skipping role validation, or not ++ checking the final set makes one of the identity/error assertions fail. ++ """ ++ ++ delegate = _RecordingToolRegistrar() ++ registrar = DeclaredToolRegistrar(delegate, role="read") ++ handlers: list[Callable[[], None]] = [] ++ for name in sorted(declared_tool_names("read")): ++ handler = _handler_for(name) ++ handlers.append(handler) ++ registered = registrar.tool()(handler) ++ assert registered is handler ++ assert delegate.handlers[-1] is handler ++ registrar.finalize() ++ assert delegate.handlers == handlers ++ assert registrar.registered_names == declared_tool_names("read") ++ ++ unauthorized = DeclaredToolRegistrar(_RecordingToolRegistrar(), role="read") ++ with pytest.raises(MCPRegistrationError, match="requires 'write'"): ++ unauthorized.tool()(_handler_for("add_tag")) ++ ++ unknown = _handler_for("search") ++ unknown.__name__ = "not_a_live_tool" ++ with pytest.raises(MCPRegistrationError, match="no executable declaration"): ++ unauthorized.tool()(unknown) ++ ++ ++def test_declaration_owned_registration_metadata_replaces_one_off_specs() -> None: ++ """Production dependency: register_declared_handler production seam. ++ ++ Anti-vacuity mutation: restoring the deleted hand-authored read-tool spec or ++ ignoring the declaration description makes the metadata assertion fail. ++ """ ++ ++ delegate = _RecordingToolRegistrar() ++ registrar = DeclaredToolRegistrar(delegate, role="read") ++ handler = _handler_for("list_read_view_profiles") ++ handler.__name__ = "private_handler" ++ handler.__doc__ = None ++ ++ registered = register_declared_handler(registrar, handler, name="list_read_view_profiles") ++ ++ declaration = MCP_TOOL_DECLARATION_BY_NAME["list_read_view_profiles"] ++ assert registered is handler ++ assert handler.__name__ == declaration.name ++ assert handler.__doc__ == declaration.description ++ assert delegate.handlers == [handler] ++ ++ ++def test_live_fastmcp_registration_matches_declarations(mcp_server: object) -> None: ++ """Production dependency: build_server -> DeclaredToolRegistrar -> FastMCP. ++ ++ Anti-vacuity mutation: removing the adapter, wrapping a handler, changing a ++ required parameter, or registering from the wrong module fails this real ++ server-route comparison. ++ """ ++ ++ server = cast(Any, mcp_server) ++ published = {tool.name: tool for tool in asyncio.run(server.list_tools())} ++ assert set(published) == set(declared_tool_names("admin")) ++ for name, declaration in MCP_TOOL_DECLARATION_BY_NAME.items(): ++ tool = published[name] ++ schema = cast(dict[str, object], tool.inputSchema) ++ properties = schema.get("properties", {}) ++ required = schema.get("required", []) ++ assert isinstance(properties, dict), name ++ assert isinstance(required, list), name ++ assert frozenset(cast(list[str], required)) == frozenset(declaration.input_contract.required_arguments), name ++ assert set(declaration.minimal_arguments_dict()) <= set(properties), name ++ live_handler = server._tool_manager._tools[name].fn ++ assert live_handler.__module__ == declaration.registration.module, name ++ ++ ++def test_role_lattice_matches_declared_visibility() -> None: ++ assert mcp_role_allows("admin", "read") ++ assert mcp_role_allows("review", "write") ++ assert not mcp_role_allows("read", "write") ++ ++ ++def test_generated_equivalence_map_is_current_and_complete() -> None: ++ """Production dependency: renderer over MCP_TOOL_DECLARATIONS. ++ ++ Anti-vacuity mutation: changing any descriptor without rerendering changes ++ ``render_output`` and fails the byte-for-byte artifact check. ++ """ ++ ++ path = Path("docs/generated/mcp-equivalence.json") ++ assert path.read_text(encoding="utf-8") == render_output() ++ payload = json.loads(path.read_text(encoding="utf-8")) ++ assert payload["compatibility_surface"]["tool_count"] == 104 ++ assert len(payload["tools"]) == 104 +diff --git a/tests/unit/mcp/test_tool_discovery.py b/tests/unit/mcp/test_tool_discovery.py +index f6e2c16863fce1394c91721d9c08fdcae9104c3a..7d0f280f9e4cfed4b4dc99cade726eb827e775a6 100644 +--- a/tests/unit/mcp/test_tool_discovery.py ++++ b/tests/unit/mcp/test_tool_discovery.py +@@ -47,12 +47,12 @@ _KNOWN_MINIMAL: dict[str, dict[str, object]] = { + "session_costs": {"session_id": _SYNTHETIC_CONV_ID}, + "cost_outlook": {"plan": "claude-pro"}, + "find_resume_candidates": {"repo_path": "/tmp/nonexistent"}, +- "aggregate_sessions": {"session_ids": "test:a,test:b"}, +- "compose_context_preamble": {"context": "{}"}, ++ "aggregate_sessions": {}, ++ "compose_context_preamble": {}, + "compare_sessions": {"session_ids": "test:a,test:b"}, +- "find_similar_sessions": {"query": "test"}, ++ "find_similar_sessions": {"session_id": _SYNTHETIC_CONV_ID}, + "correlate_session": {"session_id": _SYNTHETIC_CONV_ID}, +- "correlate_sessions": {"session_ids": "test:a,test:b"}, ++ "correlate_sessions": {"metric_x": "message_count", "metric_y": "word_count"}, + "session_tool_timing": {"session_id": _SYNTHETIC_CONV_ID}, + "facets": {"limit": 1}, + "neighbor_candidates": {"query": "test"}, +@@ -72,7 +72,7 @@ _KNOWN_MINIMAL: dict[str, dict[str, object]] = { + "cost_rollups": {"limit": 1}, + "tool_usage": {"limit": 1}, + "tool_call_latency_distribution": {"limit": 1}, +- "workflow_shape_distribution": {"limit": 1}, ++ "workflow_shape_distribution": {}, + "find_stuck_sessions": {"limit": 1}, + "find_abandoned_sessions": {"limit": 1}, + "get_stats_by": {"group_by": "origin"}, +@@ -131,13 +131,7 @@ def test_tool_returns_valid_response_envelope(tool_name: str, kwargs: dict[str, + tool = server._tool_manager._tools.get(tool_name) + assert tool is not None, f"tool {tool_name!r} not registered on read-role server" + +- try: +- result = invoke_surface(tool.fn, **kwargs) +- except TypeError: +- # Tools we don't have a known-good kwarg set for may require params +- # we cannot synthesise. The wrapper handles this gracefully. +- # We just verify it didn't crash the server. +- return ++ result = invoke_surface(tool.fn, **kwargs) + + # Must be a string (JSON payload). + assert isinstance(result, str), f"{tool_name}: expected str, got {type(result).__name__}" +@@ -208,9 +202,6 @@ def test_read_tools_have_known_minimal_kwargs() -> None: + comprehensive. + """ + uncovered = sorted(_READ_TOOL_NAMES - set(_KNOWN_MINIMAL)) +- # Tools that genuinely take no arguments and work fine are listed here. +- NO_KWARGS_NEEDED: frozenset[str] = frozenset({"compose_context_preamble"}) +- uncovered = [t for t in uncovered if t not in NO_KWARGS_NEEDED] + if uncovered: + msg = ( + "New MCP read tools without _KNOWN_MINIMAL entry. " diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..ddb911419 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/EVIDENCE.md @@ -0,0 +1,128 @@ +# Beads 03 evidence and contradiction record + +## Authority order used + +1. Supplied current project-state archive and reconstructed source at `f654480cadb7cc4c194704e24dfd483199547b35`. +2. Repository instructions in `CLAUDE.md`. +3. Complete relevant current Beads records and later notes. +4. Current tests and generated witnesses. +5. Git history for intent and previous migration patterns. +6. Test Suite Diet architecture where it constrains proof shape without overriding current source. + +## Snapshot findings + +The snapshot manifest records: + +```text +generated_at: 2026-07-17T043202Z +source: /realm/project/polylogue +branch: master +commit: f654480cadb7cc4c194704e24dfd483199547b35 +dirty: true +``` + +The same archive records `origin/master` and merge base at that commit. Its branch patch, changed-file list, and branch-only commit log are all empty. The reconstructed tracked tree matched the commit before implementation. The only supportable identity is therefore: named clean tracked commit plus an unexplained dirty marker, not a recoverable dirty patch. + +No live operator worktree, daemon, browser, archive, secrets, or deployment was available or claimed. + +## Missing prerequisite + +The mission names the accepted `beads-02` algebra/equivalence kernel as a dependency. Searches covered: + +- `/mnt/data` supplied files; +- extracted project snapshot and all refs; +- source symbols for `DeclarationSpec`/declaration packages; +- available File Library entries. + +Only the `beads-02` mission prompt was available. No accepted result ZIP, patch, files, or merged implementation was present. This falsifies any claim that the patch integrates the exact accepted kernel. The implementation is a current-source-compatible reconstruction and is marked as such in `HANDOFF.md`. + +## Source findings + +### Registration and schema authority before the patch + +- `polylogue/mcp/server_tools.py` mixed direct `@mcp.tool()` decorators, explicit `mcp.tool()` calls, a private `_MCPReadToolSpec` table, and a source-freshness helper registration. +- `polylogue/mcp/server_insight_tools.py` used `InsightListToolSpec` for dynamic list-tool signatures but directly decorated the remaining special tools. +- Role gates were distributed through the registration functions and `server_support.role_allows`. +- FastMCP inspected the original Python callable/signature to produce schemas. Replacing handlers with a generic forwarding wrapper would therefore be a schema regression unless every signature were manually recreated. +- `tests/infra/mcp.py` contained a second hand-maintained 104-name set. +- The committed `mcp-tool-schemas.json` witness contained only 76 rows and was stale relative to the actual 104-tool admin surface. + +### Lower-layer behavior authority + +- Query/list/search handlers already delegate through `MCPSessionQueryRequest`, archive/query support, and ArchiveStore rather than a declaration-local evaluator. +- The recent `fd7b35492` execution-control change gives `query_units` shared cancellation/deadline/admission infrastructure. `polylogue-z9gh.9.1` remains open for the complete sole query transaction across all reads. +- Insight tools increasingly delegate through `polylogue.insights` and `Polylogue`, especially after `881392211`; MCP should remain a leaf adapter. +- `server_support` owns safe-call/error and response-budget behavior. This migration must not add a second error/continuation contract. + +### Current live surface + +Direct FastMCP discovery at the base commit returned: + +- read: 66 tools; +- write: 95 tools; +- review: 97 tools; +- admin: 104 tools. + +The patched surface is ordered-identical for all roles. All 104 declaration descriptions match the live FastMCP descriptions; 33 are legitimately empty because their current handlers expose no description. + +## Beads findings + +### `polylogue-t46.8` + +The epic identifies the 103/104-tool choice architecture as the defect and requires one declaration algebra, semantic result classifications, role-scoped discovery, no adapter-local query engine, and evidence before removal. + +### `polylogue-t46.8.1` + +The prerequisite explicitly names `polylogue/mcp/declarations/models.py` and `registry.py`, an exact one-row-per-tool inventory, semantic result/paging/ref fields, generated expected inventories/contracts, and no legacy deletion in that slice. Its complete target also includes resources, prompts, observed-use/workflow/telemetry/deprecation fields and a 10–15-tool target surface. Those broader accepted-kernel capabilities are not inferable from the missing result package and are not claimed here. + +### `polylogue-t46.8.2` + +The read migration must remain an adapter to the shared query transaction, preserve selection/order/totals/coverage/continuation/refs/errors, and prove production-route equivalence before retiring aliases. The supplied mission narrows this delivery further by explicitly requiring exact discovery-name preservation. + +### `polylogue-t46.8.2.1` + +This later child proposes removing `archive_list_sessions` and `archive_search_sessions` as zero-use duplicates. That conflicts with the present mission's exact-name preservation. The patch follows the present mission and keeps them; the child remains future work. + +### `polylogue-z9gh.9.1` + +The shared bounded query transaction is unfinished and owns canonical selection, physical paging, resumability, cancellation, resource bounds, and deletion of surface-local owners. A declaration registration migration cannot honestly claim to complete it. + +### `polylogue-s1kr` + +The generated semantic-operation CLI/MCP/Python parity matrix is still an open task. Current source contains only a static legacy `docs/plans/api-parity.yaml`; there is no operation-level renderer/artifact to update. Inventing that program here would be a new authority, not a migration update. + +## History findings + +- `fd7b35492` establishes the recent shared execution-control seam and explicitly leaves remaining direct MCP reads to `z9gh.9.1`. +- `113d1af97` defines current MCP response-budget, replay, pagination, excerpts, and diagnostics behavior; registration changes must preserve it. +- `56eaa2245` shows the repository's established alias-removal pattern: usage evidence, exact inventory/schema/contract updates, then deletion. It supports preserving aliases here until the named proof exists. +- `881392211` moved analysis math from MCP into insights and the facade, supporting a thin registration adapter rather than a new MCP abstraction for execution. +- `6b416f091` documents the discovery-cost and route-choice problem but also keeps recipe expansion separate from query machinery. + +## Test Suite Diet findings + +`architecture/06-query-cancellation-and-bounds.md` requires one immutable query execution context and owned read lifecycle. Surface adapters must not add their own parser, timeout, background task, semantic cap, or full-result buffer. Its migration sequence preserves aliases until equivalence proof. The no-wrapper registration adapter conforms to these constraints by changing only public registration authority. + +## Contradictions adjudicated + +| Contradiction | Evidence | Decision | +|---|---|---| +| Snapshot says dirty, but branch delta is empty | Manifest `dirty: true`; delta patch/files/log all zero; tracked reconstruction matched commit | Name the commit and dirty ambiguity; do not invent a patch. | +| Mission depends on accepted `beads-02`, but package/implementation is absent | `/mnt/data`, snapshot, all refs, and File Library contain no accepted result | Reconstruct the smallest compatible kernel; mark exact dependency compatibility unverified. | +| Future Bead removes two aliases, current mission preserves exact names | `t46.8.2.1` versus mission text | Preserve both aliases in this patch. | +| Long-term epic wants 10–15 default tools, current mission requires exact discovery names | `t46.8`/`.1` versus delivery prompt | Migrate authority without reducing the surface; no false epic closure claim. | +| Mission mentions generated Python parity matrix, current source has none | `s1kr` open; only static legacy YAML, no renderer | Do not invent a parallel matrix. Record as incomplete pending the owning artifact. | +| Existing schema witness says 76 tools, live admin server says 104 | Generated fixture versus actual FastMCP discovery | Regenerate from live server and add a full wire-contract witness. | +| Runtime expected inventory projected from production can be vacuous | `tests/infra/mcp.py` now consumes declarations | Add a fixed committed 104-row declaration witness and compare production to it. | + +## Falsification evidence and residual uncertainty + +Evidence that would invalidate or materially change this patch: + +1. The accepted `beads-02` ZIP defines an incompatible declaration schema/registration generator or already contains a merged implementation. +2. A full CI/Nix run finds import-layer, generated-surface, or platform-specific failures not exercised here. +3. The project treats tool listing order as a separately versioned contract beyond the direct base/current equality already observed. +4. The canonical Python parity renderer exists outside this snapshot and requires declaration rows to bind stable semantic operation IDs. +5. Incident-scale or cancellation tests show that moving registration timing changes lifecycle behavior despite exact handler and wire parity. + +No evidence found in the supplied authority supports deleting existing aliases, changing query/pagination semantics, or adding a second execution framework in this patch. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/HANDOFF.md new file mode 100644 index 000000000..d5e43c103 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/HANDOFF.md @@ -0,0 +1,189 @@ +# Beads 03 handoff — MCP read-surface algebra migration + +## Mission and result + +This patch migrates the existing MCP query/read/search/insight/topology/timeline/status registration authority onto one typed declaration inventory while preserving the live public surface. It does not create a second query engine: every handler remains the original callable and continues through the existing ArchiveStore, Polylogue facade, insight registry, query contracts, response-budget, and safe-call paths. + +The patch declares all 104 live tools exactly once, enables declaration-driven registration for the 56 read-family tools owned by this mission, removes their superseded registration glue, generates fixed semantic and live-wire witnesses, and proves representative search, ObjectRef, topology, and timeline behavior through the actual FastMCP registration and production operations route. + +## Snapshot identity + +- Supplied project-state archive SHA-256: `3d0c5a2e5024632c05438dbc3a08aae1dcd31529472b1185ad5b80c0cd933e30`. +- Manifest generation time: `2026-07-17T043202Z`. +- Manifest source: `/realm/project/polylogue`. +- Branch: `master`. +- Commit and merge base: `f654480cadb7cc4c194704e24dfd483199547b35`. +- Remote default ref in the bundle: `origin/master`, at the same commit. +- The manifest says `dirty: true`, but `polylogue-branch-delta.patch`, `polylogue-branch-delta-files.txt`, and `polylogue-branch-delta-log.txt` are all zero bytes. Before implementation, the reconstructed tracked working tree was byte-equivalent to the named commit. The archive therefore proves a dirty marker but supplies no captured tracked patch; ignored/local runtime state is unavailable. +- Supplied Test Suite Diet archive SHA-256: `d4fa7fc31c70ff30db076e526836ffc81b8124d3f54c7add5e58d3f57c9dcbff`. +- Mission prompt SHA-256: `410f7d2493a956c44eee713367deb04a3ffa9d4bcde27b00d6a9551cfba67d09`. + +## Dependency status + +The accepted `beads-02` result package was not supplied, was not present under `/mnt/data`, was not present in the snapshot, and was not found in the available File Library. Current source contains no `DeclarationSpec` implementation or accepted declaration package to import. The only available `beads-02` material was its mission prompt. + +Accordingly, this patch reconstructs the smallest current-source-compatible declaration/registration kernel needed for this migration. It deliberately does not claim byte/API compatibility with an unavailable accepted `beads-02` package. Reconciliation against that package is the highest-priority remaining integration check. + +## Evidence inspected + +Repository guidance and architecture: + +- `CLAUDE.md`, especially substrate-first layering, MCP leaf-adapter rules, tool-contract expectations, and generated topology requirements. +- Test Suite Diet `architecture/06-query-cancellation-and-bounds.md`, plus the query/rewrite/status area notes and execution/proof maps. The relevant decision is one shared query lifecycle, no adapter-local parser/timeout/buffering authority, and alias preservation until equivalence proof. + +Production route and dependencies: + +- `polylogue/mcp/server.py`, `server_tools.py`, `server_insight_tools.py`, `server_support.py`, `archive_support.py`, `query_contracts.py`, `insight_tool_contracts.py`, and the context/mutation/personal-state/maintenance registration families. +- `polylogue/api/`, the archive/query execution and source-freshness surfaces, `polylogue/insights/`, and `polylogue/storage/sqlite/archive_tiers/archive.py` where they own actual behavior. +- FastMCP's installed registration/listing implementation, including explicit `name`/`description` support and handler-signature inspection. + +Tests and generated authorities: + +- `tests/infra/mcp.py`. +- `tests/unit/mcp/test_tool_contracts.py`, `test_tool_discovery.py`, `test_envelope_contracts.py`, and `test_server_surfaces.py`. +- Existing `tests/data/witnesses/mcp-tool-schemas.json` and generated-surface/devtools tests. +- `docs/mcp-reference.md`, `docs/devtools.md`, topology projections, and docs-coverage verification. + +Beads: + +- `polylogue-t46.8`, `polylogue-t46.8.1`, `polylogue-t46.8.2`, `polylogue-t46.8.2.1`, `polylogue-z9gh.9.1`, and `polylogue-s1kr`, including later notes. + +History: + +- `fd7b35492` — shared interruptible/admission-controlled query execution. +- `113d1af97` — MCP response-budget and pagination/diagnostic behavior. +- `56eaa2245` — prior evidence-gated duplicate MCP alias removal. +- `881392211` — insight math moved beneath MCP into insights/API owners. +- `6b416f091` — discovery/cookbook history and the large-surface usability problem. + +## Mechanism + +### Typed declaration algebra + +`polylogue/mcp/declarations/models.py` introduces frozen typed declarations for: + +- semantic family and public verb; +- object kind and role gate; +- result semantics: exhaustive page, point read, top-k, aggregate, bounded context, recursive graph, status, mutation, or maintenance; +- continuation kind; +- implementation and lower-layer operation owner; +- output contract, minimal invocation, ObjectRef/query/absence semantics, compatibility route, migration owner, and declaration-registration state. + +`polylogue/mcp/declarations/registry.py` is the single executable inventory. It contains all 104 live tools exactly once and marks 56 tools as migrated here: + +- query: 7; +- read: 10; +- insight: 18; +- topology: 7; +- timeline: 6; +- status: 8. + +The remaining 48 context/assertion/coordination/personal-state/mutation/review/maintenance tools are inventoried but remain on their current registration paths for the separately owned migration. + +### No-wrapper FastMCP adapter + +`polylogue/mcp/declarations/registration.py` looks up the declaration, applies the existing `role_allows` policy, sets the exact public name/description, tags the original callable for anti-bypass testing, and passes that callable directly to `FastMCP.tool(...)`. + +There is no `*args/**kwargs` wrapper and no new parser, request model, timeout, cursor, result buffer, or error mapper. FastMCP still inspects the original handler annotations and any explicit `__signature__`; the existing operations remain behavior authority. + +### Production migration and dominated glue removal + +- `server_tools.py` now registers the 13 query-family and 15 read/status tools from ordered declaration mappings. +- The superseded `_MCPReadToolSpec`, `_MCP_READ_TOOL_SPECS`, `_register_mcp_read_tool`, and source-freshness registration helper call are removed from this route. +- `server_insight_tools.py` registers all 28 mission-owned insight/topology/timeline tools through the same adapter. Existing `InsightListToolSpec` remains the dynamic signature/schema owner; the declaration owns public inventory and semantic classification. +- Context/assertion/coordination and privileged families retain their current decorators and role-specific implementations. + +### Generated parity witnesses + +`devtools render mcp-contract-fixtures` now renders and drift-checks: + +- `mcp-tool-declarations.json`: 104 fixed semantic inventory rows; +- `mcp-tool-schemas.json`: refreshed from the actual 104-tool FastMCP surface (the supplied witness had only 76 rows and stale shapes); +- `mcp-tool-contracts.json`: 104 exact live rows containing name, role, description, input schema, output schema, and annotations. + +The production declaration inventory now supplies `tests/infra/mcp.py`, docs coverage, the generated MCP tool index, and affordance-usage inventory. Fixed committed witnesses remain independent anti-vacuity oracles. + +## Decisions + +1. **Preserve every live name.** The mission explicitly requires exact discovery names. Although later child `polylogue-t46.8.2.1` proposes removing `archive_list_sessions` and `archive_search_sessions`, this patch keeps both aliases and their independent behavior. +2. **Migrate registration authority, not read execution semantics.** `polylogue-z9gh.9.1` still owns the unfinished sole bounded query transaction. This patch neither duplicates nor pretends to complete it. +3. **Keep FastMCP as wire-schema authority.** The declaration adapter passes original callables so dynamic signatures and Pydantic constraints cannot drift through a generic wrapper. +4. **Use a fixed witness to prevent production-derived vacuity.** Runtime inventories project from production, while committed declaration and wire fixtures fail if a row, role, description, or schema changes without an intentional regeneration. +5. **Do not delete existing tests/helpers.** Existing tests were retained and updated only where their old private registration-spec authority was superseded. +6. **Do not invent the missing Python operation-parity program.** `polylogue-s1kr` remains open; the snapshot has a static `docs/plans/api-parity.yaml` but no generated operation-level CLI/MCP/Python matrix or renderer. Creating that unrelated program here would violate current authority. This is recorded as incomplete rather than disguised. + +## Changed files + +Production declaration and migration: + +- `polylogue/mcp/declarations/__init__.py` +- `polylogue/mcp/declarations/models.py` +- `polylogue/mcp/declarations/registration.py` +- `polylogue/mcp/declarations/registry.py` +- `polylogue/mcp/server_tools.py` +- `polylogue/mcp/server_insight_tools.py` + +Generated-surface and production inventory consumers: + +- `devtools/render_mcp_contract_fixtures.py` +- `devtools/generated_surfaces.py` +- `devtools/command_catalog.py` +- `devtools/render_mcp_tool_index.py` +- `devtools/verify_docs_coverage.py` +- `devtools/affordance_usage.py` +- `docs/devtools.md` +- `docs/mcp-reference.md` +- `docs/plans/topology-target.yaml` +- `docs/topology-status.md` + +Witnesses and tests: + +- `tests/data/witnesses/mcp-tool-declarations.json` +- `tests/data/witnesses/mcp-tool-contracts.json` +- `tests/data/witnesses/mcp-tool-schemas.json` +- `tests/infra/mcp.py` +- `tests/unit/mcp/test_read_tool_declarations.py` +- `tests/unit/mcp/test_tool_contracts.py` +- `tests/unit/mcp/test_tool_discovery.py` +- `tests/unit/mcp/test_envelope_contracts.py` + +No complete replacement files are needed; `PATCH.diff` fully disambiguates the change. + +## Acceptance matrix + +| Requirement | Status | Evidence | +|---|---|---| +| Apply-ready against named snapshot | Pass | Clean detached worktree at `f654480…`; `git apply --check --binary`; exact staged replay byte-equal to `PATCH.diff`. | +| Every live tool inventoried exactly once | Pass | 104 unique declaration rows; fixed fixture and actual admin discovery both contain 104 names. | +| Mission-owned families migrated | Pass | 56 handlers carry the declaration marker and register only through the shared adapter. | +| Exact names and discovery order | Pass | Direct base-commit/current FastMCP comparison is ordered-equal for read/write/review/admin. | +| Exact role gates | Pass | Discovery counts and membership unchanged: 66 / 95 / 97 / 104. | +| Exact descriptions, input/output schemas, annotations | Pass | Pre-change/current comparison equal by name for all roles; full committed wire witness matches live admin surface. | +| Query behavior | Pass for representative route | Seeded `search` traverses actual registered handler, query/archive support, ArchiveStore/FTS, and returns the planted parent only. | +| ObjectRef semantics and absence | Pass for representative route | Registered `resolve_ref` uses real Polylogue/archive resolution, returns canonical `session:`, and preserves typed unresolved payload/caveat. | +| Topology behavior | Pass for representative route | Registered `get_session_topology` returns planted parent/root/thread through the real facade/archive path. | +| Timeline/insight behavior | Pass for representative route | Registered generic `usage_timeline` traverses insight registry and real Polylogue operation. | +| Pagination and error/absence contracts | Pass for focused existing routes | Search cursor/total, message offsets, invalid offset, not-found summary, and envelope suites pass unchanged. | +| Superseded per-tool registration glue removed | Pass for migrated slice | Old read spec/helper and direct decorators/calls are removed; declaration marker test fails on bypass. | +| MCP fixtures/generated docs updated | Pass | Renderer checks, generated-surface tests, tool index, devtools reference, topology, and docs coverage pass. | +| Generated Python semantic-operation parity matrix | Not available in snapshot | `polylogue-s1kr` is open; no renderer/artifact exists to update. No parallel substitute invented. | +| Exact compatibility with accepted `beads-02` package | Unverified | Package/API absent from all supplied authority. | +| Full repository/Nix/live daemon/browser/incident-scale verification | Unverified | Not available or not run in this container. | + +## Apply order + +1. Check out `f654480cadb7cc4c194704e24dfd483199547b35` with no local changes. +2. Run `git apply --check --binary PATCH.diff`. +3. Run `git apply --binary PATCH.diff`. +4. Sync the repository's normal development dependencies. +5. Run the generated-surface checks listed in `TESTS.md`. +6. Run the focused MCP/devtools lanes listed in `TESTS.md`, then the repository's normal quick/full gates. +7. Before merge, reconcile `polylogue/mcp/declarations/` against the actual accepted `beads-02` package if that package exists outside the supplied authority. + +## Risks and remaining work + +The principal risk is dependency mismatch: an actual accepted `beads-02` package may define different types, fields, generated artifacts, or registration APIs. If its conceptual shape matches this patch, reconciliation should be a small repair—rename/import adaptation plus fixture regeneration. If it contains the full t46.8.1 resource/prompt/equivalence/manual model or an already-generated Python operation matrix, integrating it would be a substantial second pass and could replace much of this reconstructed kernel. + +The patch intentionally does not finish `polylogue-z9gh.9.1`, collapse the default read surface to 10–15 verbs, remove competing aliases, add URI resources/prompts, run cold-model trials, or prove cancellation/RSS/temp cleanup at incident scale. Those are separate architecture and execution contracts. Treating this registration migration as their completion would be unsafe. + +The focused suites and clean-apply proof are strong for this patch's registration and representative runtime behavior. Another ordinary iteration without the missing `beads-02` artifact or full CI evidence is likely to add only small repair value. A second pass with that artifact, the canonical Python parity renderer, or failures from the full Nix/CI gate could add substantial value. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/PATCH.diff new file mode 100644 index 000000000..f613f156b --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/PATCH.diff @@ -0,0 +1,20870 @@ +diff --git a/devtools/affordance_usage.py b/devtools/affordance_usage.py +index c080f11d16cda83c3eae491eea365e4b4a7cab8a..226afcfe53c6cdab2d21e86ad135b11d9f015abd 100644 +--- a/devtools/affordance_usage.py ++++ b/devtools/affordance_usage.py +@@ -596,12 +596,12 @@ def _mcp_action_rows(conn: Connection) -> list[dict[str, object]]: + + + def _mcp_surface_inventory(conn: Connection) -> list[dict[str, object]]: +- from tests.infra.mcp import EXPECTED_TOOL_NAMES ++ from polylogue.mcp.declarations import expected_tool_names + + tool_counts = _aggregate_tool_counts(_mcp_action_rows(conn)) + counts_by_tool = {str(row["tool_name"]): row for row in tool_counts} + rows: list[dict[str, object]] = [] +- for name in sorted(EXPECTED_TOOL_NAMES): ++ for name in sorted(expected_tool_names()): + tool_key = f"polylogue/{name}" + observed = counts_by_tool.get(tool_key) + actions = _int(observed.get("actions") if observed else 0) +diff --git a/devtools/command_catalog.py b/devtools/command_catalog.py +index f621ebb30ba4383d0b5f4aa732a16363f0f81c1e..cddcddcd0eb522ff5ef41db371c5f0e0488763ff 100644 +--- a/devtools/command_catalog.py ++++ b/devtools/command_catalog.py +@@ -175,6 +175,20 @@ COMMAND_SPECS: tuple[CommandSpec, ...] = ( + "Render docs/test-quality-workflows.md from executable lane, mutation, and benchmark registries.", + "devtools.render_quality_reference", + ), ++ CommandSpec( ++ "render mcp-contract-fixtures", ++ "generated surfaces", ++ "Render MCP declaration, schema, and full wire-contract witnesses.", ++ "devtools.render_mcp_contract_fixtures", ++ use_when=( ++ "Refresh the committed MCP semantic and wire witnesses after an intentional tool " ++ "declaration, role, description, or schema change." ++ ), ++ examples=( ++ "devtools render mcp-contract-fixtures", ++ "devtools render mcp-contract-fixtures --check", ++ ), ++ ), + CommandSpec( + "render mcp-tool-index", + "generated surfaces", +@@ -182,7 +196,7 @@ COMMAND_SPECS: tuple[CommandSpec, ...] = ( + "devtools.render_mcp_tool_index", + use_when=( + "Keep every registered MCP tool name individually reachable from the docs tree " +- "(tests/infra/mcp.py:EXPECTED_TOOL_NAMES) after adding or removing a tool, so " ++ "(polylogue.mcp.declarations.expected_tool_names) after adding or removing a tool, so " + "`devtools verify docs-coverage` stays clean without hand-duplicating the list." + ), + examples=("devtools render mcp-tool-index", "devtools render mcp-tool-index --check"), +diff --git a/devtools/generated_surfaces.py b/devtools/generated_surfaces.py +index 7072da5945cc838bc357a6c11b151365622f7079..a4362c8ffadf130fd03ed08666ed37177c76b84c 100644 +--- a/devtools/generated_surfaces.py ++++ b/devtools/generated_surfaces.py +@@ -11,6 +11,7 @@ from devtools import ( + render_demo_corpus_datasheet, + render_devtools_reference, + render_docs_surface, ++ render_mcp_contract_fixtures, + render_mcp_tool_index, + render_openapi, + render_pages, +@@ -182,13 +183,27 @@ GENERATED_SURFACES: tuple[GeneratedSurface, ...] = ( + "README.md", + ), + ), ++ GeneratedSurface( ++ name="mcp-contract-fixtures", ++ label="MCP contract fixtures", ++ description="Render committed MCP declaration and live FastMCP wire-contract witnesses.", ++ command=control_plane_argv("render mcp-contract-fixtures"), ++ main=render_mcp_contract_fixtures.main, ++ inputs=( ++ "devtools/render_mcp_contract_fixtures.py", ++ "polylogue/mcp/declarations/", ++ "polylogue/mcp/server.py", ++ "polylogue/mcp/server_*.py", ++ "polylogue/mcp/insight_tool_contracts.py", ++ ), ++ ), + GeneratedSurface( + name="mcp-tool-index", + label="MCP tool index", + description="Render the exhaustive generated tool-name appendix into docs/mcp-reference.md.", + command=control_plane_argv("render mcp-tool-index"), + main=render_mcp_tool_index.main, +- inputs=("devtools/render_mcp_tool_index.py", "tests/infra/mcp.py"), ++ inputs=("devtools/render_mcp_tool_index.py", "polylogue/mcp/declarations/"), + ), + GeneratedSurface( + name="topology-status", +diff --git a/devtools/render_mcp_contract_fixtures.py b/devtools/render_mcp_contract_fixtures.py +new file mode 100644 +index 0000000000000000000000000000000000000000..140a86ded8f5ff1fdc2186268ed83fbc80e241b4 +--- /dev/null ++++ b/devtools/render_mcp_contract_fixtures.py +@@ -0,0 +1,91 @@ ++"""Render committed MCP declaration and wire-contract witnesses. ++ ++The production declaration registry owns semantic inventory and role gates. ++FastMCP remains the wire-schema authority because it inspects the original ++handlers, including dynamic insight signatures. This renderer snapshots both ++projections so a migration cannot silently change either side. ++""" ++ ++from __future__ import annotations ++ ++import argparse ++import asyncio ++import json ++import sys ++from pathlib import Path ++ ++from devtools.command_catalog import control_plane_command ++from devtools.render_support import write_if_changed ++from polylogue.mcp.declarations import MCP_TOOL_REGISTRY, tool_declaration_rows ++from polylogue.mcp.server import build_server ++ ++DEFAULT_DECLARATION_PATH = Path("tests/data/witnesses/mcp-tool-declarations.json") ++DEFAULT_SCHEMA_PATH = Path("tests/data/witnesses/mcp-tool-schemas.json") ++DEFAULT_CONTRACT_PATH = Path("tests/data/witnesses/mcp-tool-contracts.json") ++ ++ ++def _json_text(payload: object) -> str: ++ return json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=False) + "\n" ++ ++ ++async def _wire_rows() -> tuple[list[dict[str, object]], list[dict[str, object]]]: ++ tools = await build_server(role="admin").list_tools() ++ schemas: list[dict[str, object]] = [] ++ contracts: list[dict[str, object]] = [] ++ for tool in sorted(tools, key=lambda item: item.name): ++ declaration = MCP_TOOL_REGISTRY[tool.name] ++ schemas.append({"name": tool.name, "parameters": tool.inputSchema}) ++ contracts.append( ++ { ++ "name": tool.name, ++ "required_role": declaration.required_role, ++ "description": tool.description, ++ "input_schema": tool.inputSchema, ++ "output_schema": tool.outputSchema, ++ "annotations": (None if tool.annotations is None else tool.annotations.model_dump(mode="json")), ++ } ++ ) ++ return schemas, contracts ++ ++ ++def build_outputs() -> dict[Path, str]: ++ """Build all declaration and live FastMCP witness payloads.""" ++ ++ schema_rows, contract_rows = asyncio.run(_wire_rows()) ++ return { ++ DEFAULT_DECLARATION_PATH: _json_text(tool_declaration_rows()), ++ DEFAULT_SCHEMA_PATH: _json_text(schema_rows), ++ DEFAULT_CONTRACT_PATH: _json_text(contract_rows), ++ } ++ ++ ++def _check_outputs(outputs: dict[Path, str]) -> int: ++ stale = [path for path, rendered in outputs.items() if not path.exists() or path.read_text() != rendered] ++ if stale: ++ print("render mcp-contract-fixtures: out of sync:", file=sys.stderr) ++ for path in stale: ++ print(f" - {path}", file=sys.stderr) ++ print( ++ f"render mcp-contract-fixtures: run: {control_plane_command('render mcp-contract-fixtures')}", ++ file=sys.stderr, ++ ) ++ return 1 ++ print("render mcp-contract-fixtures: sync OK") ++ return 0 ++ ++ ++def main(argv: list[str] | None = None) -> int: ++ parser = argparse.ArgumentParser(description="Render MCP declaration and wire-contract witnesses.") ++ parser.add_argument("--check", action="store_true", help="Exit non-zero when any witness is stale.") ++ args = parser.parse_args(argv) ++ ++ outputs = build_outputs() ++ if args.check: ++ return _check_outputs(outputs) ++ for path, rendered in outputs.items(): ++ write_if_changed(path, rendered) ++ return 0 ++ ++ ++if __name__ == "__main__": ++ raise SystemExit(main()) +diff --git a/devtools/render_mcp_tool_index.py b/devtools/render_mcp_tool_index.py +index fe66cc79d236f69edfb1fb03195fd57b48347ae6..83e8ca6cdab09accbdd85844a1f061761994e2a2 100644 +--- a/devtools/render_mcp_tool_index.py ++++ b/devtools/render_mcp_tool_index.py +@@ -2,7 +2,7 @@ + + ``docs/mcp-reference.md`` hand-curates a representative sample of tools by + category; that stays. This adds a generated appendix enumerating every +-registered tool name (``tests/infra/mcp.py:EXPECTED_TOOL_NAMES``), so each ++registered tool name (``polylogue.mcp.declarations.expected_tool_names``), so each + tool is individually reachable from the docs tree (polylogue-3tl.9's + docs-coverage lint checks exactly this) without hand-duplicating the list. + """ +@@ -21,9 +21,9 @@ DEFAULT_DOC_PATH = Path("docs/mcp-reference.md") + + + def _tool_names() -> tuple[str, ...]: +- from tests.infra.mcp import EXPECTED_TOOL_NAMES ++ from polylogue.mcp.declarations import expected_tool_names + +- return tuple(sorted(EXPECTED_TOOL_NAMES)) ++ return tuple(sorted(expected_tool_names())) + + + def build_tool_index_section(tool_names: tuple[str, ...] | None = None) -> str: +diff --git a/devtools/verify_docs_coverage.py b/devtools/verify_docs_coverage.py +index 1f860ddd0818c29b30046b1910e4755da245949f..9b53dc66afc1b8aaac3ec0e49ad06f9d9155de8c 100644 +--- a/devtools/verify_docs_coverage.py ++++ b/devtools/verify_docs_coverage.py +@@ -13,7 +13,7 @@ reused here rather than re-derived: + + - CLI commands: ``polylogue.cli.command_inventory.iter_command_paths`` over + the live Click tree. +-- MCP tools: ``tests.infra.mcp.EXPECTED_TOOL_NAMES``. ++- MCP tools: ``polylogue.mcp.declarations.expected_tool_names``. + - Config keys: ``polylogue.config.config_inventory_by_key``. + - Daemon routes: ``polylogue.daemon.route_contracts.ROUTE_CONTRACTS``, + restricted to ``stable``/``shell_supported`` routes (``operational`` and +@@ -82,9 +82,9 @@ def _cli_inventory() -> tuple[str, ...]: + + + def _mcp_inventory() -> tuple[str, ...]: +- from tests.infra.mcp import EXPECTED_TOOL_NAMES ++ from polylogue.mcp.declarations import expected_tool_names + +- return tuple(sorted(EXPECTED_TOOL_NAMES)) ++ return tuple(sorted(expected_tool_names())) + + + def _config_inventory() -> tuple[str, ...]: +diff --git a/docs/devtools.md b/docs/devtools.md +index 356f6110a4c066b8a25083d0e39d2f360de2419e..4d76c17c63db1c7f498e1e199d8ae1c1cf0fb3fa 100644 +--- a/docs/devtools.md ++++ b/docs/devtools.md +@@ -105,6 +105,7 @@ These are the commands worth remembering during normal repo work: + | `devtools render demo-corpus-datasheet` | Render docs/plans/demo-corpus-construct-audit.md from the demo family registry and a measured seed archive. | + | `devtools render devtools-reference` | Render the command catalog inside docs/devtools.md. | + | `devtools render docs-surface` | Render docs/README.md and the README documentation table. | ++| `devtools render mcp-contract-fixtures` | Render MCP declaration, schema, and full wire-contract witnesses. | + | `devtools render mcp-tool-index` | Render the generated exhaustive tool-name appendix into docs/mcp-reference.md. | + | `devtools render openapi` | Render docs/openapi/search.yaml from typed daemon query payload models. | + | `devtools render pages` | Build the GitHub Pages documentation site into .cache/site/. | +diff --git a/docs/mcp-reference.md b/docs/mcp-reference.md +index e0f82322968e4077179cb6abb67a16fa3e0e3f95..49edf30666968a8c906b6ff6ba37e4626e73e872 100644 +--- a/docs/mcp-reference.md ++++ b/docs/mcp-reference.md +@@ -26,9 +26,9 @@ Configuration below). A representative sample by category: + - **Maintenance** (admin role): `maintenance_preview`, `maintenance_execute`, + `rebuild_index`, `update_index`, `rebuild_session_insights`. + +-The exhaustive, currently-registered tool name set is a test-enforced contract, not hand +-duplicated here: `tests/infra/mcp.py:EXPECTED_TOOL_NAMES`. Adding a tool requires updating +-that set plus its tool contract (see `CLAUDE.md` § MCP gotchas). ++The exhaustive, currently registered tool inventory is owned by the production declaration ++registry: `polylogue/mcp/declarations/registry.py`. Tests and generated documentation project ++from that authority and pin it to committed declaration/schema witnesses (see `CLAUDE.md` § MCP gotchas). + + ## Resources + +diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml +index f7f9c974216bc0c67829c306b658e87429bb1728..96a2071d3f4e4deaae7d9558c8e3fdc455143e2e 100644 +GIT binary patch +delta 282 +zcmZp_!7=R)N5dAz946M3)a0DR>GGV6veWsP8D-hP{G!Pd7%c^IlMD1A+{BX1{Ji4n +zf=P^`lWnUdr#G-OvT!3wP2XtCXoR7D^7)sd)9)HEvQL(Y7YFO%LDEwUls3oI^Z&az +zlZo-hlemH_})SYnd^ + +delta 114 +zcmbPshok)lN5dAz9H!}eSQzD|pXX&{+diL#@etGGh20X<`#Bl8r@OK(^b + +diff --git a/docs/topology-status.md b/docs/topology-status.md +index d8e0e49740260b6a6a23dd76a91192d6d66d485e..a868467a68e339e778a8083e086124f98800452f 100644 +GIT binary patch +delta 28 +jcmca3a7SRnLl#E!$&XlinG6jl7qY4Xsm(K3k1zoMo1qD~ + +delta 28 +jcmca3a7SRnLl#Ek$&XlinGDP)7qY4Xsm(K3k1zoMo307I + +diff --git a/polylogue/mcp/declarations/__init__.py b/polylogue/mcp/declarations/__init__.py +new file mode 100644 +index 0000000000000000000000000000000000000000..ea3c1033bbd394cb0eeacd882f984067143b30c3 +--- /dev/null ++++ b/polylogue/mcp/declarations/__init__.py +@@ -0,0 +1,29 @@ ++"""Shared MCP declaration algebra and registration projection.""" ++ ++from polylogue.mcp.declarations.models import ( ++ MCPContinuationKind, ++ MCPResultSemantics, ++ MCPToolDeclaration, ++ MCPToolFamily, ++ MCPToolVerb, ++) ++from polylogue.mcp.declarations.registry import ( ++ MCP_MIGRATED_READ_TOOL_NAMES, ++ MCP_TOOL_REGISTRY, ++ expected_tool_names, ++ tool_declaration_rows, ++ tool_names_for_role, ++) ++ ++__all__ = [ ++ "MCPContinuationKind", ++ "MCPResultSemantics", ++ "MCPToolDeclaration", ++ "MCPToolFamily", ++ "MCPToolVerb", ++ "MCP_MIGRATED_READ_TOOL_NAMES", ++ "MCP_TOOL_REGISTRY", ++ "expected_tool_names", ++ "tool_declaration_rows", ++ "tool_names_for_role", ++] +diff --git a/polylogue/mcp/declarations/models.py b/polylogue/mcp/declarations/models.py +new file mode 100644 +index 0000000000000000000000000000000000000000..b46af5875e7c2b78196fc2eba6daed557c58f4ae +--- /dev/null ++++ b/polylogue/mcp/declarations/models.py +@@ -0,0 +1,154 @@ ++"""Typed declaration model for the public MCP tool surface. ++ ++The declaration records protocol metadata only. Handler implementations stay ++in the existing MCP leaf modules and continue to delegate to archive/query, ++operations, insight, and policy owners below the surface. ++""" ++ ++from __future__ import annotations ++ ++import re ++from collections.abc import Mapping ++from dataclasses import dataclass, field ++from enum import StrEnum ++from types import MappingProxyType ++ ++from polylogue.mcp.server_support import MCPRole ++ ++_TOOL_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$") ++ ++ ++class MCPToolFamily(StrEnum): ++ """Semantic family used for migration, discovery, and parity checks.""" ++ ++ QUERY = "query" ++ READ = "read" ++ INSIGHT = "insight" ++ TOPOLOGY = "topology" ++ TIMELINE = "timeline" ++ STATUS = "status" ++ CONTEXT = "context" ++ ASSERTION = "assertion" ++ COORDINATION = "coordination" ++ PERSONAL_STATE = "personal-state" ++ MUTATION = "mutation" ++ REVIEW = "review" ++ MAINTENANCE = "maintenance" ++ ++ ++class MCPToolVerb(StrEnum): ++ """Public protocol verb represented by a declaration.""" ++ ++ QUERY = "query" ++ SEARCH = "search" ++ LIST = "list" ++ GET = "get" ++ READ = "read" ++ RESOLVE = "resolve" ++ EXPLAIN = "explain" ++ STATUS = "status" ++ FIND = "find" ++ AGGREGATE = "aggregate" ++ COMPARE = "compare" ++ CORRELATE = "correlate" ++ COMPILE = "compile" ++ WRITE = "write" ++ JUDGE = "judge" ++ RUN = "run" ++ ++ ++class MCPResultSemantics(StrEnum): ++ """Logical result contract, independent of transport presentation.""" ++ ++ EXHAUSTIVE_PAGE = "exhaustive-page" ++ POINT_READ = "point-read" ++ TOP_K = "top-k" ++ AGGREGATE = "aggregate" ++ BOUNDED_CONTEXT = "bounded-context" ++ RECURSIVE_GRAPH = "recursive-graph" ++ STATUS = "status" ++ MUTATION = "mutation" ++ MAINTENANCE = "maintenance" ++ ++ ++class MCPContinuationKind(StrEnum): ++ """Continuation semantics exposed by the current compatibility route.""" ++ ++ NONE = "none" ++ OFFSET = "offset" ++ CURSOR = "cursor" ++ OFFSET_OR_CURSOR = "offset-or-cursor" ++ TOP_K = "top-k-bound" ++ GRAPH_UNPAGED = "recursive-graph-unpaged" ++ BOUNDED = "bounded-context" ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPToolDeclaration: ++ """One executable inventory row for a public MCP tool. ++ ++ ``registered_from_declaration`` is true only after the handler's FastMCP ++ registration is driven through :mod:`polylogue.mcp.declarations.registration`. ++ Unmigrated rows remain in the same inventory so role/name drift is caught ++ while their implementation moves in the separately-owned migration. ++ """ ++ ++ name: str ++ description: str ++ family: MCPToolFamily ++ verb: MCPToolVerb ++ object_kind: str ++ required_role: MCPRole ++ result_semantics: MCPResultSemantics ++ continuation: MCPContinuationKind ++ implementation: str ++ operation_owner: str ++ output_contract: str ++ minimal_arguments: Mapping[str, object] = field(default_factory=dict) ++ object_ref_semantics: str = "none" ++ query_semantics: str = "none" ++ absence_contract: str = "typed empty/error envelope" ++ compatibility_route: str | None = None ++ migration_owner: str = "polylogue-t46.8.3" ++ registered_from_declaration: bool = False ++ ++ def __post_init__(self) -> None: ++ if not _TOOL_NAME_RE.fullmatch(self.name): ++ raise ValueError(f"invalid MCP tool name: {self.name!r}") ++ for field_name in ("object_kind", "implementation", "operation_owner", "output_contract"): ++ if not getattr(self, field_name): ++ raise ValueError(f"{self.name}: {field_name} must be non-empty") ++ object.__setattr__(self, "minimal_arguments", MappingProxyType(dict(self.minimal_arguments))) ++ ++ def to_contract_row(self) -> dict[str, object]: ++ """Return a stable JSON-compatible row for fixtures and generated views.""" ++ ++ return { ++ "name": self.name, ++ "description": self.description, ++ "family": self.family.value, ++ "verb": self.verb.value, ++ "object_kind": self.object_kind, ++ "required_role": self.required_role, ++ "result_semantics": self.result_semantics.value, ++ "continuation": self.continuation.value, ++ "implementation": self.implementation, ++ "operation_owner": self.operation_owner, ++ "output_contract": self.output_contract, ++ "minimal_arguments": dict(self.minimal_arguments), ++ "object_ref_semantics": self.object_ref_semantics, ++ "query_semantics": self.query_semantics, ++ "absence_contract": self.absence_contract, ++ "compatibility_route": self.compatibility_route, ++ "migration_owner": self.migration_owner, ++ "registered_from_declaration": self.registered_from_declaration, ++ } ++ ++ ++__all__ = [ ++ "MCPContinuationKind", ++ "MCPResultSemantics", ++ "MCPToolDeclaration", ++ "MCPToolFamily", ++ "MCPToolVerb", ++] +diff --git a/polylogue/mcp/declarations/registration.py b/polylogue/mcp/declarations/registration.py +new file mode 100644 +index 0000000000000000000000000000000000000000..29a001131c922baebf9f289078955fad0d5ebbe2 +--- /dev/null ++++ b/polylogue/mcp/declarations/registration.py +@@ -0,0 +1,60 @@ ++"""FastMCP registration adapter for declared tools.""" ++ ++from __future__ import annotations ++ ++from collections.abc import Callable, Mapping ++from typing import TYPE_CHECKING, Any, TypeVar ++ ++from polylogue.mcp.declarations.registry import MCP_TOOL_REGISTRY ++from polylogue.mcp.server_support import role_allows ++ ++if TYPE_CHECKING: ++ from mcp.server.fastmcp import FastMCP ++ ++ from polylogue.mcp.server_support import ServerCallbacks ++ ++ToolHandler = TypeVar("ToolHandler", bound=Callable[..., Any]) ++MCP_DECLARATION_ATTRIBUTE = "__polylogue_mcp_declaration__" ++ ++ ++def register_declared_tool( ++ mcp: FastMCP, ++ hooks: ServerCallbacks, ++ name: str, ++ handler: ToolHandler, ++) -> ToolHandler: ++ """Register ``handler`` from the single declaration row for ``name``. ++ ++ No wrapper is introduced: FastMCP inspects the original callable, so the ++ existing annotations, explicit ``__signature__`` overrides, asyncness, and ++ Pydantic constraints remain the wire-schema authority. ++ """ ++ ++ declaration = MCP_TOOL_REGISTRY[name] ++ if not declaration.registered_from_declaration: ++ raise ValueError(f"{name}: declaration is not enabled for algebra registration") ++ if not role_allows(hooks.role, declaration.required_role): ++ return handler ++ handler.__name__ = declaration.name ++ handler.__doc__ = declaration.description ++ setattr(handler, MCP_DECLARATION_ATTRIBUTE, declaration) ++ mcp.tool(name=declaration.name, description=declaration.description)(handler) ++ return handler ++ ++ ++def register_declared_tools( ++ mcp: FastMCP, ++ hooks: ServerCallbacks, ++ handlers: Mapping[str, Callable[..., Any]], ++) -> None: ++ """Register an ordered family of handlers through their declaration rows.""" ++ ++ for name, handler in handlers.items(): ++ register_declared_tool(mcp, hooks, name, handler) ++ ++ ++__all__ = [ ++ "MCP_DECLARATION_ATTRIBUTE", ++ "register_declared_tool", ++ "register_declared_tools", ++] +diff --git a/polylogue/mcp/declarations/registry.py b/polylogue/mcp/declarations/registry.py +new file mode 100644 +index 0000000000000000000000000000000000000000..66e461d9f53044994e6899c8f055872f4b0d6d0a +--- /dev/null ++++ b/polylogue/mcp/declarations/registry.py +@@ -0,0 +1,2431 @@ ++"""Executable MCP semantic inventory and read-migration registry. ++ ++Every currently registered tool appears exactly once. The read/search/insight/ ++topology/timeline rows marked ``registered_from_declaration`` are the beads-03 ++migration group; context/assertion and privileged rows stay inventoried for the ++separately-owned beads-04 migration. ++""" ++ ++from __future__ import annotations ++ ++from collections.abc import Iterator, Mapping ++ ++from polylogue.mcp.declarations.models import ( ++ MCPContinuationKind, ++ MCPResultSemantics, ++ MCPToolDeclaration, ++ MCPToolFamily, ++ MCPToolVerb, ++) ++from polylogue.mcp.server_support import MCPRole, role_allows ++ ++ ++def _declare( ++ *, ++ name: str, ++ description: str, ++ family: MCPToolFamily, ++ verb: MCPToolVerb, ++ object_kind: str, ++ required_role: MCPRole, ++ result_semantics: MCPResultSemantics, ++ continuation: MCPContinuationKind, ++ implementation: str, ++ operation_owner: str, ++ output_contract: str, ++ minimal_arguments: Mapping[str, object] | None = None, ++ object_ref_semantics: str = "none", ++ query_semantics: str = "none", ++ absence_contract: str = "typed empty/error envelope", ++ compatibility_route: str | None = None, ++ migration_owner: str = "polylogue-t46.8.3", ++ registered_from_declaration: bool = False, ++) -> MCPToolDeclaration: ++ return MCPToolDeclaration( ++ name=name, ++ description=description, ++ family=family, ++ verb=verb, ++ object_kind=object_kind, ++ required_role=required_role, ++ result_semantics=result_semantics, ++ continuation=continuation, ++ implementation=implementation, ++ operation_owner=operation_owner, ++ output_contract=output_contract, ++ minimal_arguments={} if minimal_arguments is None else minimal_arguments, ++ object_ref_semantics=object_ref_semantics, ++ query_semantics=query_semantics, ++ absence_contract=absence_contract, ++ compatibility_route=compatibility_route, ++ migration_owner=migration_owner, ++ registered_from_declaration=registered_from_declaration, ++ ) ++ ++ ++_DECLARATIONS = ( ++ _declare( ++ name="action_affordances", ++ description="Return shared query-action affordance metadata for agents.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.READ, ++ object_kind="action-affordance", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..action_affordances", ++ operation_owner="polylogue.operations.action_contracts.action_affordance_list_payload", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="add_mark", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="mark", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..add_mark", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..add_mark", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="add_tag", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="tag", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..add_tag", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..add_tag", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="agent_coordination", ++ description="Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ family=MCPToolFamily.COORDINATION, ++ verb=MCPToolVerb.READ, ++ object_kind="agent-coordination", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..agent_coordination", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..agent_coordination", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="aggregate_sessions", ++ description=( ++ "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).\n" ++ "\n" ++ "#1691: programmatic session analysis primitives — GROUP BY over session profiles.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="session-profile", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..aggregate_sessions", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"session_ids": "test:a,test:b"}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="archive_coverage", ++ description="List archive coverage from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="archive-coverage", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.archive_coverage", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="archive_debt", ++ description="Return the unified archive debt list used by CLI and daemon surfaces.", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.STATUS, ++ object_kind="archive-debt", ++ required_role="read", ++ result_semantics=MCPResultSemantics.STATUS, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..archive_debt", ++ operation_owner="polylogue.api.Polylogue.archive_debt", ++ output_contract="typed_envelope", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="archive_get_session", ++ description="Read a full session by exact id or prefix.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.GET, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..archive_get_session", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.read_session", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts exact id or compatibility prefix; emits canonical session id", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route="get_session_summary/get_messages", ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="archive_list_sessions", ++ description="List sessions from the split archive store.", ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.LIST, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_query_tools..archive_list_sessions", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.list_summaries", ++ output_contract="envelope:items,limit,offset,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="compatibility ArchiveStore route; exact parameter behavior retained", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route="list_sessions", ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="archive_search_sessions", ++ description="Search session block text.", ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.SEARCH, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_query_tools..archive_search_sessions", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.search", ++ output_contract="envelope:items,limit,query,total", ++ minimal_arguments={"limit": 1, "query": "hello"}, ++ object_ref_semantics="none", ++ query_semantics="compatibility ArchiveStore route; exact parameter behavior retained", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route="search", ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="blackboard_list", ++ description=( ++ "List persistent agent blackboard notes, newest first (#1697).\n" ++ "\n" ++ "Filter by ``kind`` (finding/blocker/decision/handoff/question/observation),\n" ++ "by ``scope_repo``, or set ``unresolved=True`` for open blockers/questions.\n" ++ ), ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.READ, ++ object_kind="assertion", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..blackboard_list", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..blackboard_list", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="blackboard_post", ++ description=( ++ "Post a note to the persistent agent blackboard (#1697).\n" ++ "\n" ++ "``kind`` must be one of finding/blocker/decision/handoff/question/\n" ++ "observation. Optional scope fields (repo/session/issue/path) and\n" ++ "related session ids are recorded with the note. Assertion metadata is\n" ++ "mirrored into the unified assertion row for agent-written notes.\n" ++ ), ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="assertion", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..blackboard_post", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..blackboard_post", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="build_context_image", ++ description=( ++ "Build a bounded context image for agent analysis.\n" ++ "\n" ++ "Selects sessions through the query algebra (filters / free-text query)\n" ++ "and compiles the requested views into the shared ``ContextImage`` payload\n" ++ "with token-budgeted accumulation and explicit omission accounting.\n" ++ "\n" ++ "Parameters:\n" ++ " project_path: Filter sessions by cwd prefix pattern.\n" ++ " project_repo: Filter sessions by git repo URL or name.\n" ++ " since: Start date for session filter (ISO format).\n" ++ " until: End date for session filter (ISO format).\n" ++ " origin: Source-origin filter.\n" ++ " query: Free-text query for semantic narrowing.\n" ++ " max_sessions: Maximum sessions to include (1-20).\n" ++ " max_tokens: Optional token budget; segments over budget are omitted\n" ++ " with reason 'budget' instead of being silently truncated.\n" ++ " detail_level: 'summary' (metadata only), 'compact'/'full' (messages).\n" ++ " redact_paths: Redact filesystem paths for privacy (default True).\n" ++ " include_assertions: Include context-inject assertion claims.\n" ++ ), ++ family=MCPToolFamily.CONTEXT, ++ verb=MCPToolVerb.COMPILE, ++ object_kind="build-context-image", ++ required_role="read", ++ result_semantics=MCPResultSemantics.BOUNDED_CONTEXT, ++ continuation=MCPContinuationKind.BOUNDED, ++ implementation="polylogue.mcp.server_context_tools.register_context_tools..build_context_image", ++ operation_owner="polylogue.mcp.server_context_tools.register_context_tools..build_context_image", ++ output_contract="single_object", ++ minimal_arguments={"project_repo": "test"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="bulk_tag_sessions", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="tag", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..bulk_tag_sessions", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..bulk_tag_sessions", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="capture_assertion_candidate", ++ description="Capture a terminal-equivalent assertion candidate for later judgment.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="assertion", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..capture_assertion_candidate", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..capture_assertion_candidate", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="clear_corrections", ++ description="Delete one or all corrections for a session.", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="correction", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..clear_corrections", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..clear_corrections", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="compare_sessions", ++ description=( ++ "Compare multiple session profiles side-by-side.\n" ++ "\n" ++ "Takes a comma-separated list of 2-10 session IDs, fetches\n" ++ "their merged-tier profiles, and returns a side-by-side comparison\n" ++ "with highlighted differences.\n" ++ "\n" ++ "#1691: programmatic session analysis primitives.\n" ++ ), ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.COMPARE, ++ object_kind="session-profile", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..compare_sessions", ++ operation_owner="polylogue.mcp.server_insight_tools.register_insight_tools..compare_sessions", ++ output_contract="single_object", ++ minimal_arguments={"session_ids": "test:a,test:b"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="compile_context", ++ description="Compile a bounded context image from query/ref seeds and read views.", ++ family=MCPToolFamily.CONTEXT, ++ verb=MCPToolVerb.COMPILE, ++ object_kind="compile-context", ++ required_role="read", ++ result_semantics=MCPResultSemantics.BOUNDED_CONTEXT, ++ continuation=MCPContinuationKind.BOUNDED, ++ implementation="polylogue.mcp.server_tools.register_query_tools..compile_context", ++ operation_owner="polylogue.mcp.server_tools.register_query_tools..compile_context", ++ output_contract="single_object", ++ minimal_arguments={"read_views": "messages", "seed_ref": "session:test:conv-discovery-nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="compose_context_preamble", ++ description=( ++ "Compose a ContextPreamble for SessionStart context injection (#1494).\n" ++ "\n" ++ "Builds a typed preamble with session lineage, recent related sessions,\n" ++ "and project state for Claude Code and Codex sessions. Designed for\n" ++ "SessionStart hook scripts that inject context into new agent sessions.\n" ++ "When the harness has allocated the new session identity, pass it as\n" ++ "``successor_session_id`` so the call-log row and resulting preamble are\n" ++ "queryable against the session that received the context.\n" ++ ), ++ family=MCPToolFamily.CONTEXT, ++ verb=MCPToolVerb.COMPILE, ++ object_kind="compose-context-preamble", ++ required_role="read", ++ result_semantics=MCPResultSemantics.BOUNDED_CONTEXT, ++ continuation=MCPContinuationKind.BOUNDED, ++ implementation="polylogue.mcp.server_context_tools.register_context_tools..compose_context_preamble", ++ operation_owner="polylogue.mcp.server_context_tools.register_context_tools..compose_context_preamble", ++ output_contract="single_object", ++ minimal_arguments={"context": "{}"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="correlate_session", ++ description=( ++ "Link a session to git commits and GitHub issue/PR references (#1690).\n" ++ "\n" ++ "Returns git commits likely produced by the session (via time-window\n" ++ "analysis and file-overlap scoring), plus GitHub issue/PR references\n" ++ "extracted from session message text.\n" ++ ), ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.CORRELATE, ++ object_kind="session-correlation", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..correlate_session", ++ operation_owner="polylogue.mcp.server_insight_tools.register_insight_tools..correlate_session", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="correlate_sessions", ++ description=( ++ "Compute Pearson correlation between two numeric session metrics.\n" ++ "\n" ++ "Fetches all session profiles matching the optional filters, extracts\n" ++ "the two named metrics, and returns the Pearson r with interpretation.\n" ++ "\n" ++ "Supported metric names from session profiles:\n" ++ "message_count, word_count, tool_use_count, thinking_count,\n" ++ "engaged_duration_ms, tool_active_duration_ms, wall_duration_ms,\n" ++ "total_cost_usd, total_duration_ms, substantive_count.\n" ++ "\n" ++ "#1691: programmatic session analysis primitives.\n" ++ ), ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.CORRELATE, ++ object_kind="session-correlation", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..correlate_sessions", ++ operation_owner="polylogue.mcp.server_insight_tools.register_insight_tools..correlate_sessions", ++ output_contract="single_object", ++ minimal_arguments={"session_ids": "test:a,test:b"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="cost_outlook", ++ description=( ++ "Project the current billing cycle for a subscription plan (#1138).\n" ++ "\n" ++ "Returns the typed :class:`polylogue.cost.outlook.CycleOutlook`\n" ++ "payload from #1137: cycle window, burn rate, projected total,\n" ++ "quota pressure, overage rows, coverage ratio, and confidence.\n" ++ "\n" ++ "Parameters\n" ++ "----------\n" ++ "plan:\n" ++ ' Subscription plan name (e.g. ``"claude-pro"``,\n' ++ ' ``"claude-max-5x"``, ``"chatgpt-plus"``). User-supplied rows\n' ++ " from ``[[cost.subscription.plans]]`` are merged with the\n" ++ " curated seed; user rows always win.\n" ++ "method:\n" ++ ' Projection method tag — one of ``"linear"``,\n' ++ ' ``"trailing-7d-mean"``, ``"eom-naive"``. The chosen method\n' ++ " is echoed in the response so callers cannot lose track of\n" ++ " how the projection was made.\n" ++ "\n" ++ "Returns the JSON-serialized outlook on success, or a typed\n" ++ "error envelope when the plan is unknown or has no cycle anchor.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="cost-outlook", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..cost_outlook", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"plan": "claude-pro"}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="cost_rollups", ++ description="List cost rollups from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="cost-rollup", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.cost_rollups", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="delete_annotation", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="annotation", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_annotation", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_annotation", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="delete_metadata", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="session-metadata", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_metadata", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_metadata", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="delete_recall_pack", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="recall-pack", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_recall_pack", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_recall_pack", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="delete_saved_view", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="saved-view", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_saved_view", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_saved_view", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="delete_session", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="delete-session", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..delete_session", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..delete_session", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="delete_workspace", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="workspace", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_workspace", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_workspace", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="embedding_preflight", ++ description="", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.STATUS, ++ object_kind="embedding-index", ++ required_role="read", ++ result_semantics=MCPResultSemantics.STATUS, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..embedding_preflight", ++ operation_owner="polylogue.storage.embeddings.status_payload.embedding_preflight_payload", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="embedding_status", ++ description="", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.STATUS, ++ object_kind="embedding-index", ++ required_role="read", ++ result_semantics=MCPResultSemantics.STATUS, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..embedding_status", ++ operation_owner="polylogue.storage.embeddings.status_payload.embedding_status_payload", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="explain_import", ++ description="Explain import detection/parsing from a local path or archived raw evidence.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.EXPLAIN, ++ object_kind="import-evidence", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..explain_import", ++ operation_owner="polylogue.api.Polylogue.explain_import", ++ output_contract="typed_envelope", ++ minimal_arguments={"limit": 1, "raw_ref": "raw:discovery-nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="explain_query_expression", ++ description="Explain query DSL parsing and lowering through the shared grammar.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.EXPLAIN, ++ object_kind="query-expression", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..explain_query_expression", ++ operation_owner="polylogue.api.Polylogue.explain_query_expression", ++ output_contract="single_object", ++ minimal_arguments={"expression": "repo:polylogue"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="facets", ++ description=( ++ "Compute scoped and global facet aggregates over the archive. Filter parameters mirror " ++ "``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries " ++ "``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency " ++ "weights for each value." ++ ), ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="archive-facet", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools.._facets", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.facets", ++ output_contract="single_object", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="find_abandoned_sessions", ++ description="Find sessions whose terminal state indicates dangling work.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.FIND, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.TOP_K, ++ continuation=MCPContinuationKind.TOP_K, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..find_abandoned_sessions", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="find_resume_candidates", ++ description="Rank logical sessions likely to match the operator's current context.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.FIND, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.TOP_K, ++ continuation=MCPContinuationKind.TOP_K, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..find_resume_candidates", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:candidates,total", ++ minimal_arguments={"repo_path": "/tmp/nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="find_similar_sessions", ++ description=( ++ "Find sessions similar to a given session.\n" ++ "\n" ++ "If embeddings are enabled, delegates to neighbor_candidates.\n" ++ "Otherwise falls back to metadata similarity: same workflow_shape,\n" ++ "same origin, similar time window, overlapping tags.\n" ++ "\n" ++ "#1691: programmatic session analysis primitives.\n" ++ ), ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.FIND, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.TOP_K, ++ continuation=MCPContinuationKind.TOP_K, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..find_similar_sessions", ++ operation_owner="polylogue.mcp.server_insight_tools.register_insight_tools..find_similar_sessions", ++ output_contract="single_object", ++ minimal_arguments={"query": "test"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="find_stuck_sessions", ++ description="Find sessions with origin tool calls bounded as stuck.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.FIND, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.TOP_K, ++ continuation=MCPContinuationKind.TOP_K, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..find_stuck_sessions", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_context_delivery", ++ description=( ++ "Read the exact persisted context image for its recorded recipient.\n" ++ "\n" ++ "The caller must supply the recipient object ref recorded at delivery.\n" ++ "A mismatch is intentionally indistinguishable from a missing receipt.\n" ++ "This tool audits an existing delivery; it never creates a context pack\n" ++ "or schedules automatic recall.\n" ++ ), ++ family=MCPToolFamily.CONTEXT, ++ verb=MCPToolVerb.GET, ++ object_kind="get-context-delivery", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_context_tools.register_context_tools..get_context_delivery", ++ operation_owner="polylogue.mcp.server_context_tools.register_context_tools..get_context_delivery", ++ output_contract="single_object", ++ minimal_arguments={ ++ "recipient_ref": "agent:discovery", ++ "snapshot_ref": "context-snapshot:test:conv-discovery-nonexistent:explicit-recall", ++ }, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="get_logical_session", ++ description="Return compact logical-session lineage for read-pull consumers.", ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.GET, ++ object_kind="logical-session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.RECURSIVE_GRAPH, ++ continuation=MCPContinuationKind.GRAPH_UNPAGED, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_logical_session", ++ operation_owner="polylogue.api.Polylogue.get_logical_session", ++ output_contract="envelope:cycle_detected,descendants,root_id,session_id,siblings,thread", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_messages", ++ description=( ++ "Return a filtered message page.\n" ++ "\n" ++ "``offset`` is evaluated after role/type/material filters. Use\n" ++ '``tail=True`` or ``offset_from="end"`` to read the filtered tail of a\n' ++ "large session without first calculating the filtered total. Set\n" ++ "``max_chars_per_message`` to cap each returned body; ``excerpt=True``\n" ++ "preserves both its head and tail, or a window around the first term in\n" ++ "``match_query`` when that query occurs in the message.\n" ++ ), ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.GET, ++ object_kind="message", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_messages", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.read_session", ++ output_contract="envelope:messages,total", ++ minimal_arguments={"limit": 1, "session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_metadata", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.GET, ++ object_kind="session-metadata", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..get_metadata", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..get_metadata", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="get_pathologies", ++ description=( ++ "Agent-workflow pathology distribution over a matched scope (#2383).\n" ++ "\n" ++ "Read-shaped delegate to :meth:`Polylogue.pathology_report`: runs the\n" ++ "deterministic detectors (wasted-loop, missed-review, stale-context) over\n" ++ "the matched sessions' run projections and returns the typed\n" ++ ":class:`polylogue.insights.pathology.PathologyReport` — findings with\n" ++ "evidence refs plus the per-kind distribution. Deterministic and\n" ++ "rule-based (no LLM-as-judge); a rebuild over identical evidence is\n" ++ "identical.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="pathology-report", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..get_pathologies", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_postmortem_bundle", ++ description=( ++ "Distilled postmortem bundle over a matched session scope (#2380).\n" ++ "\n" ++ "Read-shaped delegate to :meth:`Polylogue.postmortem_bundle`: resolves\n" ++ "the matched session set from the filter scope, aggregates per-session\n" ++ "session digests, and returns the typed\n" ++ ":class:`polylogue.insights.postmortem.PostmortemBundle` (top sessions\n" ++ "by cost, repos touched, tool/work-kind rollups, failure-mode signals).\n" ++ "The analysis cap defaults to 200 sessions; a larger match marks the\n" ++ "bundle ``truncated`` rather than silently capping.\n" ++ "\n" ++ "This is the agent-preferred distilled read over the matched scope.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="postmortem-bundle", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..get_postmortem_bundle", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_resume_brief", ++ description=( ++ "Get a typed resume brief for one archived session.\n" ++ "\n" ++ "The brief composes already-materialized session insights (profile,\n" ++ "enrichment, work events, phases, work thread) into a handoff\n" ++ "payload. Provenance fields cite the session, message, work-event,\n" ++ "and phase IDs that contributed.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.GET, ++ object_kind="resume-brief", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..get_resume_brief", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_session_summary", ++ description="", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.GET, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_session_summary", ++ operation_owner="polylogue.api.Polylogue.get_session_summary", ++ output_contract="single_object", ++ minimal_arguments={"id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts exact id or compatibility prefix; emits canonical session id", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_session_topology", ++ description=( ++ "Return typed lineage topology (ancestors/descendants/siblings/thread).\n" ++ "\n" ++ "Returns the resolved :class:`SessionTopology` graph (#1261 /\n" ++ "#866 slice D) plus pre-projected ref lists so external agents can\n" ++ "navigate session lineage without re-walking parent pointers.\n" ++ ), ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.GET, ++ object_kind="session-topology", ++ required_role="read", ++ result_semantics=MCPResultSemantics.RECURSIVE_GRAPH, ++ continuation=MCPContinuationKind.GRAPH_UNPAGED, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_session_topology", ++ operation_owner="polylogue.api.Polylogue.get_session_topology", ++ output_contract="envelope:ancestors,descendants,edges,nodes,root_id,siblings,target_id,thread", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_session_tree", ++ description="", ++ family=MCPToolFamily.TOPOLOGY, ++ verb=MCPToolVerb.GET, ++ object_kind="session-tree", ++ required_role="read", ++ result_semantics=MCPResultSemantics.RECURSIVE_GRAPH, ++ continuation=MCPContinuationKind.GRAPH_UNPAGED, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_session_tree", ++ operation_owner="polylogue.api.Polylogue.get_session_tree", ++ output_contract="envelope:items,total", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="get_stats_by", ++ description=( ++ "Session counts grouped by a calendar/origin dimension.\n" ++ "\n" ++ "These are the sessions-table count dimensions. The CLI ``stats\n" ++ "--by`` surface additionally supports ``action``/``tool``/``repo``/\n" ++ "``work-kind``, which are insight-summary aggregations over a different\n" ++ "code path, not plain session counts (#1749).\n" ++ ), ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="archive-statistics", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..get_stats_by", ++ operation_owner="polylogue.api.Polylogue.get_stats_by", ++ output_contract="stats_map", ++ minimal_arguments={"group_by": "origin"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="import_annotation_batch", ++ description="Import bounded JSONL labels as provenance-stamped candidates.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="annotation", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..import_annotation_batch", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..import_annotation_batch", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="insight_rigor_audit", ++ description=( ++ "Per-product rigor profile across materialized insights (#1275).\n" ++ "\n" ++ "Returns the JSON-serialized :class:`InsightRigorAuditReport`. For\n" ++ "each contracted insight product, reports the share of rows that\n" ++ "carry evidence/inference/fallback markers, the stale-version row\n" ++ "count, and a confidence-bucket distribution.\n" ++ ), ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="insight-rigor", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..insight_rigor_audit", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="join_typed_annotations", ++ description="Join typed annotation labels to exact structural targets.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.READ, ++ object_kind="annotation", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..join_typed_annotations", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..join_typed_annotations", ++ output_contract="envelope:groups,joined_count,rows,selected_annotation_count", ++ minimal_arguments={"limit": 1, "schema_id": "discovery", "schema_version": 1, "statuses": ["candidate"]}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="judge_assertion_candidate", ++ description="Judge one candidate; caller text is provenance, not authorization.", ++ family=MCPToolFamily.REVIEW, ++ verb=MCPToolVerb.JUDGE, ++ object_kind="assertion", ++ required_role="review", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidate", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidate", ++ output_contract="envelope:applied_count,failed_count,items", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="judge_assertion_candidates", ++ description="Bulk judge candidates with independently reported partial success.", ++ family=MCPToolFamily.REVIEW, ++ verb=MCPToolVerb.JUDGE, ++ object_kind="assertion", ++ required_role="review", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidates", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidates", ++ output_contract="envelope:applied_count,failed_count,items", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_annotations", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.LIST, ++ object_kind="annotation", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_annotations", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_annotations", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_assertion_candidate_reviews", ++ description="List durable candidate review history separately from active claims.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.LIST, ++ object_kind="assertion", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..list_assertion_candidate_reviews", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..list_assertion_candidate_reviews", ++ output_contract="envelope:items,limit,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_assertion_candidates", ++ description="List pending assertion candidates without promoting them.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.LIST, ++ object_kind="assertion", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..list_assertion_candidates", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..list_assertion_candidates", ++ output_contract="envelope:items,limit,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_assertion_claims", ++ description="List assertion-backed lifecycle claims.", ++ family=MCPToolFamily.ASSERTION, ++ verb=MCPToolVerb.LIST, ++ object_kind="assertion", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..list_assertion_claims", ++ operation_owner="polylogue.mcp.server_tools.register_read_tools..list_assertion_claims", ++ output_contract="envelope:items,limit,total", ++ minimal_arguments={"limit": 1, "target_ref": "session:test:conv-discovery-nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_corrections", ++ description="List stored learning corrections.", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.LIST, ++ object_kind="correction", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_corrections", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_corrections", ++ output_contract="envelope:corrections,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_marks", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.LIST, ++ object_kind="mark", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..list_marks", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..list_marks", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_read_view_profiles", ++ description="List executable read-view profile metadata for agents.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.LIST, ++ object_kind="read-view-profile", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..list_read_view_profiles", ++ operation_owner="polylogue.api.Polylogue.list_read_view_profiles", ++ output_contract="envelope:read_views,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="list_recall_packs", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.LIST, ++ object_kind="recall-pack", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_recall_packs", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_recall_packs", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_saved_views", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.LIST, ++ object_kind="saved-view", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_saved_views", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_saved_views", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_sessions", ++ description="List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.LIST, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_query_tools.._list_sessions", ++ operation_owner="polylogue.mcp.archive_support.archive_session_list_payload", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="list_tags", ++ description="List deterministic tag-count pages without expanding a large tag map.", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.LIST, ++ object_kind="tag", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..list_tags", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..list_tags", ++ output_contract="stats_map", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="list_workspaces", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.LIST, ++ object_kind="workspace", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_workspaces", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_workspaces", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="maintenance_execute", ++ description=( ++ "Run (or dry-run) maintenance backfill targets.\n" ++ "\n" ++ "Returns the shared :class:`MaintenanceOperationEnvelope` with\n" ++ '``mode="execute"``. Per-target failures are isolated and\n' ++ "reported through the bounded ``failure_samples`` envelope. The\n" ++ "scope-filter parameters mirror the CLI flags on\n" ++ "``polylogue ops maintenance run``.\n" ++ ), ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.RUN, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_execute", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_execute", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="maintenance_list", ++ description=( ++ "List every persisted maintenance operation snapshot (#1197).\n" ++ "\n" ++ 'Returns a bounded envelope ``{"items": [...], "total": N}`` where\n' ++ "each item is the same shape as :func:`maintenance_status`. Use\n" ++ "when the operator (or another agent) needs to see in-flight and\n" ++ "recently failed operations without knowing their ids.\n" ++ ), ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.LIST, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_list", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_list", ++ output_contract="envelope:items,total", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="maintenance_preview", ++ description=( ++ "Dry-run summary of maintenance backfill targets.\n" ++ "\n" ++ "Returns the shared :class:`MaintenanceOperationEnvelope` so the\n" ++ "result shape is byte-for-byte identical to the CLI ``polylogue\n" ++ "maintenance plan --output-format json`` and the daemon HTTP\n" ++ "``POST /api/maintenance/plan`` responses. The scope-filter\n" ++ "parameters mirror the CLI ``--session-id``, ``--origin``,\n" ++ "``--source-family``, ``--source-root``, ``--since``/``--until``,\n" ++ "``--failure-kind``, and ``--parser-version`` flags.\n" ++ ), ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.RUN, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_preview", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_preview", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="maintenance_status", ++ description=( ++ "Return one persisted maintenance operation snapshot by id (#1197).\n" ++ "\n" ++ "Wraps the shared\n" ++ ":class:`~polylogue.maintenance.envelope.MaintenanceOperationEnvelope`\n" ++ "with the state-file ``updated_at`` and ``state_path`` so the\n" ++ "result is byte-shape-identical to the daemon HTTP\n" ++ "``GET /api/maintenance/status/`` response.\n" ++ ), ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.STATUS, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_status", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_status", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="named_source_freshness", ++ description=( ++ "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight " ++ "convergence." ++ ), ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.STATUS, ++ object_kind="source-path", ++ required_role="read", ++ result_semantics=MCPResultSemantics.STATUS, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..named_source_freshness", ++ operation_owner="polylogue.storage.raw_authority.source_freshness", ++ output_contract="single_object", ++ minimal_arguments={"source_path": "/tmp/polylogue-discovery-nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="neighbor_candidates", ++ description="", ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.READ, ++ object_kind="session-neighbor", ++ required_role="read", ++ result_semantics=MCPResultSemantics.TOP_K, ++ continuation=MCPContinuationKind.TOP_K, ++ implementation="polylogue.mcp.server_tools.register_query_tools..neighbor_candidates", ++ operation_owner="polylogue.mcp.server_tools.register_query_tools..neighbor_candidates", ++ output_contract="envelope:items,limit,total", ++ minimal_arguments={"query": "test"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="provider_usage", ++ description="Return provider usage accounting diagnostics without estimating billing cost.", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="provider-usage", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..provider_usage", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.provider_usage", ++ output_contract="single_object", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="query_completions", ++ description="Return shared query/action completion metadata for non-shell clients.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.EXPLAIN, ++ object_kind="query-language", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..query_completions", ++ operation_owner="polylogue.api.Polylogue.query_completions", ++ output_contract="single_object", ++ minimal_arguments={"incomplete": "d", "kind": "field"}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="query_units", ++ description="Return terminal rows for explicit unit-source query expressions.", ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.QUERY, ++ object_kind="query-unit", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_query_tools..query_units", ++ operation_owner="polylogue.archive.query.execution_control.execute_archive_read", ++ output_contract="envelope:items,limit,offset,total,unit", ++ minimal_arguments={"expression": "messages where text:hello", "limit": 1}, ++ object_ref_semantics="terminal rows retain public refs from canonical query execution", ++ query_semantics="query DSL parse/lower/execute through shared QueryExecutionContext", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="raw_artifacts", ++ description="", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.READ, ++ object_kind="raw-artifact", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_tools.register_read_tools..raw_artifacts", ++ operation_owner="polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ output_contract="envelope:raw_artifacts,total", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="readiness_check", ++ description="", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.STATUS, ++ object_kind="archive-readiness", ++ required_role="read", ++ result_semantics=MCPResultSemantics.STATUS, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_read_tools..readiness_check", ++ operation_owner="polylogue.readiness.get_readiness", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="rebuild_index", ++ description="", ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.RUN, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_index", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_index", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="rebuild_session_insights", ++ description="", ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.RUN, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_session_insights", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_session_insights", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="record_correction", ++ description="Record a user correction targeting a derived insight.", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="correction", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..record_correction", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..record_correction", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="remove_mark", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="mark", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_mark", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_mark", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="remove_tag", ++ description="", ++ family=MCPToolFamily.MUTATION, ++ verb=MCPToolVerb.WRITE, ++ object_kind="tag", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_tag", ++ operation_owner="polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_tag", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="resolve_ref", ++ description="Resolve a public object/evidence ref into a bounded payload.", ++ family=MCPToolFamily.READ, ++ verb=MCPToolVerb.RESOLVE, ++ object_kind="object-ref", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..resolve_ref", ++ operation_owner="polylogue.api.Polylogue.resolve_ref", ++ output_contract="single_object", ++ minimal_arguments={"ref": "session:test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts canonical ObjectRef string and preserves typed not-found result", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="save_annotation", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="annotation", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_annotation", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_annotation", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="save_recall_pack", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="recall-pack", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_recall_pack", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_recall_pack", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="save_saved_view", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="saved-view", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_saved_view", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_saved_view", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="save_workspace", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="workspace", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_workspace", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_workspace", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="search", ++ description=( ++ "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` " ++ "fields." ++ ), ++ family=MCPToolFamily.QUERY, ++ verb=MCPToolVerb.SEARCH, ++ object_kind="session", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET_OR_CURSOR, ++ implementation="polylogue.mcp.server_tools.register_query_tools.._search", ++ operation_owner="polylogue.mcp.archive_support.archive_search_payload", ++ output_contract="envelope:hits,total", ++ minimal_arguments={"limit": 1, "query": "hello"}, ++ object_ref_semantics="none", ++ query_semantics="MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_costs", ++ description="List session costs from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="session-cost", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.session_costs", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_latency_profile", ++ description="Get per-session latency profile by session ID.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="session-latency", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..session_latency_profile", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_phases", ++ description="List session phases from the archive.", ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.READ, ++ object_kind="session-phase", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.session_phases", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_profile", ++ description="Get a single session profile by session ID.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="session-profile", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..session_profile", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_profiles", ++ description="List session profiles from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="session-profile", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.session_profiles", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_tag_rollups", ++ description="List session tag rollups from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="session-tag-rollup", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.session_tag_rollups", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_tool_timing", ++ description=( ++ "Get per-tool timing breakdown with evidence provenance.\n" ++ "\n" ++ "Returns per-tool timing from OTLP spans when available, falling back\n" ++ "to message-gap estimates. Each timing entry carries an\n" ++ '``evidence_source`` field: ``"otlp_span"`` for exact wallclock timing\n' ++ 'from instrumented tool calls, or ``"message_gap_estimate"`` for timing\n' ++ "inferred from inter-message gaps.\n" ++ ), ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.GET, ++ object_kind="tool-timing", ++ required_role="read", ++ result_semantics=MCPResultSemantics.POINT_READ, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..session_tool_timing", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"session_id": "test:conv-discovery-nonexistent"}, ++ object_ref_semantics="accepts session identifier and emits canonical evidence/session refs where available", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="session_work_events", ++ description="List work events from the archive.", ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.READ, ++ object_kind="work-event", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.session_work_events", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="set_metadata", ++ description="", ++ family=MCPToolFamily.PERSONAL_STATE, ++ verb=MCPToolVerb.WRITE, ++ object_kind="session-metadata", ++ required_role="write", ++ result_semantics=MCPResultSemantics.MUTATION, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..set_metadata", ++ operation_owner="polylogue.mcp.server_personal_state_tools.register_personal_state_tools..set_metadata", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="stats", ++ description="", ++ family=MCPToolFamily.STATUS, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="archive-statistics", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_tools.register_query_tools..stats", ++ operation_owner="polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ output_contract="single_object", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="threads", ++ description="List work threads from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="work-thread", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.threads", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="tool_call_latency_distribution", ++ description="Distribution of materialized per-session tool-call latency.", ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="tool-latency", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..tool_call_latency_distribution", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="tool_usage", ++ description="List tool usage from the archive.", ++ family=MCPToolFamily.INSIGHT, ++ verb=MCPToolVerb.READ, ++ object_kind="tool-usage", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.tool_usage", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="update_index", ++ description="", ++ family=MCPToolFamily.MAINTENANCE, ++ verb=MCPToolVerb.RUN, ++ object_kind="maintenance-operation", ++ required_role="admin", ++ result_semantics=MCPResultSemantics.MAINTENANCE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..update_index", ++ operation_owner="polylogue.mcp.server_maintenance_tools.register_maintenance_tools..update_index", ++ output_contract="operation_result", ++ minimal_arguments={}, ++ object_ref_semantics="none", ++ query_semantics="none", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.3", ++ registered_from_declaration=False, ++ ), ++ _declare( ++ name="usage_timeline", ++ description="List usage timeline from the archive.", ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.READ, ++ object_kind="usage-timeline", ++ required_role="read", ++ result_semantics=MCPResultSemantics.EXHAUSTIVE_PAGE, ++ continuation=MCPContinuationKind.OFFSET, ++ implementation="polylogue.mcp.server_insight_tools.usage_timeline", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="envelope:items,total", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++ _declare( ++ name="workflow_shape_distribution", ++ description="Histogram session workflow shapes by week, origin, or project.", ++ family=MCPToolFamily.TIMELINE, ++ verb=MCPToolVerb.AGGREGATE, ++ object_kind="workflow-shape", ++ required_role="read", ++ result_semantics=MCPResultSemantics.AGGREGATE, ++ continuation=MCPContinuationKind.NONE, ++ implementation="polylogue.mcp.server_insight_tools.register_insight_tools..workflow_shape_distribution", ++ operation_owner="polylogue.api.Polylogue", ++ output_contract="single_object", ++ minimal_arguments={"limit": 1}, ++ object_ref_semantics="none", ++ query_semantics="Polylogue insight operations/query model; no MCP-local evaluator", ++ absence_contract="typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ compatibility_route=None, ++ migration_owner="polylogue-t46.8.2", ++ registered_from_declaration=True, ++ ), ++) ++ ++ ++class MCPToolRegistry(Mapping[str, MCPToolDeclaration]): ++ """Validated immutable lookup over the executable declaration inventory.""" ++ ++ def __init__(self, declarations: tuple[MCPToolDeclaration, ...]) -> None: ++ by_name: dict[str, MCPToolDeclaration] = {} ++ for declaration in declarations: ++ if declaration.name in by_name: ++ raise ValueError(f"duplicate MCP declaration: {declaration.name}") ++ by_name[declaration.name] = declaration ++ self._by_name = by_name ++ ++ def __getitem__(self, name: str) -> MCPToolDeclaration: ++ return self._by_name[name] ++ ++ def __iter__(self) -> Iterator[str]: ++ return iter(self._by_name) ++ ++ def __len__(self) -> int: ++ return len(self._by_name) ++ ++ def names_for_role(self, role: MCPRole) -> frozenset[str]: ++ return frozenset( ++ declaration.name for declaration in self._by_name.values() if role_allows(role, declaration.required_role) ++ ) ++ ++ def contract_rows(self) -> tuple[dict[str, object], ...]: ++ return tuple(self._by_name[name].to_contract_row() for name in sorted(self._by_name)) ++ ++ ++MCP_TOOL_REGISTRY = MCPToolRegistry(_DECLARATIONS) ++MCP_MIGRATED_READ_TOOL_NAMES = frozenset( ++ declaration.name for declaration in _DECLARATIONS if declaration.registered_from_declaration ++) ++ ++ ++def expected_tool_names() -> frozenset[str]: ++ """Return the exact all-role discovery inventory.""" ++ ++ return frozenset(MCP_TOOL_REGISTRY) ++ ++ ++def tool_names_for_role(role: MCPRole) -> frozenset[str]: ++ """Project the exact role-scoped discovery inventory.""" ++ ++ return MCP_TOOL_REGISTRY.names_for_role(role) ++ ++ ++def tool_declaration_rows() -> tuple[dict[str, object], ...]: ++ """Return stable rows used by generated fixtures and parity checks.""" ++ ++ return MCP_TOOL_REGISTRY.contract_rows() ++ ++ ++__all__ = [ ++ "MCP_MIGRATED_READ_TOOL_NAMES", ++ "MCP_TOOL_REGISTRY", ++ "MCPToolRegistry", ++ "expected_tool_names", ++ "tool_declaration_rows", ++ "tool_names_for_role", ++] +diff --git a/polylogue/mcp/server_insight_tools.py b/polylogue/mcp/server_insight_tools.py +index 92adf394a7f15b2f9c4341383dc3a188687fcaa0..e2d6fec21b074a90bd82a56b01d7db4614c13ae6 100644 +--- a/polylogue/mcp/server_insight_tools.py ++++ b/polylogue/mcp/server_insight_tools.py +@@ -20,6 +20,7 @@ from polylogue.insights.registry import ( + fetch_insights_async, + insight_items_payload, + ) ++from polylogue.mcp.declarations.registration import register_declared_tool, register_declared_tools + from polylogue.mcp.insight_tool_contracts import InsightListToolSpec + from polylogue.mcp.payloads import MCPRootPayload + from polylogue.mcp.query_contracts import MCPSessionQueryRequest +@@ -96,7 +97,7 @@ def _register_list_tool( + return_annotation=str, + ) + +- mcp.tool()(wrapper) ++ register_declared_tool(mcp, hooks, spec.name, wrapper) + + + def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: +@@ -111,7 +112,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + # --- Special tools --- + +- @mcp.tool() + async def tool_call_latency_distribution( + since: str | None = None, + until: str | None = None, +@@ -134,7 +134,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("tool_call_latency_distribution", run) + +- @mcp.tool() + async def session_latency_profile(session_id: str) -> str: + """Get per-session latency profile by session ID.""" + +@@ -154,7 +153,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("session_latency_profile", run, session_id=session_id) + +- @mcp.tool() + async def find_stuck_sessions(since: str | None = None, limit: int = 20) -> str: + """Find sessions with origin tool calls bounded as stuck.""" + +@@ -179,7 +177,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("find_stuck_sessions", run) + +- @mcp.tool() + async def workflow_shape_distribution( + since: str | None = None, + until: str | None = None, +@@ -208,7 +205,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("workflow_shape_distribution", run) + +- @mcp.tool() + async def find_abandoned_sessions( + since: str | None = None, + repo_path: str | None = None, +@@ -237,7 +233,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("find_abandoned_sessions", run) + +- @mcp.tool() + async def session_profile(session_id: str, tier: str = "merged") -> str: + """Get a single session profile by session ID.""" + +@@ -256,7 +251,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("session_profile", run, session_id=session_id) + +- @mcp.tool() + async def get_resume_brief(session_id: str, related_limit: int = 6) -> str: + """Get a typed resume brief for one archived session. + +@@ -275,7 +269,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_resume_brief", run, session_id=session_id) + +- @mcp.tool() + async def find_resume_candidates( + repo_path: str, + cwd: str | None = None, +@@ -304,7 +297,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("find_resume_candidates", run) + +- @mcp.tool() + async def cost_outlook(plan: str, method: str = "linear") -> str: + """Project the current billing cycle for a subscription plan (#1138). + +@@ -362,7 +354,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("cost_outlook", run) + +- @mcp.tool() + async def insight_rigor_audit(sample_limit: int = 500) -> str: + """Per-product rigor profile across materialized insights (#1275). + +@@ -381,7 +372,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("insight_rigor_audit", run) + +- @mcp.tool() + async def aggregate_sessions( + group_by: str = "workflow_shape", + since: str | None = None, +@@ -412,7 +402,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("aggregate_sessions", run) + +- @mcp.tool() + async def compare_sessions(session_ids: str | None = None) -> str: + """Compare multiple session profiles side-by-side. + +@@ -453,7 +442,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + correlated_ids = tuple(s.strip() for s in (session_ids or "").split(",") if s.strip()) + return await hooks.async_safe_call("compare_sessions", run, session_ids=correlated_ids) + +- @mcp.tool() + async def find_similar_sessions( + session_id: str, + similarity_dimension: str = "auto", +@@ -546,7 +534,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("find_similar_sessions", run, session_id=session_id) + +- @mcp.tool() + async def correlate_sessions( + metric_x: str, + metric_y: str, +@@ -590,7 +577,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("correlate_sessions", run) + +- @mcp.tool() + async def correlate_session( + session_id: str, + repo_path: str | None = None, +@@ -673,7 +659,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("correlate_session", run, session_id=session_id) + +- @mcp.tool() + async def session_tool_timing(session_id: str) -> str: + """Get per-tool timing breakdown with evidence provenance. + +@@ -706,7 +691,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("session_tool_timing", run, session_id=session_id) + +- @mcp.tool() + async def get_postmortem_bundle( + query: str | None = None, + origin: str | None = None, +@@ -750,7 +734,6 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_postmortem_bundle", run) + +- @mcp.tool() + async def get_pathologies( + query: str | None = None, + origin: str | None = None, +@@ -787,5 +770,30 @@ def register_insight_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_pathologies", run) + ++ register_declared_tools( ++ mcp, ++ hooks, ++ { ++ "tool_call_latency_distribution": tool_call_latency_distribution, ++ "session_latency_profile": session_latency_profile, ++ "find_stuck_sessions": find_stuck_sessions, ++ "workflow_shape_distribution": workflow_shape_distribution, ++ "find_abandoned_sessions": find_abandoned_sessions, ++ "session_profile": session_profile, ++ "get_resume_brief": get_resume_brief, ++ "find_resume_candidates": find_resume_candidates, ++ "cost_outlook": cost_outlook, ++ "insight_rigor_audit": insight_rigor_audit, ++ "aggregate_sessions": aggregate_sessions, ++ "compare_sessions": compare_sessions, ++ "find_similar_sessions": find_similar_sessions, ++ "correlate_sessions": correlate_sessions, ++ "correlate_session": correlate_session, ++ "session_tool_timing": session_tool_timing, ++ "get_postmortem_bundle": get_postmortem_bundle, ++ "get_pathologies": get_pathologies, ++ }, ++ ) ++ + + __all__ = ["register_insight_tools"] +diff --git a/polylogue/mcp/server_tools.py b/polylogue/mcp/server_tools.py +index 5c08d927da843bda6c12ca39da18cf748f8440dc..4952cef412cd57de8d9ac21e962b00086610ce17 100644 +--- a/polylogue/mcp/server_tools.py ++++ b/polylogue/mcp/server_tools.py +@@ -23,6 +23,7 @@ from polylogue.mcp.archive_support import ( + blackboard_note_payload, + mcp_archive_root, + ) ++from polylogue.mcp.declarations.registration import register_declared_tools + from polylogue.mcp.payloads import ( + MCPArchiveSearchHitPayload, + MCPArchiveSearchPayload, +@@ -133,29 +134,6 @@ def _stats_embedding_overrides(config: Config) -> dict[str, object]: + } + + +-@dataclass(frozen=True) +-class _MCPReadToolSpec: +- name: str +- description: str +- linked_read_view: str | None = None +- output_model: str | None = None +- +- +-_MCP_READ_TOOL_SPECS: dict[str, _MCPReadToolSpec] = { +- "list_read_view_profiles": _MCPReadToolSpec( +- name="list_read_view_profiles", +- description="List executable read-view profile metadata for agents.", +- output_model="MCPRootPayload", +- ), +-} +- +- +-def _register_mcp_read_tool(mcp: FastMCP, handler: Any, spec: _MCPReadToolSpec) -> None: +- handler.__name__ = spec.name +- handler.__doc__ = spec.description +- mcp.tool()(handler) +- +- + def _split_archive_csv(value: str | None) -> tuple[str, ...]: + if value is None: + return () +@@ -263,9 +241,7 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + "Search the archive for sessions matching ``query``. " + "Filter parameters mirror ``MCPSessionQueryRequest`` fields." + ) +- mcp.tool()(_search) + +- @mcp.tool() + async def query_units( + expression: str, + limit: MCPToolLimit = 10, +@@ -372,7 +348,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("query_units", run) + +- @mcp.tool() + async def resolve_ref(ref: str) -> str: + """Resolve a public object/evidence ref into a bounded payload.""" + +@@ -407,7 +382,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + _list_sessions.__doc__ = ( + "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields." + ) +- mcp.tool()(_list_sessions) + + @mcp.tool() + async def compile_context( +@@ -448,7 +422,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("compile_context", run) + +- @mcp.tool() + async def archive_list_sessions( + origin: str | None = None, + exclude_origin: str | None = None, +@@ -573,7 +546,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("archive_list_sessions", run) + +- @mcp.tool() + async def archive_search_sessions( + query: str, + origin: str | None = None, +@@ -651,7 +623,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("archive_search_sessions", run) + +- @mcp.tool() + async def archive_get_session(session_id: str) -> str: + """Read a full session by exact id or prefix.""" + +@@ -664,7 +635,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("archive_get_session", run, session_id=session_id) + +- @mcp.tool() + async def neighbor_candidates( + id: str | None = None, + query: str | None = None, +@@ -692,7 +662,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("neighbor_candidates", run, session_id=id) + +- @mcp.tool() + async def stats() -> str: + async def run() -> str: + config = hooks.get_config() +@@ -710,7 +679,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("stats", run) + +- @mcp.tool() + def embedding_status(detail: bool = False) -> str: + def run() -> str: + from polylogue.readiness.capability import component_from_embedding_payload +@@ -728,7 +696,6 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return hooks.safe_call("embedding_status", run) + +- @mcp.tool() + def embedding_preflight( + rebuild: bool = False, + max_sessions: int | None = None, +@@ -776,9 +743,7 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + "``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket " + "counts plus inverse-document-frequency weights for each value." + ) +- mcp.tool()(_facets) + +- @mcp.tool() + async def provider_usage(origin: str | None = None, limit: int = 25, detail: str = "full") -> str: + """Return provider usage accounting diagnostics without estimating billing cost.""" + +@@ -794,15 +759,32 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("provider_usage", run) + ++ register_declared_tools( ++ mcp, ++ hooks, ++ { ++ "search": _search, ++ "query_units": query_units, ++ "resolve_ref": resolve_ref, ++ "list_sessions": _list_sessions, ++ "archive_list_sessions": archive_list_sessions, ++ "archive_search_sessions": archive_search_sessions, ++ "archive_get_session": archive_get_session, ++ "neighbor_candidates": neighbor_candidates, ++ "stats": stats, ++ "embedding_status": embedding_status, ++ "embedding_preflight": embedding_preflight, ++ "facets": _facets, ++ "provider_usage": provider_usage, ++ }, ++ ) ++ + + def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + # Exact source freshness is a read-only diagnostic. Configuration chooses + # the archive; callers can supply only the exact source path and cannot + # turn this into an arbitrary local-file inspection route. +- from polylogue.archive.query.source_freshness_surfaces import ( +- make_source_freshness_mcp_handler, +- register_source_freshness_mcp_tool, +- ) ++ from polylogue.archive.query.source_freshness_surfaces import make_source_freshness_mcp_handler + + source_freshness_handler = make_source_freshness_mcp_handler(lambda: mcp_archive_root(hooks.get_config())) + +@@ -816,8 +798,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("named_source_freshness", run) + +- register_source_freshness_mcp_tool(mcp, named_source_freshness) +- + compact_coordination_cache = _CompactCoordinationCache() + + @mcp.tool() +@@ -958,7 +938,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_assertion_candidate_reviews", run) + +- @mcp.tool() + async def get_session_summary(id: str) -> str: + async def run() -> str: + config = hooks.get_config() +@@ -974,7 +953,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_session_summary", run, session_id=id) + +- @mcp.tool() + async def get_session_tree(session_id: str) -> str: + async def run() -> str: + poly = hooks.get_polylogue() +@@ -983,7 +961,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_session_tree", run, session_id=session_id) + +- @mcp.tool() + async def get_session_topology(session_id: str) -> str: + """Return typed lineage topology (ancestors/descendants/siblings/thread). + +@@ -1004,7 +981,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_session_topology", run, session_id=session_id) + +- @mcp.tool() + async def get_logical_session(session_id: str) -> str: + """Return compact logical-session lineage for read-pull consumers.""" + +@@ -1020,7 +996,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_logical_session", run, session_id=session_id) + +- @mcp.tool() + async def get_stats_by(group_by: Literal["origin", "day", "month", "year"] = "origin") -> str: + """Session counts grouped by a calendar/origin dimension. + +@@ -1036,7 +1011,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_stats_by", run) + +- @mcp.tool() + async def get_messages( + session_id: str, + message_role: str | None = None, +@@ -1113,7 +1087,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_messages", run, session_id=session_id) + +- @mcp.tool() + async def raw_artifacts( + session_id: str, + limit: MCPToolLimit = 50, +@@ -1142,7 +1115,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("raw_artifacts", run, session_id=session_id) + +- @mcp.tool() + def readiness_check() -> str: + def run() -> str: + from polylogue.mcp.call_log import mcp_call_outbox_status +@@ -1162,7 +1134,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return hooks.safe_call("readiness_check", run) + +- @mcp.tool() + async def archive_debt( + kind: str | None = None, + only_actionable: bool = False, +@@ -1183,7 +1154,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("archive_debt", run) + +- @mcp.tool() + async def explain_import( + path: str | None = None, + raw_ref: str | None = None, +@@ -1222,9 +1192,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_read_view_profiles", run) + +- _register_mcp_read_tool(mcp, list_read_view_profiles, _MCP_READ_TOOL_SPECS["list_read_view_profiles"]) +- +- @mcp.tool() + async def explain_query_expression(expression: str) -> str: + """Explain query DSL parsing and lowering through the shared grammar.""" + +@@ -1234,7 +1201,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("explain_query_expression", run) + +- @mcp.tool() + async def query_completions( + kind: str, + incomplete: str = "", +@@ -1294,7 +1260,6 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("agent_coordination", run) + +- @mcp.tool() + async def action_affordances() -> str: + """Return shared query-action affordance metadata for agents.""" + +@@ -1308,6 +1273,28 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("action_affordances", run) + ++ register_declared_tools( ++ mcp, ++ hooks, ++ { ++ "named_source_freshness": named_source_freshness, ++ "get_session_summary": get_session_summary, ++ "get_session_tree": get_session_tree, ++ "get_session_topology": get_session_topology, ++ "get_logical_session": get_logical_session, ++ "get_stats_by": get_stats_by, ++ "get_messages": get_messages, ++ "raw_artifacts": raw_artifacts, ++ "readiness_check": readiness_check, ++ "archive_debt": archive_debt, ++ "explain_import": explain_import, ++ "list_read_view_profiles": list_read_view_profiles, ++ "explain_query_expression": explain_query_expression, ++ "query_completions": query_completions, ++ "action_affordances": action_affordances, ++ }, ++ ) ++ + + def register_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + register_query_tools(mcp, hooks) +diff --git a/tests/data/witnesses/mcp-tool-contracts.json b/tests/data/witnesses/mcp-tool-contracts.json +new file mode 100644 +index 0000000000000000000000000000000000000000..a5d943f62e029ced1a55bc09a41bc9f9b3f0c399 +--- /dev/null ++++ b/tests/data/witnesses/mcp-tool-contracts.json +@@ -0,0 +1,8418 @@ ++[ ++ { ++ "annotations": null, ++ "description": "Return shared query-action affordance metadata for agents.", ++ "input_schema": { ++ "properties": {}, ++ "title": "action_affordancesArguments", ++ "type": "object" ++ }, ++ "name": "action_affordances", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "action_affordancesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "mark_type": { ++ "title": "Mark Type", ++ "type": "string" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "default": "session", ++ "title": "Target Type", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "mark_type" ++ ], ++ "title": "add_markArguments", ++ "type": "object" ++ }, ++ "name": "add_mark", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "add_markOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "author_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Kind" ++ }, ++ "author_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Ref" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "tag": { ++ "title": "Tag", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "tag" ++ ], ++ "title": "add_tagArguments", ++ "type": "object" ++ }, ++ "name": "add_tag", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "add_tagOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ "input_schema": { ++ "properties": { ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "detail": { ++ "default": false, ++ "title": "Detail", ++ "type": "boolean" ++ }, ++ "fresh": { ++ "default": false, ++ "title": "Fresh", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "view": { ++ "default": "status", ++ "enum": [ ++ "status", ++ "self", ++ "work-item", ++ "conflicts", ++ "handoff" ++ ], ++ "title": "View", ++ "type": "string" ++ } ++ }, ++ "title": "agent_coordinationArguments", ++ "type": "object" ++ }, ++ "name": "agent_coordination", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "agent_coordinationOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).\n\n#1691: programmatic session analysis primitives — GROUP BY over session profiles.\n", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "default": "workflow_shape", ++ "title": "Group By", ++ "type": "string" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "aggregate_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "aggregate_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "aggregate_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List archive coverage from the archive.", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "default": "origin", ++ "title": "Group By", ++ "type": "string" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "archive_coverageArguments", ++ "type": "object" ++ }, ++ "name": "archive_coverage", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "archive_coverageOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return the unified archive debt list used by CLI and daemon surfaces.", ++ "input_schema": { ++ "properties": { ++ "exact_fts": { ++ "default": false, ++ "title": "Exact Fts", ++ "type": "boolean" ++ }, ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "only_actionable": { ++ "default": false, ++ "title": "Only Actionable", ++ "type": "boolean" ++ } ++ }, ++ "title": "archive_debtArguments", ++ "type": "object" ++ }, ++ "name": "archive_debt", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "archive_debtOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Read a full session by exact id or prefix.", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "archive_get_sessionArguments", ++ "type": "object" ++ }, ++ "name": "archive_get_session", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "archive_get_sessionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List sessions from the split archive store.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "archive_list_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "archive_list_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "archive_list_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Search session block text.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "query": { ++ "title": "Query", ++ "type": "string" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "required": [ ++ "query" ++ ], ++ "title": "archive_search_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "archive_search_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "archive_search_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List persistent agent blackboard notes, newest first (#1697).\n\nFilter by ``kind`` (finding/blocker/decision/handoff/question/observation),\nby ``scope_repo``, or set ``unresolved=True`` for open blockers/questions.\n", ++ "input_schema": { ++ "properties": { ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "scope_repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Repo" ++ }, ++ "unresolved": { ++ "default": false, ++ "title": "Unresolved", ++ "type": "boolean" ++ } ++ }, ++ "title": "blackboard_listArguments", ++ "type": "object" ++ }, ++ "name": "blackboard_list", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "blackboard_listOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Post a note to the persistent agent blackboard (#1697).\n\n``kind`` must be one of finding/blocker/decision/handoff/question/\nobservation. Optional scope fields (repo/session/issue/path) and\nrelated session ids are recorded with the note. Assertion metadata is\nmirrored into the unified assertion row for agent-written notes.\n", ++ "input_schema": { ++ "properties": { ++ "author_kind": { ++ "default": "agent", ++ "title": "Author Kind", ++ "type": "string" ++ }, ++ "author_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Ref" ++ }, ++ "content": { ++ "title": "Content", ++ "type": "string" ++ }, ++ "context_policy": { ++ "anyOf": [ ++ { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Context Policy" ++ }, ++ "evidence_refs": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Evidence Refs" ++ }, ++ "kind": { ++ "title": "Kind", ++ "type": "string" ++ }, ++ "related_sessions": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Related Sessions" ++ }, ++ "scope_issue": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Issue" ++ }, ++ "scope_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Path" ++ }, ++ "scope_repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Repo" ++ }, ++ "scope_session": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Session" ++ }, ++ "staleness": { ++ "anyOf": [ ++ { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Staleness" ++ }, ++ "title": { ++ "title": "Title", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "kind", ++ "title", ++ "content" ++ ], ++ "title": "blackboard_postArguments", ++ "type": "object" ++ }, ++ "name": "blackboard_post", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "blackboard_postOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Build a bounded context image for agent analysis.\n\nSelects sessions through the query algebra (filters / free-text query)\nand compiles the requested views into the shared ``ContextImage`` payload\nwith token-budgeted accumulation and explicit omission accounting.\n\nParameters:\n project_path: Filter sessions by cwd prefix pattern.\n project_repo: Filter sessions by git repo URL or name.\n since: Start date for session filter (ISO format).\n until: End date for session filter (ISO format).\n origin: Source-origin filter.\n query: Free-text query for semantic narrowing.\n max_sessions: Maximum sessions to include (1-20).\n max_tokens: Optional token budget; segments over budget are omitted\n with reason 'budget' instead of being silently truncated.\n detail_level: 'summary' (metadata only), 'compact'/'full' (messages).\n redact_paths: Redact filesystem paths for privacy (default True).\n include_assertions: Include context-inject assertion claims.\n", ++ "input_schema": { ++ "properties": { ++ "detail_level": { ++ "default": "compact", ++ "title": "Detail Level", ++ "type": "string" ++ }, ++ "include_assertions": { ++ "default": true, ++ "title": "Include Assertions", ++ "type": "boolean" ++ }, ++ "max_sessions": { ++ "default": 5, ++ "title": "Max Sessions", ++ "type": "integer" ++ }, ++ "max_tokens": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Tokens" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project Path" ++ }, ++ "project_repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project Repo" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "redact_paths": { ++ "default": true, ++ "title": "Redact Paths", ++ "type": "boolean" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "build_context_imageArguments", ++ "type": "object" ++ }, ++ "name": "build_context_image", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "build_context_imageOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_ids": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Session Ids", ++ "type": "array" ++ }, ++ "tags": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Tags", ++ "type": "array" ++ } ++ }, ++ "required": [ ++ "session_ids", ++ "tags" ++ ], ++ "title": "bulk_tag_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "bulk_tag_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "bulk_tag_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Capture a terminal-equivalent assertion candidate for later judgment.", ++ "input_schema": { ++ "properties": { ++ "author_ref": { ++ "title": "Author Ref", ++ "type": "string" ++ }, ++ "body_text": { ++ "title": "Body Text", ++ "type": "string" ++ }, ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "kind": { ++ "default": "note", ++ "title": "Kind", ++ "type": "string" ++ }, ++ "refs": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Refs" ++ }, ++ "scope_refs": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Refs" ++ } ++ }, ++ "required": [ ++ "body_text", ++ "author_ref" ++ ], ++ "title": "capture_assertion_candidateArguments", ++ "type": "object" ++ }, ++ "name": "capture_assertion_candidate", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "capture_assertion_candidateOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Delete one or all corrections for a session.", ++ "input_schema": { ++ "properties": { ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "clear_correctionsArguments", ++ "type": "object" ++ }, ++ "name": "clear_corrections", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "clear_correctionsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Compare multiple session profiles side-by-side.\n\nTakes a comma-separated list of 2-10 session IDs, fetches\ntheir merged-tier profiles, and returns a side-by-side comparison\nwith highlighted differences.\n\n#1691: programmatic session analysis primitives.\n", ++ "input_schema": { ++ "properties": { ++ "session_ids": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Ids" ++ } ++ }, ++ "title": "compare_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "compare_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "compare_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Compile a bounded context image from query/ref seeds and read views.", ++ "input_schema": { ++ "properties": { ++ "include_assertions": { ++ "default": true, ++ "title": "Include Assertions", ++ "type": "boolean" ++ }, ++ "include_candidates": { ++ "default": false, ++ "title": "Include Candidates", ++ "type": "boolean" ++ }, ++ "max_tokens": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Tokens" ++ }, ++ "read_views": { ++ "default": "messages", ++ "title": "Read Views", ++ "type": "string" ++ }, ++ "seed_query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Seed Query" ++ }, ++ "seed_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Seed Ref" ++ } ++ }, ++ "title": "compile_contextArguments", ++ "type": "object" ++ }, ++ "name": "compile_context", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "compile_contextOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Compose a ContextPreamble for SessionStart context injection (#1494).\n\nBuilds a typed preamble with session lineage, recent related sessions,\nand project state for Claude Code and Codex sessions. Designed for\nSessionStart hook scripts that inject context into new agent sessions.\nWhen the harness has allocated the new session identity, pass it as\n``successor_session_id`` so the call-log row and resulting preamble are\nqueryable against the session that received the context.\n", ++ "input_schema": { ++ "properties": { ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "limit": { ++ "default": 5, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "recent_files": { ++ "default": [], ++ "items": { ++ "type": "string" ++ }, ++ "title": "Recent Files", ++ "type": "array" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, ++ "successor_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Successor Session Id" ++ } ++ }, ++ "title": "compose_context_preambleArguments", ++ "type": "object" ++ }, ++ "name": "compose_context_preamble", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "compose_context_preambleOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Link a session to git commits and GitHub issue/PR references (#1690).\n\nReturns git commits likely produced by the session (via time-window\nanalysis and file-overlap scoring), plus GitHub issue/PR references\nextracted from session message text.\n", ++ "input_schema": { ++ "properties": { ++ "confidence_threshold": { ++ "default": 0.3, ++ "title": "Confidence Threshold", ++ "type": "number" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "since_hours": { ++ "default": 2, ++ "title": "Since Hours", ++ "type": "integer" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "correlate_sessionArguments", ++ "type": "object" ++ }, ++ "name": "correlate_session", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "correlate_sessionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Compute Pearson correlation between two numeric session metrics.\n\nFetches all session profiles matching the optional filters, extracts\nthe two named metrics, and returns the Pearson r with interpretation.\n\nSupported metric names from session profiles:\nmessage_count, word_count, tool_use_count, thinking_count,\nengaged_duration_ms, tool_active_duration_ms, wall_duration_ms,\ntotal_cost_usd, total_duration_ms, substantive_count.\n\n#1691: programmatic session analysis primitives.\n", ++ "input_schema": { ++ "properties": { ++ "metric_x": { ++ "title": "Metric X", ++ "type": "string" ++ }, ++ "metric_y": { ++ "title": "Metric Y", ++ "type": "string" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "required": [ ++ "metric_x", ++ "metric_y" ++ ], ++ "title": "correlate_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "correlate_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "correlate_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Project the current billing cycle for a subscription plan (#1138).\n\nReturns the typed :class:`polylogue.cost.outlook.CycleOutlook`\npayload from #1137: cycle window, burn rate, projected total,\nquota pressure, overage rows, coverage ratio, and confidence.\n\nParameters\n----------\nplan:\n Subscription plan name (e.g. ``\"claude-pro\"``,\n ``\"claude-max-5x\"``, ``\"chatgpt-plus\"``). User-supplied rows\n from ``[[cost.subscription.plans]]`` are merged with the\n curated seed; user rows always win.\nmethod:\n Projection method tag — one of ``\"linear\"``,\n ``\"trailing-7d-mean\"``, ``\"eom-naive\"``. The chosen method\n is echoed in the response so callers cannot lose track of\n how the projection was made.\n\nReturns the JSON-serialized outlook on success, or a typed\nerror envelope when the plan is unknown or has no cycle anchor.\n", ++ "input_schema": { ++ "properties": { ++ "method": { ++ "default": "linear", ++ "title": "Method", ++ "type": "string" ++ }, ++ "plan": { ++ "title": "Plan", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "plan" ++ ], ++ "title": "cost_outlookArguments", ++ "type": "object" ++ }, ++ "name": "cost_outlook", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "cost_outlookOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List cost rollups from the archive.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "model": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Model" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "cost_rollupsArguments", ++ "type": "object" ++ }, ++ "name": "cost_rollups", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "cost_rollupsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "annotation_id": { ++ "title": "Annotation Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "annotation_id" ++ ], ++ "title": "delete_annotationArguments", ++ "type": "object" ++ }, ++ "name": "delete_annotation", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_annotationOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "key": { ++ "title": "Key", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "key" ++ ], ++ "title": "delete_metadataArguments", ++ "type": "object" ++ }, ++ "name": "delete_metadata", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_metadataOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "pack_id": { ++ "title": "Pack Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "pack_id" ++ ], ++ "title": "delete_recall_packArguments", ++ "type": "object" ++ }, ++ "name": "delete_recall_pack", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_recall_packOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "view_id": { ++ "title": "View Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "view_id" ++ ], ++ "title": "delete_saved_viewArguments", ++ "type": "object" ++ }, ++ "name": "delete_saved_view", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_saved_viewOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "confirm": { ++ "default": false, ++ "title": "Confirm", ++ "type": "boolean" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "delete_sessionArguments", ++ "type": "object" ++ }, ++ "name": "delete_session", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_sessionOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "workspace_id": { ++ "title": "Workspace Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "workspace_id" ++ ], ++ "title": "delete_workspaceArguments", ++ "type": "object" ++ }, ++ "name": "delete_workspace", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "delete_workspaceOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "max_cost_usd": { ++ "anyOf": [ ++ { ++ "type": "number" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Cost Usd" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_sessions": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Sessions" ++ }, ++ "rebuild": { ++ "default": false, ++ "title": "Rebuild", ++ "type": "boolean" ++ } ++ }, ++ "title": "embedding_preflightArguments", ++ "type": "object" ++ }, ++ "name": "embedding_preflight", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "embedding_preflightOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "detail": { ++ "default": false, ++ "title": "Detail", ++ "type": "boolean" ++ } ++ }, ++ "title": "embedding_statusArguments", ++ "type": "object" ++ }, ++ "name": "embedding_status", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "embedding_statusOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Explain import detection/parsing from a local path or archived raw evidence.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 100, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Path" ++ }, ++ "raw_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Raw Ref" ++ }, ++ "redact_paths": { ++ "default": true, ++ "title": "Redact Paths", ++ "type": "boolean" ++ }, ++ "source_name": { ++ "default": "unknown", ++ "title": "Source Name", ++ "type": "string" ++ }, ++ "source_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Path" ++ } ++ }, ++ "title": "explain_importArguments", ++ "type": "object" ++ }, ++ "name": "explain_import", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "explain_importOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Explain query DSL parsing and lowering through the shared grammar.", ++ "input_schema": { ++ "properties": { ++ "expression": { ++ "title": "Expression", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "expression" ++ ], ++ "title": "explain_query_expressionArguments", ++ "type": "object" ++ }, ++ "name": "explain_query_expression", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "explain_query_expressionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Compute scoped and global facet aggregates over the archive. Filter parameters mirror ``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries ``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency weights for each value.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "contains": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Contains" ++ }, ++ "conv_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Conv Id" ++ }, ++ "cursor": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cursor" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Text" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "include_affordances": { ++ "default": false, ++ "title": "Include Affordances", ++ "type": "boolean" ++ }, ++ "latest": { ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "retrieval_lane": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Retrieval Lane" ++ }, ++ "reverse": { ++ "default": false, ++ "title": "Reverse", ++ "type": "boolean" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "similar_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Session Id" ++ }, ++ "similar_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Text" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "since_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Session Id" ++ }, ++ "sort": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sort" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "facetsArguments", ++ "type": "object" ++ }, ++ "name": "facets", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "facetsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Find sessions whose terminal state indicates dangling work.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "min_severity": { ++ "default": "question_left", ++ "title": "Min Severity", ++ "type": "string" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ } ++ }, ++ "title": "find_abandoned_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "find_abandoned_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "find_abandoned_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Rank logical sessions likely to match the operator's current context.", ++ "input_schema": { ++ "properties": { ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "limit": { ++ "default": 10, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "recent_files": { ++ "default": [], ++ "items": { ++ "type": "string" ++ }, ++ "title": "Recent Files", ++ "type": "array" ++ }, ++ "repo_path": { ++ "title": "Repo Path", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "repo_path" ++ ], ++ "title": "find_resume_candidatesArguments", ++ "type": "object" ++ }, ++ "name": "find_resume_candidates", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "find_resume_candidatesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Find sessions similar to a given session.\n\nIf embeddings are enabled, delegates to neighbor_candidates.\nOtherwise falls back to metadata similarity: same workflow_shape,\nsame origin, similar time window, overlapping tags.\n\n#1691: programmatic session analysis primitives.\n", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 10, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "similarity_dimension": { ++ "default": "auto", ++ "title": "Similarity Dimension", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "find_similar_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "find_similar_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "find_similar_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Find sessions with origin tool calls bounded as stuck.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ } ++ }, ++ "title": "find_stuck_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "find_stuck_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "find_stuck_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Read the exact persisted context image for its recorded recipient.\n\nThe caller must supply the recipient object ref recorded at delivery.\nA mismatch is intentionally indistinguishable from a missing receipt.\nThis tool audits an existing delivery; it never creates a context pack\nor schedules automatic recall.\n", ++ "input_schema": { ++ "properties": { ++ "recipient_ref": { ++ "title": "Recipient Ref", ++ "type": "string" ++ }, ++ "snapshot_ref": { ++ "title": "Snapshot Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "snapshot_ref", ++ "recipient_ref" ++ ], ++ "title": "get_context_deliveryArguments", ++ "type": "object" ++ }, ++ "name": "get_context_delivery", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_context_deliveryOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return compact logical-session lineage for read-pull consumers.", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_logical_sessionArguments", ++ "type": "object" ++ }, ++ "name": "get_logical_session", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_logical_sessionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return a filtered message page.\n\n``offset`` is evaluated after role/type/material filters. Use\n``tail=True`` or ``offset_from=\"end\"`` to read the filtered tail of a\nlarge session without first calculating the filtered total. Set\n``max_chars_per_message`` to cap each returned body; ``excerpt=True``\npreserves both its head and tail, or a window around the first term in\n``match_query`` when that query occurs in the message.\n", ++ "input_schema": { ++ "properties": { ++ "excerpt": { ++ "default": false, ++ "title": "Excerpt", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "match_query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Match Query" ++ }, ++ "material_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Material Origin" ++ }, ++ "max_chars_per_message": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Message" ++ }, ++ "message_role": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Role" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "offset_from": { ++ "default": "start", ++ "title": "Offset From", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "tail": { ++ "default": false, ++ "title": "Tail", ++ "type": "boolean" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_messagesArguments", ++ "type": "object" ++ }, ++ "name": "get_messages", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_messagesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_metadataArguments", ++ "type": "object" ++ }, ++ "name": "get_metadata", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_metadataOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Agent-workflow pathology distribution over a matched scope (#2383).\n\nRead-shaped delegate to :meth:`Polylogue.pathology_report`: runs the\ndeterministic detectors (wasted-loop, missed-review, stale-context) over\nthe matched sessions' run projections and returns the typed\n:class:`polylogue.insights.pathology.PathologyReport` — findings with\nevidence refs plus the per-kind distribution. Deterministic and\nrule-based (no LLM-as-judge); a rebuild over identical evidence is\nidentical.\n", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Limit" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "get_pathologiesArguments", ++ "type": "object" ++ }, ++ "name": "get_pathologies", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_pathologiesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Distilled postmortem bundle over a matched session scope (#2380).\n\nRead-shaped delegate to :meth:`Polylogue.postmortem_bundle`: resolves\nthe matched session set from the filter scope, aggregates per-session\nsession digests, and returns the typed\n:class:`polylogue.insights.postmortem.PostmortemBundle` (top sessions\nby cost, repos touched, tool/work-kind rollups, failure-mode signals).\nThe analysis cap defaults to 200 sessions; a larger match marks the\nbundle ``truncated`` rather than silently capping.\n\nThis is the agent-preferred distilled read over the matched scope.\n", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Limit" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "get_postmortem_bundleArguments", ++ "type": "object" ++ }, ++ "name": "get_postmortem_bundle", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_postmortem_bundleOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Get a typed resume brief for one archived session.\n\nThe brief composes already-materialized session insights (profile,\nenrichment, work events, phases, work thread) into a handoff\npayload. Provenance fields cite the session, message, work-event,\nand phase IDs that contributed.\n", ++ "input_schema": { ++ "properties": { ++ "related_limit": { ++ "default": 6, ++ "title": "Related Limit", ++ "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_resume_briefArguments", ++ "type": "object" ++ }, ++ "name": "get_resume_brief", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_resume_briefOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "id": { ++ "title": "Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "id" ++ ], ++ "title": "get_session_summaryArguments", ++ "type": "object" ++ }, ++ "name": "get_session_summary", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_session_summaryOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return typed lineage topology (ancestors/descendants/siblings/thread).\n\nReturns the resolved :class:`SessionTopology` graph (#1261 /\n#866 slice D) plus pre-projected ref lists so external agents can\nnavigate session lineage without re-walking parent pointers.\n", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_session_topologyArguments", ++ "type": "object" ++ }, ++ "name": "get_session_topology", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_session_topologyOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_session_treeArguments", ++ "type": "object" ++ }, ++ "name": "get_session_tree", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_session_treeOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Session counts grouped by a calendar/origin dimension.\n\nThese are the sessions-table count dimensions. The CLI ``stats\n--by`` surface additionally supports ``action``/``tool``/``repo``/\n``work-kind``, which are insight-summary aggregations over a different\ncode path, not plain session counts (#1749).\n", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "default": "origin", ++ "enum": [ ++ "origin", ++ "day", ++ "month", ++ "year" ++ ], ++ "title": "Group By", ++ "type": "string" ++ } ++ }, ++ "title": "get_stats_byArguments", ++ "type": "object" ++ }, ++ "name": "get_stats_by", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "get_stats_byOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Import bounded JSONL labels as provenance-stamped candidates.", ++ "input_schema": { ++ "properties": { ++ "actor_ref": { ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "batch_id": { ++ "title": "Batch Id", ++ "type": "string" ++ }, ++ "jsonl": { ++ "title": "Jsonl", ++ "type": "string" ++ }, ++ "metadata": { ++ "anyOf": [ ++ { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Metadata" ++ }, ++ "model_ref": { ++ "title": "Model Ref", ++ "type": "string" ++ }, ++ "prompt_ref": { ++ "title": "Prompt Ref", ++ "type": "string" ++ }, ++ "schema_id": { ++ "title": "Schema Id", ++ "type": "string" ++ }, ++ "schema_version": { ++ "title": "Schema Version", ++ "type": "integer" ++ }, ++ "source_result_ref": { ++ "title": "Source Result Ref", ++ "type": "string" ++ }, ++ "target_ref": { ++ "title": "Target Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "jsonl", ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "target_ref", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref" ++ ], ++ "title": "import_annotation_batchArguments", ++ "type": "object" ++ }, ++ "name": "import_annotation_batch", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "import_annotation_batchOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Per-product rigor profile across materialized insights (#1275).\n\nReturns the JSON-serialized :class:`InsightRigorAuditReport`. For\neach contracted insight product, reports the share of rows that\ncarry evidence/inference/fallback markers, the stale-version row\ncount, and a confidence-bucket distribution.\n", ++ "input_schema": { ++ "properties": { ++ "sample_limit": { ++ "default": 500, ++ "title": "Sample Limit", ++ "type": "integer" ++ } ++ }, ++ "title": "insight_rigor_auditArguments", ++ "type": "object" ++ }, ++ "name": "insight_rigor_audit", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "insight_rigor_auditOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Join typed annotation labels to exact structural targets.", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Group By" ++ }, ++ "limit": { ++ "default": 500, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "schema_id": { ++ "title": "Schema Id", ++ "type": "string" ++ }, ++ "schema_version": { ++ "title": "Schema Version", ++ "type": "integer" ++ }, ++ "statuses": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Statuses", ++ "type": "array" ++ }, ++ "target_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Kind" ++ } ++ }, ++ "required": [ ++ "schema_id", ++ "schema_version", ++ "statuses" ++ ], ++ "title": "join_typed_annotationsArguments", ++ "type": "object" ++ }, ++ "name": "join_typed_annotations", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "join_typed_annotationsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Judge one candidate; caller text is provenance, not authorization.", ++ "input_schema": { ++ "properties": { ++ "actor_ref": { ++ "default": "user:local", ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "candidate_ref": { ++ "title": "Candidate Ref", ++ "type": "string" ++ }, ++ "decision": { ++ "enum": [ ++ "accept", ++ "reject", ++ "defer", ++ "supersede" ++ ], ++ "title": "Decision", ++ "type": "string" ++ }, ++ "inject": { ++ "default": false, ++ "title": "Inject", ++ "type": "boolean" ++ }, ++ "reason": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Reason" ++ }, ++ "replacement_body_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Body Text" ++ }, ++ "replacement_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Kind" ++ }, ++ "replacement_value": { ++ "anyOf": [ ++ {}, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Value" ++ } ++ }, ++ "required": [ ++ "candidate_ref", ++ "decision" ++ ], ++ "title": "judge_assertion_candidateArguments", ++ "type": "object" ++ }, ++ "name": "judge_assertion_candidate", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "judge_assertion_candidateOutput", ++ "type": "object" ++ }, ++ "required_role": "review" ++ }, ++ { ++ "annotations": null, ++ "description": "Bulk judge candidates with independently reported partial success.", ++ "input_schema": { ++ "properties": { ++ "actor_ref": { ++ "default": "user:local", ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "items": { ++ "items": { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ "title": "Items", ++ "type": "array" ++ } ++ }, ++ "required": [ ++ "items" ++ ], ++ "title": "judge_assertion_candidatesArguments", ++ "type": "object" ++ }, ++ "name": "judge_assertion_candidates", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "judge_assertion_candidatesOutput", ++ "type": "object" ++ }, ++ "required_role": "review" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Type" ++ } ++ }, ++ "title": "list_annotationsArguments", ++ "type": "object" ++ }, ++ "name": "list_annotations", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_annotationsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "List durable candidate review history separately from active claims.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_candidate_reviewsArguments", ++ "type": "object" ++ }, ++ "name": "list_assertion_candidate_reviews", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_assertion_candidate_reviewsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List pending assertion candidates without promoting them.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_candidatesArguments", ++ "type": "object" ++ }, ++ "name": "list_assertion_candidates", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_assertion_candidatesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List assertion-backed lifecycle claims.", ++ "input_schema": { ++ "properties": { ++ "context_inject": { ++ "anyOf": [ ++ { ++ "type": "boolean" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Context Inject" ++ }, ++ "kinds": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kinds" ++ }, ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "scope_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Ref" ++ }, ++ "statuses": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": "active,candidate", ++ "title": "Statuses" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_claimsArguments", ++ "type": "object" ++ }, ++ "name": "list_assertion_claims", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_assertion_claimsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List stored learning corrections.", ++ "input_schema": { ++ "properties": { ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_correction": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Correction" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ } ++ }, ++ "title": "list_correctionsArguments", ++ "type": "object" ++ }, ++ "name": "list_corrections", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_correctionsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "mark_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Mark Type" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Type" ++ } ++ }, ++ "title": "list_marksArguments", ++ "type": "object" ++ }, ++ "name": "list_marks", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_marksOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "List executable read-view profile metadata for agents.", ++ "input_schema": { ++ "properties": {}, ++ "title": "list_read_view_profilesArguments", ++ "type": "object" ++ }, ++ "name": "list_read_view_profiles", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_read_view_profilesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_recall_packsArguments", ++ "type": "object" ++ }, ++ "name": "list_recall_packs", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_recall_packsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_saved_viewsArguments", ++ "type": "object" ++ }, ++ "name": "list_saved_views", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_saved_viewsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "contains": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Contains" ++ }, ++ "conv_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Conv Id" ++ }, ++ "cursor": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cursor" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Text" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "latest": { ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "retrieval_lane": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Retrieval Lane" ++ }, ++ "reverse": { ++ "default": false, ++ "title": "Reverse", ++ "type": "boolean" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "similar_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Session Id" ++ }, ++ "similar_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Text" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "since_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Session Id" ++ }, ++ "sort": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sort" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "list_sessionsArguments", ++ "type": "object" ++ }, ++ "name": "list_sessions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_sessionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List deterministic tag-count pages without expanding a large tag map.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ } ++ }, ++ "title": "list_tagsArguments", ++ "type": "object" ++ }, ++ "name": "list_tags", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_tagsOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_workspacesArguments", ++ "type": "object" ++ }, ++ "name": "list_workspaces", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "list_workspacesOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Run (or dry-run) maintenance backfill targets.\n\nReturns the shared :class:`MaintenanceOperationEnvelope` with\n``mode=\"execute\"``. Per-target failures are isolated and\nreported through the bounded ``failure_samples`` envelope. The\nscope-filter parameters mirror the CLI flags on\n``polylogue ops maintenance run``.\n", ++ "input_schema": { ++ "properties": { ++ "dry_run": { ++ "default": false, ++ "title": "Dry Run", ++ "type": "boolean" ++ }, ++ "failure_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Failure Kind" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "parser_version": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Parser Version" ++ }, ++ "session_ids": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Ids" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "source_family": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Family" ++ }, ++ "source_root": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Root" ++ }, ++ "targets": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Targets" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "maintenance_executeArguments", ++ "type": "object" ++ }, ++ "name": "maintenance_execute", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "maintenance_executeOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "List every persisted maintenance operation snapshot (#1197).\n\nReturns a bounded envelope ``{\"items\": [...], \"total\": N}`` where\neach item is the same shape as :func:`maintenance_status`. Use\nwhen the operator (or another agent) needs to see in-flight and\nrecently failed operations without knowing their ids.\n", ++ "input_schema": { ++ "properties": {}, ++ "title": "maintenance_listArguments", ++ "type": "object" ++ }, ++ "name": "maintenance_list", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "maintenance_listOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "Dry-run summary of maintenance backfill targets.\n\nReturns the shared :class:`MaintenanceOperationEnvelope` so the\nresult shape is byte-for-byte identical to the CLI ``polylogue\nmaintenance plan --output-format json`` and the daemon HTTP\n``POST /api/maintenance/plan`` responses. The scope-filter\nparameters mirror the CLI ``--session-id``, ``--origin``,\n``--source-family``, ``--source-root``, ``--since``/``--until``,\n``--failure-kind``, and ``--parser-version`` flags.\n", ++ "input_schema": { ++ "properties": { ++ "failure_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Failure Kind" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "parser_version": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Parser Version" ++ }, ++ "session_ids": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Ids" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "source_family": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Family" ++ }, ++ "source_root": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Root" ++ }, ++ "targets": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Targets" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "maintenance_previewArguments", ++ "type": "object" ++ }, ++ "name": "maintenance_preview", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "maintenance_previewOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "Return one persisted maintenance operation snapshot by id (#1197).\n\nWraps the shared\n:class:`~polylogue.maintenance.envelope.MaintenanceOperationEnvelope`\nwith the state-file ``updated_at`` and ``state_path`` so the\nresult is byte-shape-identical to the daemon HTTP\n``GET /api/maintenance/status/`` response.\n", ++ "input_schema": { ++ "properties": { ++ "operation_id": { ++ "title": "Operation Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "operation_id" ++ ], ++ "title": "maintenance_statusArguments", ++ "type": "object" ++ }, ++ "name": "maintenance_status", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "maintenance_statusOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight convergence.", ++ "input_schema": { ++ "properties": { ++ "source_path": { ++ "title": "Source Path", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "source_path" ++ ], ++ "title": "named_source_freshnessArguments", ++ "type": "object" ++ }, ++ "name": "named_source_freshness", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "named_source_freshnessOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Id" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "window_hours": { ++ "default": 24, ++ "title": "Window Hours", ++ "type": "integer" ++ } ++ }, ++ "title": "neighbor_candidatesArguments", ++ "type": "object" ++ }, ++ "name": "neighbor_candidates", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "neighbor_candidatesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return provider usage accounting diagnostics without estimating billing cost.", ++ "input_schema": { ++ "properties": { ++ "detail": { ++ "default": "full", ++ "title": "Detail", ++ "type": "string" ++ }, ++ "limit": { ++ "default": 25, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ } ++ }, ++ "title": "provider_usageArguments", ++ "type": "object" ++ }, ++ "name": "provider_usage", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "provider_usageOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return shared query/action completion metadata for non-shell clients.", ++ "input_schema": { ++ "properties": { ++ "field": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Field" ++ }, ++ "incomplete": { ++ "default": "", ++ "title": "Incomplete", ++ "type": "string" ++ }, ++ "kind": { ++ "title": "Kind", ++ "type": "string" ++ }, ++ "unit": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Unit" ++ } ++ }, ++ "required": [ ++ "kind" ++ ], ++ "title": "query_completionsArguments", ++ "type": "object" ++ }, ++ "name": "query_completions", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "query_completionsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Return terminal rows for explicit unit-source query expressions.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "expression": { ++ "title": "Expression", ++ "type": "string" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "required": [ ++ "expression" ++ ], ++ "title": "query_unitsArguments", ++ "type": "object" ++ }, ++ "name": "query_units", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "query_unitsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "raw_artifactsArguments", ++ "type": "object" ++ }, ++ "name": "raw_artifacts", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "raw_artifactsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": {}, ++ "title": "readiness_checkArguments", ++ "type": "object" ++ }, ++ "name": "readiness_check", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "readiness_checkOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": {}, ++ "title": "rebuild_indexArguments", ++ "type": "object" ++ }, ++ "name": "rebuild_index", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "rebuild_indexOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_ids": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Ids" ++ } ++ }, ++ "title": "rebuild_session_insightsArguments", ++ "type": "object" ++ }, ++ "name": "rebuild_session_insights", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "rebuild_session_insightsOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "Record a user correction targeting a derived insight.", ++ "input_schema": { ++ "properties": { ++ "author_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Kind" ++ }, ++ "author_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Ref" ++ }, ++ "kind": { ++ "title": "Kind", ++ "type": "string" ++ }, ++ "note": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Note" ++ }, ++ "payload": { ++ "additionalProperties": { ++ "type": "string" ++ }, ++ "title": "Payload", ++ "type": "object" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "kind", ++ "payload" ++ ], ++ "title": "record_correctionArguments", ++ "type": "object" ++ }, ++ "name": "record_correction", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "record_correctionOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "mark_type": { ++ "title": "Mark Type", ++ "type": "string" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "default": "session", ++ "title": "Target Type", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "mark_type" ++ ], ++ "title": "remove_markArguments", ++ "type": "object" ++ }, ++ "name": "remove_mark", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "remove_markOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "tag": { ++ "title": "Tag", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "tag" ++ ], ++ "title": "remove_tagArguments", ++ "type": "object" ++ }, ++ "name": "remove_tag", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "remove_tagOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Resolve a public object/evidence ref into a bounded payload.", ++ "input_schema": { ++ "properties": { ++ "ref": { ++ "title": "Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "ref" ++ ], ++ "title": "resolve_refArguments", ++ "type": "object" ++ }, ++ "name": "resolve_ref", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "resolve_refOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "annotation_id": { ++ "title": "Annotation Id", ++ "type": "string" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "note_text": { ++ "title": "Note Text", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "default": "session", ++ "title": "Target Type", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "annotation_id", ++ "session_id", ++ "note_text" ++ ], ++ "title": "save_annotationArguments", ++ "type": "object" ++ }, ++ "name": "save_annotation", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "save_annotationOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "label": { ++ "title": "Label", ++ "type": "string" ++ }, ++ "pack_id": { ++ "title": "Pack Id", ++ "type": "string" ++ }, ++ "payload_json": { ++ "default": "{}", ++ "title": "Payload Json", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "pack_id", ++ "label" ++ ], ++ "title": "save_recall_packArguments", ++ "type": "object" ++ }, ++ "name": "save_recall_pack", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "save_recall_packOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "name": { ++ "title": "Name", ++ "type": "string" ++ }, ++ "query_json": { ++ "title": "Query Json", ++ "type": "string" ++ }, ++ "view_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "View Id" ++ } ++ }, ++ "required": [ ++ "name", ++ "query_json" ++ ], ++ "title": "save_saved_viewArguments", ++ "type": "object" ++ }, ++ "name": "save_saved_view", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "save_saved_viewOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "active_target_json": { ++ "default": "{}", ++ "title": "Active Target Json", ++ "type": "string" ++ }, ++ "layout_json": { ++ "default": "{}", ++ "title": "Layout Json", ++ "type": "string" ++ }, ++ "mode": { ++ "default": "tabs", ++ "title": "Mode", ++ "type": "string" ++ }, ++ "name": { ++ "title": "Name", ++ "type": "string" ++ }, ++ "open_targets_json": { ++ "default": "[]", ++ "title": "Open Targets Json", ++ "type": "string" ++ }, ++ "workspace_id": { ++ "title": "Workspace Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "workspace_id", ++ "name" ++ ], ++ "title": "save_workspaceArguments", ++ "type": "object" ++ }, ++ "name": "save_workspace", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "save_workspaceOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "input_schema": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "contains": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Contains" ++ }, ++ "conv_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Conv Id" ++ }, ++ "cursor": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cursor" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Text" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "include_affordances": { ++ "default": false, ++ "title": "Include Affordances", ++ "type": "boolean" ++ }, ++ "latest": { ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "query": { ++ "title": "Query", ++ "type": "string" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "retrieval_lane": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Retrieval Lane" ++ }, ++ "reverse": { ++ "default": false, ++ "title": "Reverse", ++ "type": "boolean" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "similar_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Session Id" ++ }, ++ "similar_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Text" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "since_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Session Id" ++ }, ++ "sort": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sort" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "required": [ ++ "query" ++ ], ++ "title": "searchArguments", ++ "type": "object" ++ }, ++ "name": "search", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "searchOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List session costs from the archive.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "model": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Model" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "status": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Status" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "session_costsArguments", ++ "type": "object" ++ }, ++ "name": "session_costs", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_costsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Get per-session latency profile by session ID.", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "session_latency_profileArguments", ++ "type": "object" ++ }, ++ "name": "session_latency_profile", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_latency_profileOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List session phases from the archive.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "session_date_since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Since" ++ }, ++ "session_date_until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Until" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "session_phasesArguments", ++ "type": "object" ++ }, ++ "name": "session_phases", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_phasesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Get a single session profile by session ID.", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "tier": { ++ "default": "merged", ++ "title": "Tier", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "session_profileArguments", ++ "type": "object" ++ }, ++ "name": "session_profile", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_profileOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List session profiles from the archive.", ++ "input_schema": { ++ "properties": { ++ "first_message_since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "First Message Since" ++ }, ++ "first_message_until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "First Message Until" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_wallclock_seconds": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Wallclock Seconds" ++ }, ++ "min_wallclock_seconds": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Wallclock Seconds" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "session_date_since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Since" ++ }, ++ "session_date_until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Until" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "sort": { ++ "default": "source", ++ "title": "Sort", ++ "type": "string" ++ }, ++ "terminal_state": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Terminal State" ++ }, ++ "tier": { ++ "default": "merged", ++ "title": "Tier", ++ "type": "string" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ }, ++ "workflow_shape": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Workflow Shape" ++ } ++ }, ++ "title": "session_profilesArguments", ++ "type": "object" ++ }, ++ "name": "session_profiles", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_profilesOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List session tag rollups from the archive.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 100, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "session_tag_rollupsArguments", ++ "type": "object" ++ }, ++ "name": "session_tag_rollups", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_tag_rollupsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Get per-tool timing breakdown with evidence provenance.\n\nReturns per-tool timing from OTLP spans when available, falling back\nto message-gap estimates. Each timing entry carries an\n``evidence_source`` field: ``\"otlp_span\"`` for exact wallclock timing\nfrom instrumented tool calls, or ``\"message_gap_estimate\"`` for timing\ninferred from inter-message gaps.\n", ++ "input_schema": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "session_tool_timingArguments", ++ "type": "object" ++ }, ++ "name": "session_tool_timing", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_tool_timingOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List work events from the archive.", ++ "input_schema": { ++ "properties": { ++ "heuristic_label": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Heuristic Label" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "session_date_since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Since" ++ }, ++ "session_date_until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Date Until" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "session_work_eventsArguments", ++ "type": "object" ++ }, ++ "name": "session_work_events", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "session_work_eventsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "key": { ++ "title": "Key", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "value": { ++ "title": "Value", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "key", ++ "value" ++ ], ++ "title": "set_metadataArguments", ++ "type": "object" ++ }, ++ "name": "set_metadata", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "set_metadataOutput", ++ "type": "object" ++ }, ++ "required_role": "write" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": {}, ++ "title": "statsArguments", ++ "type": "object" ++ }, ++ "name": "stats", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "statsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List work threads from the archive.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "threadsArguments", ++ "type": "object" ++ }, ++ "name": "threads", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "threadsOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Distribution of materialized per-session tool-call latency.", ++ "input_schema": { ++ "properties": { ++ "limit": { ++ "default": 500, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tool_category": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool Category" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "tool_call_latency_distributionArguments", ++ "type": "object" ++ }, ++ "name": "tool_call_latency_distribution", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "tool_call_latency_distributionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "List tool usage from the archive.", ++ "input_schema": { ++ "properties": { ++ "action_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Kind" ++ }, ++ "limit": { ++ "default": 200, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "mcp_server": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Mcp Server" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since_ms": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Ms" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ } ++ }, ++ "title": "tool_usageArguments", ++ "type": "object" ++ }, ++ "name": "tool_usage", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "tool_usageOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "", ++ "input_schema": { ++ "properties": { ++ "session_ids": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Session Ids", ++ "type": "array" ++ } ++ }, ++ "required": [ ++ "session_ids" ++ ], ++ "title": "update_indexArguments", ++ "type": "object" ++ }, ++ "name": "update_index", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "update_indexOutput", ++ "type": "object" ++ }, ++ "required_role": "admin" ++ }, ++ { ++ "annotations": null, ++ "description": "List usage timeline from the archive.", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "default": "month-origin-model", ++ "title": "Group By", ++ "type": "string" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "model": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Model" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "usage_timelineArguments", ++ "type": "object" ++ }, ++ "name": "usage_timeline", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "usage_timelineOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ }, ++ { ++ "annotations": null, ++ "description": "Histogram session workflow shapes by week, origin, or project.", ++ "input_schema": { ++ "properties": { ++ "group_by": { ++ "default": "week", ++ "title": "Group By", ++ "type": "string" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "workflow_shape_distributionArguments", ++ "type": "object" ++ }, ++ "name": "workflow_shape_distribution", ++ "output_schema": { ++ "properties": { ++ "result": { ++ "title": "Result", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "result" ++ ], ++ "title": "workflow_shape_distributionOutput", ++ "type": "object" ++ }, ++ "required_role": "read" ++ } ++] +diff --git a/tests/data/witnesses/mcp-tool-declarations.json b/tests/data/witnesses/mcp-tool-declarations.json +new file mode 100644 +index 0000000000000000000000000000000000000000..fd68f42a4c6b68157c47f73a7098139eeb95e7fa +--- /dev/null ++++ b/tests/data/witnesses/mcp-tool-declarations.json +@@ -0,0 +1,2208 @@ ++[ ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return shared query-action affordance metadata for agents.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..action_affordances", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "action_affordances", ++ "object_kind": "action-affordance", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.operations.action_contracts.action_affordance_list_payload", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..add_mark", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "add_mark", ++ "object_kind": "mark", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..add_mark", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..add_tag", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "add_tag", ++ "object_kind": "tag", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..add_tag", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return the compact shared envelope; ``fresh`` bypasses its warm cache.", ++ "family": "coordination", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..agent_coordination", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "agent_coordination", ++ "object_kind": "agent-coordination", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..agent_coordination", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Aggregate session counts by a dimension (workflow_shape, terminal_state, origin).\n\n#1691: programmatic session analysis primitives — GROUP BY over session profiles.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..aggregate_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_ids": "test:a,test:b" ++ }, ++ "name": "aggregate_sessions", ++ "object_kind": "session-profile", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List archive coverage from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.archive_coverage", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "archive_coverage", ++ "object_kind": "archive-coverage", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return the unified archive debt list used by CLI and daemon surfaces.", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..archive_debt", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "archive_debt", ++ "object_kind": "archive-debt", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.archive_debt", ++ "output_contract": "typed_envelope", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "status", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": "get_session_summary/get_messages", ++ "continuation": "none", ++ "description": "Read a full session by exact id or prefix.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..archive_get_session", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "archive_get_session", ++ "object_kind": "session", ++ "object_ref_semantics": "accepts exact id or compatibility prefix; emits canonical session id", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.read_session", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": "list_sessions", ++ "continuation": "offset", ++ "description": "List sessions from the split archive store.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..archive_list_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "archive_list_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.list_summaries", ++ "output_contract": "envelope:items,limit,offset,total", ++ "query_semantics": "compatibility ArchiveStore route; exact parameter behavior retained", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": "search", ++ "continuation": "offset", ++ "description": "Search session block text.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..archive_search_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1, ++ "query": "hello" ++ }, ++ "name": "archive_search_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.search", ++ "output_contract": "envelope:items,limit,query,total", ++ "query_semantics": "compatibility ArchiveStore route; exact parameter behavior retained", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "search" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "List persistent agent blackboard notes, newest first (#1697).\n\nFilter by ``kind`` (finding/blocker/decision/handoff/question/observation),\nby ``scope_repo``, or set ``unresolved=True`` for open blockers/questions.\n", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..blackboard_list", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "blackboard_list", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..blackboard_list", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Post a note to the persistent agent blackboard (#1697).\n\n``kind`` must be one of finding/blocker/decision/handoff/question/\nobservation. Optional scope fields (repo/session/issue/path) and\nrelated session ids are recorded with the note. Assertion metadata is\nmirrored into the unified assertion row for agent-written notes.\n", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..blackboard_post", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "blackboard_post", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..blackboard_post", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "bounded-context", ++ "description": "Build a bounded context image for agent analysis.\n\nSelects sessions through the query algebra (filters / free-text query)\nand compiles the requested views into the shared ``ContextImage`` payload\nwith token-budgeted accumulation and explicit omission accounting.\n\nParameters:\n project_path: Filter sessions by cwd prefix pattern.\n project_repo: Filter sessions by git repo URL or name.\n since: Start date for session filter (ISO format).\n until: End date for session filter (ISO format).\n origin: Source-origin filter.\n query: Free-text query for semantic narrowing.\n max_sessions: Maximum sessions to include (1-20).\n max_tokens: Optional token budget; segments over budget are omitted\n with reason 'budget' instead of being silently truncated.\n detail_level: 'summary' (metadata only), 'compact'/'full' (messages).\n redact_paths: Redact filesystem paths for privacy (default True).\n include_assertions: Include context-inject assertion claims.\n", ++ "family": "context", ++ "implementation": "polylogue.mcp.server_context_tools.register_context_tools..build_context_image", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "project_repo": "test" ++ }, ++ "name": "build_context_image", ++ "object_kind": "build-context-image", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_context_tools.register_context_tools..build_context_image", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "bounded-context", ++ "verb": "compile" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..bulk_tag_sessions", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "bulk_tag_sessions", ++ "object_kind": "tag", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..bulk_tag_sessions", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Capture a terminal-equivalent assertion candidate for later judgment.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..capture_assertion_candidate", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "capture_assertion_candidate", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..capture_assertion_candidate", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Delete one or all corrections for a session.", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..clear_corrections", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "clear_corrections", ++ "object_kind": "correction", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..clear_corrections", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Compare multiple session profiles side-by-side.\n\nTakes a comma-separated list of 2-10 session IDs, fetches\ntheir merged-tier profiles, and returns a side-by-side comparison\nwith highlighted differences.\n\n#1691: programmatic session analysis primitives.\n", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..compare_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_ids": "test:a,test:b" ++ }, ++ "name": "compare_sessions", ++ "object_kind": "session-profile", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_insight_tools.register_insight_tools..compare_sessions", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "compare" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "bounded-context", ++ "description": "Compile a bounded context image from query/ref seeds and read views.", ++ "family": "context", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..compile_context", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "read_views": "messages", ++ "seed_ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "name": "compile_context", ++ "object_kind": "compile-context", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_query_tools..compile_context", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "bounded-context", ++ "verb": "compile" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "bounded-context", ++ "description": "Compose a ContextPreamble for SessionStart context injection (#1494).\n\nBuilds a typed preamble with session lineage, recent related sessions,\nand project state for Claude Code and Codex sessions. Designed for\nSessionStart hook scripts that inject context into new agent sessions.\nWhen the harness has allocated the new session identity, pass it as\n``successor_session_id`` so the call-log row and resulting preamble are\nqueryable against the session that received the context.\n", ++ "family": "context", ++ "implementation": "polylogue.mcp.server_context_tools.register_context_tools..compose_context_preamble", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "context": "{}" ++ }, ++ "name": "compose_context_preamble", ++ "object_kind": "compose-context-preamble", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_context_tools.register_context_tools..compose_context_preamble", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "bounded-context", ++ "verb": "compile" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Link a session to git commits and GitHub issue/PR references (#1690).\n\nReturns git commits likely produced by the session (via time-window\nanalysis and file-overlap scoring), plus GitHub issue/PR references\nextracted from session message text.\n", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..correlate_session", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "correlate_session", ++ "object_kind": "session-correlation", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.mcp.server_insight_tools.register_insight_tools..correlate_session", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "correlate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Compute Pearson correlation between two numeric session metrics.\n\nFetches all session profiles matching the optional filters, extracts\nthe two named metrics, and returns the Pearson r with interpretation.\n\nSupported metric names from session profiles:\nmessage_count, word_count, tool_use_count, thinking_count,\nengaged_duration_ms, tool_active_duration_ms, wall_duration_ms,\ntotal_cost_usd, total_duration_ms, substantive_count.\n\n#1691: programmatic session analysis primitives.\n", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..correlate_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_ids": "test:a,test:b" ++ }, ++ "name": "correlate_sessions", ++ "object_kind": "session-correlation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_insight_tools.register_insight_tools..correlate_sessions", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "correlate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Project the current billing cycle for a subscription plan (#1138).\n\nReturns the typed :class:`polylogue.cost.outlook.CycleOutlook`\npayload from #1137: cycle window, burn rate, projected total,\nquota pressure, overage rows, coverage ratio, and confidence.\n\nParameters\n----------\nplan:\n Subscription plan name (e.g. ``\"claude-pro\"``,\n ``\"claude-max-5x\"``, ``\"chatgpt-plus\"``). User-supplied rows\n from ``[[cost.subscription.plans]]`` are merged with the\n curated seed; user rows always win.\nmethod:\n Projection method tag — one of ``\"linear\"``,\n ``\"trailing-7d-mean\"``, ``\"eom-naive\"``. The chosen method\n is echoed in the response so callers cannot lose track of\n how the projection was made.\n\nReturns the JSON-serialized outlook on success, or a typed\nerror envelope when the plan is unknown or has no cycle anchor.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..cost_outlook", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "plan": "claude-pro" ++ }, ++ "name": "cost_outlook", ++ "object_kind": "cost-outlook", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List cost rollups from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.cost_rollups", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "cost_rollups", ++ "object_kind": "cost-rollup", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_annotation", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_annotation", ++ "object_kind": "annotation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_annotation", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_metadata", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_metadata", ++ "object_kind": "session-metadata", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_metadata", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_recall_pack", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_recall_pack", ++ "object_kind": "recall-pack", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_recall_pack", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_saved_view", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_saved_view", ++ "object_kind": "saved-view", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_saved_view", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..delete_session", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_session", ++ "object_kind": "delete-session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..delete_session", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_workspace", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "delete_workspace", ++ "object_kind": "workspace", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..delete_workspace", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..embedding_preflight", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "embedding_preflight", ++ "object_kind": "embedding-index", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.embeddings.status_payload.embedding_preflight_payload", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "status", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..embedding_status", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "embedding_status", ++ "object_kind": "embedding-index", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.embeddings.status_payload.embedding_status_payload", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "status", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Explain import detection/parsing from a local path or archived raw evidence.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..explain_import", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1, ++ "raw_ref": "raw:discovery-nonexistent" ++ }, ++ "name": "explain_import", ++ "object_kind": "import-evidence", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.explain_import", ++ "output_contract": "typed_envelope", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "explain" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Explain query DSL parsing and lowering through the shared grammar.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..explain_query_expression", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "expression": "repo:polylogue" ++ }, ++ "name": "explain_query_expression", ++ "object_kind": "query-expression", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.explain_query_expression", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "explain" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Compute scoped and global facet aggregates over the archive. Filter parameters mirror ``MCPSessionQueryRequest`` fields; when any filter narrows the result set the response carries ``scoped_to_query=true`` with both ``scoped`` and ``global`` bucket counts plus inverse-document-frequency weights for each value.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools.._facets", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "facets", ++ "object_kind": "archive-facet", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.facets", ++ "output_contract": "single_object", ++ "query_semantics": "MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "top-k-bound", ++ "description": "Find sessions whose terminal state indicates dangling work.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..find_abandoned_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "find_abandoned_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "top-k", ++ "verb": "find" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "top-k-bound", ++ "description": "Rank logical sessions likely to match the operator's current context.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..find_resume_candidates", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "repo_path": "/tmp/nonexistent" ++ }, ++ "name": "find_resume_candidates", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:candidates,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "top-k", ++ "verb": "find" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "top-k-bound", ++ "description": "Find sessions similar to a given session.\n\nIf embeddings are enabled, delegates to neighbor_candidates.\nOtherwise falls back to metadata similarity: same workflow_shape,\nsame origin, similar time window, overlapping tags.\n\n#1691: programmatic session analysis primitives.\n", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..find_similar_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "query": "test" ++ }, ++ "name": "find_similar_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_insight_tools.register_insight_tools..find_similar_sessions", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "top-k", ++ "verb": "find" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "top-k-bound", ++ "description": "Find sessions with origin tool calls bounded as stuck.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..find_stuck_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "find_stuck_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "top-k", ++ "verb": "find" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Read the exact persisted context image for its recorded recipient.\n\nThe caller must supply the recipient object ref recorded at delivery.\nA mismatch is intentionally indistinguishable from a missing receipt.\nThis tool audits an existing delivery; it never creates a context pack\nor schedules automatic recall.\n", ++ "family": "context", ++ "implementation": "polylogue.mcp.server_context_tools.register_context_tools..get_context_delivery", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "recipient_ref": "agent:discovery", ++ "snapshot_ref": "context-snapshot:test:conv-discovery-nonexistent:explicit-recall" ++ }, ++ "name": "get_context_delivery", ++ "object_kind": "get-context-delivery", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_context_tools.register_context_tools..get_context_delivery", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "recursive-graph-unpaged", ++ "description": "Return compact logical-session lineage for read-pull consumers.", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_logical_session", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_logical_session", ++ "object_kind": "logical-session", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue.get_logical_session", ++ "output_contract": "envelope:cycle_detected,descendants,root_id,session_id,siblings,thread", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "recursive-graph", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "Return a filtered message page.\n\n``offset`` is evaluated after role/type/material filters. Use\n``tail=True`` or ``offset_from=\"end\"`` to read the filtered tail of a\nlarge session without first calculating the filtered total. Set\n``max_chars_per_message`` to cap each returned body; ``excerpt=True``\npreserves both its head and tail, or a window around the first term in\n``match_query`` when that query occurs in the message.\n", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_messages", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1, ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_messages", ++ "object_kind": "message", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.read_session", ++ "output_contract": "envelope:messages,total", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..get_metadata", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "get_metadata", ++ "object_kind": "session-metadata", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..get_metadata", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Agent-workflow pathology distribution over a matched scope (#2383).\n\nRead-shaped delegate to :meth:`Polylogue.pathology_report`: runs the\ndeterministic detectors (wasted-loop, missed-review, stale-context) over\nthe matched sessions' run projections and returns the typed\n:class:`polylogue.insights.pathology.PathologyReport` — findings with\nevidence refs plus the per-kind distribution. Deterministic and\nrule-based (no LLM-as-judge); a rebuild over identical evidence is\nidentical.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..get_pathologies", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "get_pathologies", ++ "object_kind": "pathology-report", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Distilled postmortem bundle over a matched session scope (#2380).\n\nRead-shaped delegate to :meth:`Polylogue.postmortem_bundle`: resolves\nthe matched session set from the filter scope, aggregates per-session\nsession digests, and returns the typed\n:class:`polylogue.insights.postmortem.PostmortemBundle` (top sessions\nby cost, repos touched, tool/work-kind rollups, failure-mode signals).\nThe analysis cap defaults to 200 sessions; a larger match marks the\nbundle ``truncated`` rather than silently capping.\n\nThis is the agent-preferred distilled read over the matched scope.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..get_postmortem_bundle", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "get_postmortem_bundle", ++ "object_kind": "postmortem-bundle", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Get a typed resume brief for one archived session.\n\nThe brief composes already-materialized session insights (profile,\nenrichment, work events, phases, work thread) into a handoff\npayload. Provenance fields cite the session, message, work-event,\nand phase IDs that contributed.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..get_resume_brief", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_resume_brief", ++ "object_kind": "resume-brief", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_session_summary", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_session_summary", ++ "object_kind": "session", ++ "object_ref_semantics": "accepts exact id or compatibility prefix; emits canonical session id", ++ "operation_owner": "polylogue.api.Polylogue.get_session_summary", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "recursive-graph-unpaged", ++ "description": "Return typed lineage topology (ancestors/descendants/siblings/thread).\n\nReturns the resolved :class:`SessionTopology` graph (#1261 /\n#866 slice D) plus pre-projected ref lists so external agents can\nnavigate session lineage without re-walking parent pointers.\n", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_session_topology", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_session_topology", ++ "object_kind": "session-topology", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue.get_session_topology", ++ "output_contract": "envelope:ancestors,descendants,edges,nodes,root_id,siblings,target_id,thread", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "recursive-graph", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "recursive-graph-unpaged", ++ "description": "", ++ "family": "topology", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_session_tree", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "get_session_tree", ++ "object_kind": "session-tree", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue.get_session_tree", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "recursive-graph", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Session counts grouped by a calendar/origin dimension.\n\nThese are the sessions-table count dimensions. The CLI ``stats\n--by`` surface additionally supports ``action``/``tool``/``repo``/\n``work-kind``, which are insight-summary aggregations over a different\ncode path, not plain session counts (#1749).\n", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..get_stats_by", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "group_by": "origin" ++ }, ++ "name": "get_stats_by", ++ "object_kind": "archive-statistics", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.get_stats_by", ++ "output_contract": "stats_map", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Import bounded JSONL labels as provenance-stamped candidates.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..import_annotation_batch", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "import_annotation_batch", ++ "object_kind": "annotation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..import_annotation_batch", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Per-product rigor profile across materialized insights (#1275).\n\nReturns the JSON-serialized :class:`InsightRigorAuditReport`. For\neach contracted insight product, reports the share of rows that\ncarry evidence/inference/fallback markers, the stale-version row\ncount, and a confidence-bucket distribution.\n", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..insight_rigor_audit", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "insight_rigor_audit", ++ "object_kind": "insight-rigor", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "Join typed annotation labels to exact structural targets.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..join_typed_annotations", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "limit": 1, ++ "schema_id": "discovery", ++ "schema_version": 1, ++ "statuses": [ ++ "candidate" ++ ] ++ }, ++ "name": "join_typed_annotations", ++ "object_kind": "annotation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..join_typed_annotations", ++ "output_contract": "envelope:groups,joined_count,rows,selected_annotation_count", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Judge one candidate; caller text is provenance, not authorization.", ++ "family": "review", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidate", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "judge_assertion_candidate", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidate", ++ "output_contract": "envelope:applied_count,failed_count,items", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "review", ++ "result_semantics": "mutation", ++ "verb": "judge" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Bulk judge candidates with independently reported partial success.", ++ "family": "review", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidates", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "judge_assertion_candidates", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_assertion_review_tools..judge_assertion_candidates", ++ "output_contract": "envelope:applied_count,failed_count,items", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "review", ++ "result_semantics": "mutation", ++ "verb": "judge" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_annotations", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_annotations", ++ "object_kind": "annotation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_annotations", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List durable candidate review history separately from active claims.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..list_assertion_candidate_reviews", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "list_assertion_candidate_reviews", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..list_assertion_candidate_reviews", ++ "output_contract": "envelope:items,limit,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List pending assertion candidates without promoting them.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..list_assertion_candidates", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "list_assertion_candidates", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..list_assertion_candidates", ++ "output_contract": "envelope:items,limit,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List assertion-backed lifecycle claims.", ++ "family": "assertion", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..list_assertion_claims", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": { ++ "limit": 1, ++ "target_ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "name": "list_assertion_claims", ++ "object_kind": "assertion", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_read_tools..list_assertion_claims", ++ "output_contract": "envelope:items,limit,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List stored learning corrections.", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_corrections", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_corrections", ++ "object_kind": "correction", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_corrections", ++ "output_contract": "envelope:corrections,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..list_marks", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_marks", ++ "object_kind": "mark", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..list_marks", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "List executable read-view profile metadata for agents.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..list_read_view_profiles", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "list_read_view_profiles", ++ "object_kind": "read-view-profile", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.list_read_view_profiles", ++ "output_contract": "envelope:read_views,total", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_recall_packs", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_recall_packs", ++ "object_kind": "recall-pack", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_recall_packs", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_saved_views", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_saved_views", ++ "object_kind": "saved-view", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_saved_views", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List sessions matching the supplied filters. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools.._list_sessions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "list_sessions", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.archive_support.archive_session_list_payload", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "List deterministic tag-count pages without expanding a large tag map.", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..list_tags", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_tags", ++ "object_kind": "tag", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..list_tags", ++ "output_contract": "stats_map", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_workspaces", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "list_workspaces", ++ "object_kind": "workspace", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..list_workspaces", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Run (or dry-run) maintenance backfill targets.\n\nReturns the shared :class:`MaintenanceOperationEnvelope` with\n``mode=\"execute\"``. Per-target failures are isolated and\nreported through the bounded ``failure_samples`` envelope. The\nscope-filter parameters mirror the CLI flags on\n``polylogue ops maintenance run``.\n", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_execute", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "maintenance_execute", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_execute", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "run" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List every persisted maintenance operation snapshot (#1197).\n\nReturns a bounded envelope ``{\"items\": [...], \"total\": N}`` where\neach item is the same shape as :func:`maintenance_status`. Use\nwhen the operator (or another agent) needs to see in-flight and\nrecently failed operations without knowing their ids.\n", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_list", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "maintenance_list", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_list", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "list" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Dry-run summary of maintenance backfill targets.\n\nReturns the shared :class:`MaintenanceOperationEnvelope` so the\nresult shape is byte-for-byte identical to the CLI ``polylogue\nmaintenance plan --output-format json`` and the daemon HTTP\n``POST /api/maintenance/plan`` responses. The scope-filter\nparameters mirror the CLI ``--session-id``, ``--origin``,\n``--source-family``, ``--source-root``, ``--since``/``--until``,\n``--failure-kind``, and ``--parser-version`` flags.\n", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_preview", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "maintenance_preview", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_preview", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "run" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return one persisted maintenance operation snapshot by id (#1197).\n\nWraps the shared\n:class:`~polylogue.maintenance.envelope.MaintenanceOperationEnvelope`\nwith the state-file ``updated_at`` and ``state_path`` so the\nresult is byte-shape-identical to the daemon HTTP\n``GET /api/maintenance/status/`` response.\n", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_status", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "maintenance_status", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..maintenance_status", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Trace one exact source path from filesystem/cursor evidence through raw, parse, index, FTS, and insight convergence.", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..named_source_freshness", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "source_path": "/tmp/polylogue-discovery-nonexistent" ++ }, ++ "name": "named_source_freshness", ++ "object_kind": "source-path", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.raw_authority.source_freshness", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "status", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "top-k-bound", ++ "description": "", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..neighbor_candidates", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "query": "test" ++ }, ++ "name": "neighbor_candidates", ++ "object_kind": "session-neighbor", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_tools.register_query_tools..neighbor_candidates", ++ "output_contract": "envelope:items,limit,total", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "top-k", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return provider usage accounting diagnostics without estimating billing cost.", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..provider_usage", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "provider_usage", ++ "object_kind": "provider-usage", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.provider_usage", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Return shared query/action completion metadata for non-shell clients.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..query_completions", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "incomplete": "d", ++ "kind": "field" ++ }, ++ "name": "query_completions", ++ "object_kind": "query-language", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue.query_completions", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "explain" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "Return terminal rows for explicit unit-source query expressions.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..query_units", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "expression": "messages where text:hello", ++ "limit": 1 ++ }, ++ "name": "query_units", ++ "object_kind": "query-unit", ++ "object_ref_semantics": "terminal rows retain public refs from canonical query execution", ++ "operation_owner": "polylogue.archive.query.execution_control.execute_archive_read", ++ "output_contract": "envelope:items,limit,offset,total,unit", ++ "query_semantics": "query DSL parse/lower/execute through shared QueryExecutionContext", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "query" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..raw_artifacts", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "raw_artifacts", ++ "object_kind": "raw-artifact", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue.get_raw_artifacts_for_session", ++ "output_contract": "envelope:raw_artifacts,total", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_read_tools..readiness_check", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "readiness_check", ++ "object_kind": "archive-readiness", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.readiness.get_readiness", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "status", ++ "verb": "status" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_index", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "rebuild_index", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_index", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "run" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_session_insights", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "rebuild_session_insights", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..rebuild_session_insights", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "run" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Record a user correction targeting a derived insight.", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..record_correction", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "record_correction", ++ "object_kind": "correction", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..record_correction", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_mark", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "remove_mark", ++ "object_kind": "mark", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_mark", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "mutation", ++ "implementation": "polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_tag", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "remove_tag", ++ "object_kind": "tag", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_mutation_tools.register_mutation_tools..remove_tag", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Resolve a public object/evidence ref into a bounded payload.", ++ "family": "read", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..resolve_ref", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "ref": "session:test:conv-discovery-nonexistent" ++ }, ++ "name": "resolve_ref", ++ "object_kind": "object-ref", ++ "object_ref_semantics": "accepts canonical ObjectRef string and preserves typed not-found result", ++ "operation_owner": "polylogue.api.Polylogue.resolve_ref", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "resolve" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_annotation", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "save_annotation", ++ "object_kind": "annotation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_annotation", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_recall_pack", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "save_recall_pack", ++ "object_kind": "recall-pack", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_recall_pack", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_saved_view", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "save_saved_view", ++ "object_kind": "saved-view", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_saved_view", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_workspace", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "save_workspace", ++ "object_kind": "workspace", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..save_workspace", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset-or-cursor", ++ "description": "Search the archive for sessions matching ``query``. Filter parameters mirror ``MCPSessionQueryRequest`` fields.", ++ "family": "query", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools.._search", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1, ++ "query": "hello" ++ }, ++ "name": "search", ++ "object_kind": "session", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.archive_support.archive_search_payload", ++ "output_contract": "envelope:hits,total", ++ "query_semantics": "MCPSessionQueryRequest -> SessionQuerySpec -> shared archive query payload", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "search" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List session costs from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.session_costs", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "session_costs", ++ "object_kind": "session-cost", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Get per-session latency profile by session ID.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..session_latency_profile", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "session_latency_profile", ++ "object_kind": "session-latency", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List session phases from the archive.", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.session_phases", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "session_phases", ++ "object_kind": "session-phase", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Get a single session profile by session ID.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..session_profile", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "session_profile", ++ "object_kind": "session-profile", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List session profiles from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.session_profiles", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "session_profiles", ++ "object_kind": "session-profile", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List session tag rollups from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.session_tag_rollups", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "session_tag_rollups", ++ "object_kind": "session-tag-rollup", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Get per-tool timing breakdown with evidence provenance.\n\nReturns per-tool timing from OTLP spans when available, falling back\nto message-gap estimates. Each timing entry carries an\n``evidence_source`` field: ``\"otlp_span\"`` for exact wallclock timing\nfrom instrumented tool calls, or ``\"message_gap_estimate\"`` for timing\ninferred from inter-message gaps.\n", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..session_tool_timing", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "session_id": "test:conv-discovery-nonexistent" ++ }, ++ "name": "session_tool_timing", ++ "object_kind": "tool-timing", ++ "object_ref_semantics": "accepts session identifier and emits canonical evidence/session refs where available", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "point-read", ++ "verb": "get" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List work events from the archive.", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.session_work_events", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "session_work_events", ++ "object_kind": "work-event", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "personal-state", ++ "implementation": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..set_metadata", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "set_metadata", ++ "object_kind": "session-metadata", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_personal_state_tools.register_personal_state_tools..set_metadata", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "write", ++ "result_semantics": "mutation", ++ "verb": "write" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "status", ++ "implementation": "polylogue.mcp.server_tools.register_query_tools..stats", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": {}, ++ "name": "stats", ++ "object_kind": "archive-statistics", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.stats", ++ "output_contract": "single_object", ++ "query_semantics": "none", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List work threads from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.threads", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "threads", ++ "object_kind": "work-thread", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Distribution of materialized per-session tool-call latency.", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..tool_call_latency_distribution", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "tool_call_latency_distribution", ++ "object_kind": "tool-latency", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List tool usage from the archive.", ++ "family": "insight", ++ "implementation": "polylogue.mcp.server_insight_tools.tool_usage", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "tool_usage", ++ "object_kind": "tool-usage", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "", ++ "family": "maintenance", ++ "implementation": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..update_index", ++ "migration_owner": "polylogue-t46.8.3", ++ "minimal_arguments": {}, ++ "name": "update_index", ++ "object_kind": "maintenance-operation", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.mcp.server_maintenance_tools.register_maintenance_tools..update_index", ++ "output_contract": "operation_result", ++ "query_semantics": "none", ++ "registered_from_declaration": false, ++ "required_role": "admin", ++ "result_semantics": "maintenance", ++ "verb": "run" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "offset", ++ "description": "List usage timeline from the archive.", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.usage_timeline", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "usage_timeline", ++ "object_kind": "usage-timeline", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "envelope:items,total", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "exhaustive-page", ++ "verb": "read" ++ }, ++ { ++ "absence_contract": "typed empty envelope or typed not-found/error; no exception escapes safe-call boundary", ++ "compatibility_route": null, ++ "continuation": "none", ++ "description": "Histogram session workflow shapes by week, origin, or project.", ++ "family": "timeline", ++ "implementation": "polylogue.mcp.server_insight_tools.register_insight_tools..workflow_shape_distribution", ++ "migration_owner": "polylogue-t46.8.2", ++ "minimal_arguments": { ++ "limit": 1 ++ }, ++ "name": "workflow_shape_distribution", ++ "object_kind": "workflow-shape", ++ "object_ref_semantics": "none", ++ "operation_owner": "polylogue.api.Polylogue", ++ "output_contract": "single_object", ++ "query_semantics": "Polylogue insight operations/query model; no MCP-local evaluator", ++ "registered_from_declaration": true, ++ "required_role": "read", ++ "result_semantics": "aggregate", ++ "verb": "aggregate" ++ } ++] +diff --git a/tests/data/witnesses/mcp-tool-schemas.json b/tests/data/witnesses/mcp-tool-schemas.json +index 8f8dcb8952689b5b0082730c9c318de667e84a0a..fdaaa9042819cb12dafbfd79ddc8e0c166edd55b 100644 +--- a/tests/data/witnesses/mcp-tool-schemas.json ++++ b/tests/data/witnesses/mcp-tool-schemas.json +@@ -1,12 +1,16 @@ + [ ++ { ++ "name": "action_affordances", ++ "parameters": { ++ "properties": {}, ++ "title": "action_affordancesArguments", ++ "type": "object" ++ } ++ }, + { + "name": "add_mark", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, + "mark_type": { + "title": "Mark Type", + "type": "string" +@@ -23,6 +27,10 @@ + "default": null, + "title": "Message Id" + }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, + "target_id": { + "anyOf": [ + { +@@ -53,6 +61,30 @@ + "name": "add_tag", + "parameters": { + "properties": { ++ "author_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Kind" ++ }, ++ "author_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Ref" ++ }, + "session_id": { + "title": "Session Id", + "type": "string" +@@ -70,6 +102,55 @@ + "type": "object" + } + }, ++ { ++ "name": "agent_coordination", ++ "parameters": { ++ "properties": { ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "detail": { ++ "default": false, ++ "title": "Detail", ++ "type": "boolean" ++ }, ++ "fresh": { ++ "default": false, ++ "title": "Fresh", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "view": { ++ "default": "status", ++ "enum": [ ++ "status", ++ "self", ++ "work-item", ++ "conflicts", ++ "handoff" ++ ], ++ "title": "View", ++ "type": "string" ++ } ++ }, ++ "title": "agent_coordinationArguments", ++ "type": "object" ++ } ++ }, + { + "name": "aggregate_sessions", + "parameters": { +@@ -79,7 +160,7 @@ + "title": "Group By", + "type": "string" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -89,7 +170,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "since": { + "anyOf": [ +@@ -125,7 +206,7 @@ + "parameters": { + "properties": { + "group_by": { +- "default": "provider", ++ "default": "origin", + "title": "Group By", + "type": "string" + }, +@@ -141,7 +222,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -151,7 +232,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "since": { + "anyOf": [ +@@ -186,7 +267,12 @@ + "name": "archive_debt", + "parameters": { + "properties": { +- "category": { ++ "exact_fts": { ++ "default": false, ++ "title": "Exact Fts", ++ "type": "boolean" ++ }, ++ "kind": { + "anyOf": [ + { + "type": "string" +@@ -196,7 +282,7 @@ + } + ], + "default": null, +- "title": "Category" ++ "title": "Kind" + }, + "limit": { + "default": 50, +@@ -204,12 +290,6 @@ + "title": "Limit", + "type": "integer" + }, +- "offset": { +- "default": 0, +- "minimum": 0, +- "title": "Offset", +- "type": "integer" +- }, + "only_actionable": { + "default": false, + "title": "Only Actionable", +@@ -221,37 +301,38 @@ + } + }, + { +- "name": "build_context_image", ++ "name": "archive_get_session", + "parameters": { + "properties": { +- "detail_level": { +- "default": "compact", +- "title": "Detail Level", ++ "session_id": { ++ "title": "Session Id", + "type": "string" +- }, +- "max_sessions": { +- "default": 5, +- "title": "Max Sessions", +- "type": "integer" +- }, +- "max_tokens": { ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "archive_get_sessionArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "archive_list_sessions", ++ "parameters": { ++ "properties": { ++ "action": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Max Tokens" +- }, +- "include_assertions": { +- "default": true, +- "title": "Include Assertions", +- "type": "boolean" ++ "title": "Action" + }, +- "project_path": { ++ "action_sequence": { + "anyOf": [ + { + "type": "string" +@@ -261,9 +342,9 @@ + } + ], + "default": null, +- "title": "Project Path" ++ "title": "Action Sequence" + }, +- "project_repo": { ++ "action_text": { + "anyOf": [ + { + "type": "string" +@@ -273,9 +354,9 @@ + } + ], + "default": null, +- "title": "Project Repo" ++ "title": "Action Text" + }, +- "provider": { ++ "cwd_prefix": { + "anyOf": [ + { + "type": "string" +@@ -285,9 +366,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Cwd Prefix" + }, +- "query": { ++ "exclude_action": { + "anyOf": [ + { + "type": "string" +@@ -297,14 +378,9 @@ + } + ], + "default": null, +- "title": "Query" +- }, +- "redact_paths": { +- "default": true, +- "title": "Redact Paths", +- "type": "boolean" ++ "title": "Exclude Action" + }, +- "since": { ++ "exclude_origin": { + "anyOf": [ + { + "type": "string" +@@ -314,9 +390,9 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Exclude Origin" + }, +- "until": { ++ "exclude_tag": { + "anyOf": [ + { + "type": "string" +@@ -326,49 +402,9 @@ + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "title": "build_context_imageArguments", +- "type": "object" +- } +- }, +- { +- "name": "bulk_tag_sessions", +- "parameters": { +- "properties": { +- "session_ids": { +- "items": { +- "type": "string" +- }, +- "title": "Session Ids", +- "type": "array" +- }, +- "tags": { +- "items": { +- "type": "string" +- }, +- "title": "Tags", +- "type": "array" +- } +- }, +- "required": [ +- "session_ids", +- "tags" +- ], +- "title": "bulk_tag_sessionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "clear_corrections", +- "parameters": { +- "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" ++ "title": "Exclude Tag" + }, +- "kind": { ++ "exclude_tool": { + "anyOf": [ + { + "type": "string" +@@ -378,21 +414,24 @@ + } + ], + "default": null, +- "title": "Kind" +- } +- }, +- "required": [ +- "session_id" +- ], +- "title": "clear_correctionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "compare_sessions", +- "parameters": { +- "properties": { +- "session_ids": { ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { + "anyOf": [ + { + "type": "string" +@@ -402,69 +441,39 @@ + } + ], + "default": null, +- "title": "Session Ids" +- } +- }, +- "title": "compare_sessionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "compose_context_preamble", +- "parameters": { +- "properties": { +- "cwd": { ++ "title": "Has Type" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { + "anyOf": [ + { +- "type": "string" ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Cwd" +- }, +- "limit": { +- "default": 5, +- "title": "Limit", +- "type": "integer" +- }, +- "recent_files": { +- "default": [], +- "items": { +- "type": "string" +- }, +- "title": "Recent Files", +- "type": "array" ++ "title": "Max Messages" + }, +- "repo_path": { ++ "max_words": { + "anyOf": [ + { +- "type": "string" ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Repo Path" +- } +- }, +- "title": "compose_context_preambleArguments", +- "type": "object" +- } +- }, +- { +- "name": "correlate_session", +- "parameters": { +- "properties": { +- "confidence_threshold": { +- "default": 0.3, +- "title": "Confidence Threshold", +- "type": "number" ++ "title": "Max Words" + }, +- "repo_path": { ++ "message_type": { + "anyOf": [ + { + "type": "string" +@@ -474,62 +483,39 @@ + } + ], + "default": null, +- "title": "Repo Path" +- }, +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, +- "since_hours": { +- "default": 2, +- "title": "Since Hours", +- "type": "integer" +- } +- }, +- "required": [ +- "session_id" +- ], +- "title": "correlate_sessionArguments", +- "type": "object" +- } +- }, +- { +- "name": "correlate_sessions", +- "parameters": { +- "properties": { +- "metric_x": { +- "title": "Metric X", +- "type": "string" +- }, +- "metric_y": { +- "title": "Metric Y", +- "type": "string" ++ "title": "Message Type" + }, +- "provider": { ++ "min_messages": { + "anyOf": [ + { +- "type": "string" ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Provider" ++ "title": "Min Messages" + }, +- "since": { ++ "min_words": { + "anyOf": [ + { +- "type": "string" ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Since" ++ "title": "Min Words" + }, +- "until": { ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -539,49 +525,9 @@ + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "required": [ +- "metric_x", +- "metric_y" +- ], +- "title": "correlate_sessionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "cost_outlook", +- "parameters": { +- "properties": { +- "method": { +- "default": "linear", +- "title": "Method", +- "type": "string" +- }, +- "plan": { +- "title": "Plan", +- "type": "string" +- } +- }, +- "required": [ +- "plan" +- ], +- "title": "cost_outlookArguments", +- "type": "object" +- } +- }, +- { +- "name": "cost_rollups", +- "parameters": { +- "properties": { +- "limit": { +- "default": 50, +- "minimum": 1, +- "title": "Limit", +- "type": "integer" ++ "title": "Origin" + }, +- "model": { ++ "project": { + "anyOf": [ + { + "type": "string" +@@ -591,15 +537,9 @@ + } + ], + "default": null, +- "title": "Model" +- }, +- "offset": { +- "default": 0, +- "minimum": 0, +- "title": "Offset", +- "type": "integer" ++ "title": "Project" + }, +- "provider": { ++ "referenced_path": { + "anyOf": [ + { + "type": "string" +@@ -609,9 +549,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Referenced Path" + }, +- "since": { ++ "repo": { + "anyOf": [ + { + "type": "string" +@@ -621,200 +561,57 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Repo" + }, +- "until": { ++ "sample": { + "anyOf": [ + { +- "type": "string" ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "title": "cost_rollupsArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_annotation", +- "parameters": { +- "properties": { +- "annotation_id": { +- "title": "Annotation Id", +- "type": "string" +- } +- }, +- "required": [ +- "annotation_id" +- ], +- "title": "delete_annotationArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_session", +- "parameters": { +- "properties": { +- "confirm": { +- "default": false, +- "title": "Confirm", +- "type": "boolean" +- }, +- "session_id": { +- "title": "Session Id", +- "type": "string" +- } +- }, +- "required": [ +- "session_id" +- ], +- "title": "delete_sessionArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_metadata", +- "parameters": { +- "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" ++ "title": "Sample" + }, +- "key": { +- "title": "Key", +- "type": "string" +- } +- }, +- "required": [ +- "session_id", +- "key" +- ], +- "title": "delete_metadataArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_recall_pack", +- "parameters": { +- "properties": { +- "pack_id": { +- "title": "Pack Id", +- "type": "string" +- } +- }, +- "required": [ +- "pack_id" +- ], +- "title": "delete_recall_packArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_saved_view", +- "parameters": { +- "properties": { +- "view_id": { +- "title": "View Id", +- "type": "string" +- } +- }, +- "required": [ +- "view_id" +- ], +- "title": "delete_saved_viewArguments", +- "type": "object" +- } +- }, +- { +- "name": "delete_workspace", +- "parameters": { +- "properties": { +- "workspace_id": { +- "title": "Workspace Id", +- "type": "string" +- } +- }, +- "required": [ +- "workspace_id" +- ], +- "title": "delete_workspaceArguments", +- "type": "object" +- } +- }, +- { +- "name": "embedding_preflight", +- "parameters": { +- "properties": { +- "max_sessions": { ++ "since": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Max Sessions" ++ "title": "Since" + }, +- "max_cost_usd": { ++ "tag": { + "anyOf": [ + { +- "type": "number" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Max Cost Usd" ++ "title": "Tag" + }, +- "max_messages": { ++ "title": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Max Messages" ++ "title": "Title" + }, +- "rebuild": { +- "default": false, +- "title": "Rebuild", +- "type": "boolean" +- } +- }, +- "title": "embedding_preflightArguments", +- "type": "object" +- } +- }, +- { +- "name": "embedding_status", +- "parameters": { +- "properties": { +- "detail": { +- "default": false, +- "title": "Detail", +- "type": "boolean" +- } +- }, +- "title": "embedding_statusArguments", +- "type": "object" +- } +- }, +- { +- "name": "facets", +- "parameters": { +- "properties": { +- "action": { ++ "tool": { + "anyOf": [ + { + "type": "string" +@@ -824,21 +621,14 @@ + } + ], + "default": null, +- "title": "Action" ++ "title": "Tool" + }, +- "action_sequence": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Action Sequence" ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" + }, +- "action_text": { ++ "until": { + "anyOf": [ + { + "type": "string" +@@ -848,9 +638,18 @@ + } + ], + "default": null, +- "title": "Action Text" +- }, +- "contains": { ++ "title": "Until" ++ } ++ }, ++ "title": "archive_list_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "archive_search_sessions", ++ "parameters": { ++ "properties": { ++ "action": { + "anyOf": [ + { + "type": "string" +@@ -860,9 +659,9 @@ + } + ], + "default": null, +- "title": "Contains" ++ "title": "Action" + }, +- "conv_id": { ++ "action_sequence": { + "anyOf": [ + { + "type": "string" +@@ -872,9 +671,9 @@ + } + ], + "default": null, +- "title": "Conv Id" ++ "title": "Action Sequence" + }, +- "cursor": { ++ "action_text": { + "anyOf": [ + { + "type": "string" +@@ -884,7 +683,7 @@ + } + ], + "default": null, +- "title": "Cursor" ++ "title": "Action Text" + }, + "cwd_prefix": { + "anyOf": [ +@@ -910,7 +709,7 @@ + "default": null, + "title": "Exclude Action" + }, +- "exclude_provider": { ++ "exclude_origin": { + "anyOf": [ + { + "type": "string" +@@ -920,7 +719,7 @@ + } + ], + "default": null, +- "title": "Exclude Provider" ++ "title": "Exclude Origin" + }, + "exclude_tag": { + "anyOf": [ +@@ -934,18 +733,6 @@ + "default": null, + "title": "Exclude Tag" + }, +- "exclude_text": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Exclude Text" +- }, + "exclude_tool": { + "anyOf": [ + { +@@ -958,9 +745,9 @@ + "default": null, + "title": "Exclude Tool" + }, +- "has_paste": { ++ "has_paste_evidence": { + "default": false, +- "title": "Has Paste", ++ "title": "Has Paste Evidence", + "type": "boolean" + }, + "has_thinking": { +@@ -985,18 +772,6 @@ + "default": null, + "title": "Has Type" + }, +- "latest": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Latest" +- }, + "limit": { + "default": 10, + "minimum": 1, +@@ -1051,13 +826,7 @@ + "default": null, + "title": "Min Words" + }, +- "offset": { +- "default": 0, +- "minimum": 0, +- "title": "Offset", +- "type": "integer" +- }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -1067,9 +836,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, +- "query": { ++ "project": { + "anyOf": [ + { + "type": "string" +@@ -1079,7 +848,11 @@ + } + ], + "default": null, +- "title": "Query" ++ "title": "Project" ++ }, ++ "query": { ++ "title": "Query", ++ "type": "string" + }, + "referenced_path": { + "anyOf": [ +@@ -1105,7 +878,7 @@ + "default": null, + "title": "Repo" + }, +- "retrieval_lane": { ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -1115,26 +888,21 @@ + } + ], + "default": null, +- "title": "Retrieval Lane" +- }, +- "reverse": { +- "default": false, +- "title": "Reverse", +- "type": "boolean" ++ "title": "Since" + }, +- "sample": { ++ "tag": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Sample" ++ "title": "Tag" + }, +- "similar_text": { ++ "title": { + "anyOf": [ + { + "type": "string" +@@ -1144,9 +912,9 @@ + } + ], + "default": null, +- "title": "Similar Text" ++ "title": "Title" + }, +- "since": { ++ "tool": { + "anyOf": [ + { + "type": "string" +@@ -1156,9 +924,14 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" + }, +- "since_session": { ++ "until": { + "anyOf": [ + { + "type": "string" +@@ -1168,9 +941,21 @@ + } + ], + "default": null, +- "title": "Since Session" +- }, +- "since_session_id": { ++ "title": "Until" ++ } ++ }, ++ "required": [ ++ "query" ++ ], ++ "title": "archive_search_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "blackboard_list", ++ "parameters": { ++ "properties": { ++ "kind": { + "anyOf": [ + { + "type": "string" +@@ -1180,9 +965,14 @@ + } + ], + "default": null, +- "title": "Since Session Id" ++ "title": "Kind" + }, +- "sort": { ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "scope_repo": { + "anyOf": [ + { + "type": "string" +@@ -1192,9 +982,28 @@ + } + ], + "default": null, +- "title": "Sort" ++ "title": "Scope Repo" + }, +- "tag": { ++ "unresolved": { ++ "default": false, ++ "title": "Unresolved", ++ "type": "boolean" ++ } ++ }, ++ "title": "blackboard_listArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "blackboard_post", ++ "parameters": { ++ "properties": { ++ "author_kind": { ++ "default": "agent", ++ "title": "Author Kind", ++ "type": "string" ++ }, ++ "author_ref": { + "anyOf": [ + { + "type": "string" +@@ -1204,69 +1013,72 @@ + } + ], + "default": null, +- "title": "Tag" ++ "title": "Author Ref" + }, +- "title": { ++ "content": { ++ "title": "Content", ++ "type": "string" ++ }, ++ "context_policy": { + "anyOf": [ + { +- "type": "string" ++ "additionalProperties": true, ++ "type": "object" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Title" ++ "title": "Context Policy" + }, +- "tool": { ++ "evidence_refs": { + "anyOf": [ + { +- "type": "string" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Tool" ++ "title": "Evidence Refs" + }, +- "typed_only": { +- "default": false, +- "title": "Typed Only", +- "type": "boolean" ++ "kind": { ++ "title": "Kind", ++ "type": "string" + }, +- "until": { ++ "related_sessions": { + "anyOf": [ + { +- "type": "string" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "title": "facetsArguments", +- "type": "object" +- } +- }, +- { +- "name": "find_abandoned_sessions", +- "parameters": { +- "properties": { +- "limit": { +- "default": 20, +- "title": "Limit", +- "type": "integer" ++ "title": "Related Sessions" + }, +- "min_severity": { +- "default": "question_left", +- "title": "Min Severity", +- "type": "string" ++ "scope_issue": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Issue" + }, +- "repo_path": { ++ "scope_path": { + "anyOf": [ + { + "type": "string" +@@ -1276,9 +1088,9 @@ + } + ], + "default": null, +- "title": "Repo Path" ++ "title": "Scope Path" + }, +- "since": { ++ "scope_repo": { + "anyOf": [ + { + "type": "string" +@@ -1288,18 +1100,9 @@ + } + ], + "default": null, +- "title": "Since" +- } +- }, +- "title": "find_abandoned_sessionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "find_resume_candidates", +- "parameters": { +- "properties": { +- "cwd": { ++ "title": "Scope Repo" ++ }, ++ "scope_session": { + "anyOf": [ + { + "type": "string" +@@ -1309,68 +1112,119 @@ + } + ], + "default": null, +- "title": "Cwd" +- }, +- "limit": { +- "default": 10, +- "title": "Limit", +- "type": "integer" ++ "title": "Scope Session" + }, +- "recent_files": { +- "default": [], +- "items": { +- "type": "string" +- }, +- "title": "Recent Files", +- "type": "array" ++ "staleness": { ++ "anyOf": [ ++ { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Staleness" + }, +- "repo_path": { +- "title": "Repo Path", ++ "title": { ++ "title": "Title", + "type": "string" + } + }, + "required": [ +- "repo_path" ++ "kind", ++ "title", ++ "content" + ], +- "title": "find_resume_candidatesArguments", ++ "title": "blackboard_postArguments", + "type": "object" + } + }, + { +- "name": "find_similar_sessions", ++ "name": "build_context_image", + "parameters": { + "properties": { +- "limit": { +- "default": 10, +- "title": "Limit", +- "type": "integer" +- }, +- "session_id": { +- "title": "Session Id", ++ "detail_level": { ++ "default": "compact", ++ "title": "Detail Level", + "type": "string" + }, +- "similarity_dimension": { +- "default": "auto", +- "title": "Similarity Dimension", +- "type": "string" +- } +- }, +- "required": [ +- "session_id" +- ], +- "title": "find_similar_sessionsArguments", +- "type": "object" +- } +- }, +- { +- "name": "find_stuck_sessions", +- "parameters": { +- "properties": { +- "limit": { +- "default": 20, +- "title": "Limit", ++ "include_assertions": { ++ "default": true, ++ "title": "Include Assertions", ++ "type": "boolean" ++ }, ++ "max_sessions": { ++ "default": 5, ++ "title": "Max Sessions", + "type": "integer" + }, ++ "max_tokens": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Tokens" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project Path" ++ }, ++ "project_repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project Repo" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "redact_paths": { ++ "default": true, ++ "title": "Redact Paths", ++ "type": "boolean" ++ }, + "since": { + "anyOf": [ + { +@@ -1382,179 +1236,2538 @@ + ], + "default": null, + "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" + } + }, +- "title": "find_stuck_sessionsArguments", ++ "title": "build_context_imageArguments", + "type": "object" + } + }, + { +- "name": "get_session_summary", ++ "name": "bulk_tag_sessions", + "parameters": { + "properties": { +- "id": { +- "title": "Id", +- "type": "string" ++ "session_ids": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Session Ids", ++ "type": "array" ++ }, ++ "tags": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Tags", ++ "type": "array" + } + }, + "required": [ +- "id" ++ "session_ids", ++ "tags" + ], +- "title": "get_session_summaryArguments", ++ "title": "bulk_tag_sessionsArguments", + "type": "object" + } + }, + { +- "name": "get_logical_session", ++ "name": "capture_assertion_candidate", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", ++ "author_ref": { ++ "title": "Author Ref", ++ "type": "string" ++ }, ++ "body_text": { ++ "title": "Body Text", ++ "type": "string" ++ }, ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "kind": { ++ "default": "note", ++ "title": "Kind", + "type": "string" ++ }, ++ "refs": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Refs" ++ }, ++ "scope_refs": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Refs" + } + }, + "required": [ +- "session_id" ++ "body_text", ++ "author_ref" + ], +- "title": "get_logical_sessionArguments", ++ "title": "capture_assertion_candidateArguments", + "type": "object" + } + }, + { +- "name": "get_messages", ++ "name": "clear_corrections", + "parameters": { + "properties": { ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, + "session_id": { + "title": "Session Id", + "type": "string" +- }, +- "limit": { +- "default": 50, +- "minimum": 1, +- "title": "Limit", +- "type": "integer" +- }, +- "offset": { +- "default": 0, +- "minimum": 0, +- "title": "Offset", +- "type": "integer" + } + }, + "required": [ + "session_id" + ], +- "title": "get_messagesArguments", ++ "title": "clear_correctionsArguments", + "type": "object" + } + }, + { +- "name": "get_metadata", ++ "name": "compare_sessions", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" ++ "session_ids": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Ids" + } + }, +- "required": [ +- "session_id" +- ], +- "title": "get_metadataArguments", ++ "title": "compare_sessionsArguments", + "type": "object" + } + }, + { +- "name": "get_resume_brief", ++ "name": "compile_context", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", ++ "include_assertions": { ++ "default": true, ++ "title": "Include Assertions", ++ "type": "boolean" ++ }, ++ "include_candidates": { ++ "default": false, ++ "title": "Include Candidates", ++ "type": "boolean" ++ }, ++ "max_tokens": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Tokens" ++ }, ++ "read_views": { ++ "default": "messages", ++ "title": "Read Views", + "type": "string" + }, +- "related_limit": { +- "default": 6, +- "title": "Related Limit", +- "type": "integer" ++ "seed_query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Seed Query" ++ }, ++ "seed_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Seed Ref" + } + }, +- "required": [ +- "session_id" +- ], +- "title": "get_resume_briefArguments", ++ "title": "compile_contextArguments", + "type": "object" + } + }, + { +- "name": "get_session_topology", ++ "name": "compose_context_preamble", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "limit": { ++ "default": 5, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "recent_files": { ++ "default": [], ++ "items": { ++ "type": "string" ++ }, ++ "title": "Recent Files", ++ "type": "array" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, ++ "successor_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Successor Session Id" + } + }, +- "required": [ +- "session_id" +- ], +- "title": "get_session_topologyArguments", ++ "title": "compose_context_preambleArguments", + "type": "object" + } + }, + { +- "name": "get_session_tree", ++ "name": "correlate_session", + "parameters": { + "properties": { ++ "confidence_threshold": { ++ "default": 0.3, ++ "title": "Confidence Threshold", ++ "type": "number" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, + "session_id": { + "title": "Session Id", + "type": "string" ++ }, ++ "since_hours": { ++ "default": 2, ++ "title": "Since Hours", ++ "type": "integer" + } + }, + "required": [ + "session_id" + ], +- "title": "get_session_treeArguments", ++ "title": "correlate_sessionArguments", + "type": "object" + } + }, + { +- "name": "get_stats_by", ++ "name": "correlate_sessions", + "parameters": { + "properties": { +- "group_by": { +- "default": "provider", +- "enum": [ +- "provider", +- "month", +- "year" +- ], +- "title": "Group By", ++ "metric_x": { ++ "title": "Metric X", ++ "type": "string" ++ }, ++ "metric_y": { ++ "title": "Metric Y", + "type": "string" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" + } + }, +- "title": "get_stats_byArguments", ++ "required": [ ++ "metric_x", ++ "metric_y" ++ ], ++ "title": "correlate_sessionsArguments", + "type": "object" + } + }, + { +- "name": "insight_rigor_audit", ++ "name": "cost_outlook", + "parameters": { + "properties": { +- "sample_limit": { +- "default": 500, +- "title": "Sample Limit", +- "type": "integer" ++ "method": { ++ "default": "linear", ++ "title": "Method", ++ "type": "string" ++ }, ++ "plan": { ++ "title": "Plan", ++ "type": "string" + } + }, +- "title": "insight_rigor_auditArguments", ++ "required": [ ++ "plan" ++ ], ++ "title": "cost_outlookArguments", + "type": "object" + } + }, + { +- "name": "list_annotations", ++ "name": "cost_rollups", + "parameters": { + "properties": { +- "session_id": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "model": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Model" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "cost_rollupsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_annotation", ++ "parameters": { ++ "properties": { ++ "annotation_id": { ++ "title": "Annotation Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "annotation_id" ++ ], ++ "title": "delete_annotationArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_metadata", ++ "parameters": { ++ "properties": { ++ "key": { ++ "title": "Key", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id", ++ "key" ++ ], ++ "title": "delete_metadataArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_recall_pack", ++ "parameters": { ++ "properties": { ++ "pack_id": { ++ "title": "Pack Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "pack_id" ++ ], ++ "title": "delete_recall_packArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_saved_view", ++ "parameters": { ++ "properties": { ++ "view_id": { ++ "title": "View Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "view_id" ++ ], ++ "title": "delete_saved_viewArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_session", ++ "parameters": { ++ "properties": { ++ "confirm": { ++ "default": false, ++ "title": "Confirm", ++ "type": "boolean" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "delete_sessionArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "delete_workspace", ++ "parameters": { ++ "properties": { ++ "workspace_id": { ++ "title": "Workspace Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "workspace_id" ++ ], ++ "title": "delete_workspaceArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "embedding_preflight", ++ "parameters": { ++ "properties": { ++ "max_cost_usd": { ++ "anyOf": [ ++ { ++ "type": "number" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Cost Usd" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_sessions": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Sessions" ++ }, ++ "rebuild": { ++ "default": false, ++ "title": "Rebuild", ++ "type": "boolean" ++ } ++ }, ++ "title": "embedding_preflightArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "embedding_status", ++ "parameters": { ++ "properties": { ++ "detail": { ++ "default": false, ++ "title": "Detail", ++ "type": "boolean" ++ } ++ }, ++ "title": "embedding_statusArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "explain_import", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 100, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Path" ++ }, ++ "raw_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Raw Ref" ++ }, ++ "redact_paths": { ++ "default": true, ++ "title": "Redact Paths", ++ "type": "boolean" ++ }, ++ "source_name": { ++ "default": "unknown", ++ "title": "Source Name", ++ "type": "string" ++ }, ++ "source_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Source Path" ++ } ++ }, ++ "title": "explain_importArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "explain_query_expression", ++ "parameters": { ++ "properties": { ++ "expression": { ++ "title": "Expression", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "expression" ++ ], ++ "title": "explain_query_expressionArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "facets", ++ "parameters": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "contains": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Contains" ++ }, ++ "conv_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Conv Id" ++ }, ++ "cursor": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cursor" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Origin" ++ }, ++ "exclude_tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tag" ++ }, ++ "exclude_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Text" ++ }, ++ "exclude_tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Has Type" ++ }, ++ "include_affordances": { ++ "default": false, ++ "title": "Include Affordances", ++ "type": "boolean" ++ }, ++ "latest": { ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Project" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "referenced_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Referenced Path" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "retrieval_lane": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Retrieval Lane" ++ }, ++ "reverse": { ++ "default": false, ++ "title": "Reverse", ++ "type": "boolean" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "similar_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Session Id" ++ }, ++ "similar_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Similar Text" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "since_session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Session Id" ++ }, ++ "sort": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sort" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" ++ }, ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "facetsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "find_abandoned_sessions", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "min_severity": { ++ "default": "question_left", ++ "title": "Min Severity", ++ "type": "string" ++ }, ++ "repo_path": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo Path" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ } ++ }, ++ "title": "find_abandoned_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "find_resume_candidates", ++ "parameters": { ++ "properties": { ++ "cwd": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd" ++ }, ++ "limit": { ++ "default": 10, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "recent_files": { ++ "default": [], ++ "items": { ++ "type": "string" ++ }, ++ "title": "Recent Files", ++ "type": "array" ++ }, ++ "repo_path": { ++ "title": "Repo Path", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "repo_path" ++ ], ++ "title": "find_resume_candidatesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "find_similar_sessions", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 10, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "similarity_dimension": { ++ "default": "auto", ++ "title": "Similarity Dimension", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "find_similar_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "find_stuck_sessions", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ } ++ }, ++ "title": "find_stuck_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_context_delivery", ++ "parameters": { ++ "properties": { ++ "recipient_ref": { ++ "title": "Recipient Ref", ++ "type": "string" ++ }, ++ "snapshot_ref": { ++ "title": "Snapshot Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "snapshot_ref", ++ "recipient_ref" ++ ], ++ "title": "get_context_deliveryArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_logical_session", ++ "parameters": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_logical_sessionArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_messages", ++ "parameters": { ++ "properties": { ++ "excerpt": { ++ "default": false, ++ "title": "Excerpt", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "match_query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Match Query" ++ }, ++ "material_origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Material Origin" ++ }, ++ "max_chars_per_message": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Message" ++ }, ++ "message_role": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Role" ++ }, ++ "message_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Type" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "offset_from": { ++ "default": "start", ++ "title": "Offset From", ++ "type": "string" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, ++ "tail": { ++ "default": false, ++ "title": "Tail", ++ "type": "boolean" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_messagesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_metadata", ++ "parameters": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_metadataArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_pathologies", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Limit" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "get_pathologiesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_postmortem_bundle", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Limit" ++ }, ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "repo": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Repo" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "get_postmortem_bundleArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_resume_brief", ++ "parameters": { ++ "properties": { ++ "related_limit": { ++ "default": 6, ++ "title": "Related Limit", ++ "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_resume_briefArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_session_summary", ++ "parameters": { ++ "properties": { ++ "id": { ++ "title": "Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "id" ++ ], ++ "title": "get_session_summaryArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_session_topology", ++ "parameters": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_session_topologyArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_session_tree", ++ "parameters": { ++ "properties": { ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "session_id" ++ ], ++ "title": "get_session_treeArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "get_stats_by", ++ "parameters": { ++ "properties": { ++ "group_by": { ++ "default": "origin", ++ "enum": [ ++ "origin", ++ "day", ++ "month", ++ "year" ++ ], ++ "title": "Group By", ++ "type": "string" ++ } ++ }, ++ "title": "get_stats_byArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "import_annotation_batch", ++ "parameters": { ++ "properties": { ++ "actor_ref": { ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "batch_id": { ++ "title": "Batch Id", ++ "type": "string" ++ }, ++ "jsonl": { ++ "title": "Jsonl", ++ "type": "string" ++ }, ++ "metadata": { ++ "anyOf": [ ++ { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Metadata" ++ }, ++ "model_ref": { ++ "title": "Model Ref", ++ "type": "string" ++ }, ++ "prompt_ref": { ++ "title": "Prompt Ref", ++ "type": "string" ++ }, ++ "schema_id": { ++ "title": "Schema Id", ++ "type": "string" ++ }, ++ "schema_version": { ++ "title": "Schema Version", ++ "type": "integer" ++ }, ++ "source_result_ref": { ++ "title": "Source Result Ref", ++ "type": "string" ++ }, ++ "target_ref": { ++ "title": "Target Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "jsonl", ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "target_ref", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref" ++ ], ++ "title": "import_annotation_batchArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "insight_rigor_audit", ++ "parameters": { ++ "properties": { ++ "sample_limit": { ++ "default": 500, ++ "title": "Sample Limit", ++ "type": "integer" ++ } ++ }, ++ "title": "insight_rigor_auditArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "join_typed_annotations", ++ "parameters": { ++ "properties": { ++ "group_by": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Group By" ++ }, ++ "limit": { ++ "default": 500, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "schema_id": { ++ "title": "Schema Id", ++ "type": "string" ++ }, ++ "schema_version": { ++ "title": "Schema Version", ++ "type": "integer" ++ }, ++ "statuses": { ++ "items": { ++ "type": "string" ++ }, ++ "title": "Statuses", ++ "type": "array" ++ }, ++ "target_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Kind" ++ } ++ }, ++ "required": [ ++ "schema_id", ++ "schema_version", ++ "statuses" ++ ], ++ "title": "join_typed_annotationsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "judge_assertion_candidate", ++ "parameters": { ++ "properties": { ++ "actor_ref": { ++ "default": "user:local", ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "candidate_ref": { ++ "title": "Candidate Ref", ++ "type": "string" ++ }, ++ "decision": { ++ "enum": [ ++ "accept", ++ "reject", ++ "defer", ++ "supersede" ++ ], ++ "title": "Decision", ++ "type": "string" ++ }, ++ "inject": { ++ "default": false, ++ "title": "Inject", ++ "type": "boolean" ++ }, ++ "reason": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Reason" ++ }, ++ "replacement_body_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Body Text" ++ }, ++ "replacement_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Kind" ++ }, ++ "replacement_value": { ++ "anyOf": [ ++ {}, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Replacement Value" ++ } ++ }, ++ "required": [ ++ "candidate_ref", ++ "decision" ++ ], ++ "title": "judge_assertion_candidateArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "judge_assertion_candidates", ++ "parameters": { ++ "properties": { ++ "actor_ref": { ++ "default": "user:local", ++ "title": "Actor Ref", ++ "type": "string" ++ }, ++ "items": { ++ "items": { ++ "additionalProperties": true, ++ "type": "object" ++ }, ++ "title": "Items", ++ "type": "array" ++ } ++ }, ++ "required": [ ++ "items" ++ ], ++ "title": "judge_assertion_candidatesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_annotations", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Type" ++ } ++ }, ++ "title": "list_annotationsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_assertion_candidate_reviews", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_candidate_reviewsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_assertion_candidates", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_candidatesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_assertion_claims", ++ "parameters": { ++ "properties": { ++ "context_inject": { ++ "anyOf": [ ++ { ++ "type": "boolean" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Context Inject" ++ }, ++ "kinds": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kinds" ++ }, ++ "limit": { ++ "default": 20, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "scope_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Scope Ref" ++ }, ++ "statuses": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": "active,candidate", ++ "title": "Statuses" ++ }, ++ "target_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Ref" ++ } ++ }, ++ "title": "list_assertion_claimsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_corrections", ++ "parameters": { ++ "properties": { ++ "kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Kind" ++ }, ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_correction": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Correction" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ } ++ }, ++ "title": "list_correctionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_marks", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "mark_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Mark Type" ++ }, ++ "message_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Message Id" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, ++ "target_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Id" ++ }, ++ "target_type": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Target Type" ++ } ++ }, ++ "title": "list_marksArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_read_view_profiles", ++ "parameters": { ++ "properties": {}, ++ "title": "list_read_view_profilesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_recall_packs", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_recall_packsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_saved_views", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_saved_viewsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_sessions", ++ "parameters": { ++ "properties": { ++ "action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action" ++ }, ++ "action_sequence": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Sequence" ++ }, ++ "action_text": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Action Text" ++ }, ++ "contains": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Contains" ++ }, ++ "conv_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Conv Id" ++ }, ++ "cursor": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cursor" ++ }, ++ "cwd_prefix": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Cwd Prefix" ++ }, ++ "exclude_action": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Exclude Action" ++ }, ++ "exclude_origin": { + "anyOf": [ + { + "type": "string" +@@ -1564,9 +3777,9 @@ + } + ], + "default": null, +- "title": "Session Id" ++ "title": "Exclude Origin" + }, +- "message_id": { ++ "exclude_tag": { + "anyOf": [ + { + "type": "string" +@@ -1576,9 +3789,9 @@ + } + ], + "default": null, +- "title": "Message Id" ++ "title": "Exclude Tag" + }, +- "target_id": { ++ "exclude_text": { + "anyOf": [ + { + "type": "string" +@@ -1588,9 +3801,9 @@ + } + ], + "default": null, +- "title": "Target Id" ++ "title": "Exclude Text" + }, +- "target_type": { ++ "exclude_tool": { + "anyOf": [ + { + "type": "string" +@@ -1600,18 +3813,24 @@ + } + ], + "default": null, +- "title": "Target Type" +- } +- }, +- "title": "list_annotationsArguments", +- "type": "object" +- } +- }, +- { +- "name": "list_sessions", +- "parameters": { +- "properties": { +- "action": { ++ "title": "Exclude Tool" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { + "anyOf": [ + { + "type": "string" +@@ -1621,9 +3840,46 @@ + } + ], + "default": null, +- "title": "Action" ++ "title": "Has Type" + }, +- "action_sequence": { ++ "latest": { ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Messages" ++ }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, ++ "message_type": { + "anyOf": [ + { + "type": "string" +@@ -1633,9 +3889,41 @@ + } + ], + "default": null, +- "title": "Action Sequence" ++ "title": "Message Type" + }, +- "action_text": { ++ "min_messages": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Messages" ++ }, ++ "min_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Min Words" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -1645,9 +3933,9 @@ + } + ], + "default": null, +- "title": "Action Text" ++ "title": "Origin" + }, +- "contains": { ++ "project": { + "anyOf": [ + { + "type": "string" +@@ -1657,9 +3945,9 @@ + } + ], + "default": null, +- "title": "Contains" ++ "title": "Project" + }, +- "conv_id": { ++ "referenced_path": { + "anyOf": [ + { + "type": "string" +@@ -1669,9 +3957,9 @@ + } + ], + "default": null, +- "title": "Conv Id" ++ "title": "Referenced Path" + }, +- "cursor": { ++ "repo": { + "anyOf": [ + { + "type": "string" +@@ -1681,9 +3969,9 @@ + } + ], + "default": null, +- "title": "Cursor" ++ "title": "Repo" + }, +- "cwd_prefix": { ++ "retrieval_lane": { + "anyOf": [ + { + "type": "string" +@@ -1693,9 +3981,26 @@ + } + ], + "default": null, +- "title": "Cwd Prefix" ++ "title": "Retrieval Lane" + }, +- "exclude_action": { ++ "reverse": { ++ "default": false, ++ "title": "Reverse", ++ "type": "boolean" ++ }, ++ "sample": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Sample" ++ }, ++ "similar_session_id": { + "anyOf": [ + { + "type": "string" +@@ -1705,9 +4010,9 @@ + } + ], + "default": null, +- "title": "Exclude Action" ++ "title": "Similar Session Id" + }, +- "exclude_provider": { ++ "similar_text": { + "anyOf": [ + { + "type": "string" +@@ -1717,9 +4022,9 @@ + } + ], + "default": null, +- "title": "Exclude Provider" ++ "title": "Similar Text" + }, +- "exclude_tag": { ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -1729,9 +4034,9 @@ + } + ], + "default": null, +- "title": "Exclude Tag" ++ "title": "Since" + }, +- "exclude_text": { ++ "since_session_id": { + "anyOf": [ + { + "type": "string" +@@ -1741,9 +4046,9 @@ + } + ], + "default": null, +- "title": "Exclude Text" ++ "title": "Since Session Id" + }, +- "exclude_tool": { ++ "sort": { + "anyOf": [ + { + "type": "string" +@@ -1753,24 +4058,50 @@ + } + ], + "default": null, +- "title": "Exclude Tool" ++ "title": "Sort" + }, +- "has_paste": { +- "default": false, +- "title": "Has Paste", +- "type": "boolean" ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" + }, +- "has_thinking": { +- "default": false, +- "title": "Has Thinking", +- "type": "boolean" ++ "title": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Title" + }, +- "has_tool_use": { ++ "tool": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tool" ++ }, ++ "typed_only": { + "default": false, +- "title": "Has Tool Use", ++ "title": "Typed Only", + "type": "boolean" + }, +- "has_type": { ++ "until": { + "anyOf": [ + { + "type": "string" +@@ -1780,9 +4111,30 @@ + } + ], + "default": null, +- "title": "Has Type" ++ "title": "Until" ++ } ++ }, ++ "title": "list_sessionsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_tags", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" + }, +- "latest": { ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -1792,27 +4144,69 @@ + } + ], + "default": null, +- "title": "Latest" +- }, ++ "title": "Origin" ++ } ++ }, ++ "title": "list_tagsArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "list_workspaces", ++ "parameters": { ++ "properties": { + "limit": { +- "default": 10, ++ "default": 50, + "minimum": 1, + "title": "Limit", + "type": "integer" + }, +- "max_messages": { ++ "max_chars_per_item": { ++ "anyOf": [ ++ { ++ "minimum": 1, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Chars Per Item" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ } ++ }, ++ "title": "list_workspacesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "maintenance_execute", ++ "parameters": { ++ "properties": { ++ "dry_run": { ++ "default": false, ++ "title": "Dry Run", ++ "type": "boolean" ++ }, ++ "failure_kind": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Max Messages" ++ "title": "Failure Kind" + }, +- "message_type": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -1822,39 +4216,36 @@ + } + ], + "default": null, +- "title": "Message Type" ++ "title": "Origin" + }, +- "min_messages": { ++ "parser_version": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Min Messages" ++ "title": "Parser Version" + }, +- "min_words": { ++ "session_ids": { + "anyOf": [ + { +- "type": "integer" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Min Words" +- }, +- "offset": { +- "default": 0, +- "minimum": 0, +- "title": "Offset", +- "type": "integer" ++ "title": "Session Ids" + }, +- "provider": { ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -1864,9 +4255,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Since" + }, +- "referenced_path": { ++ "source_family": { + "anyOf": [ + { + "type": "string" +@@ -1876,9 +4267,9 @@ + } + ], + "default": null, +- "title": "Referenced Path" ++ "title": "Source Family" + }, +- "repo": { ++ "source_root": { + "anyOf": [ + { + "type": "string" +@@ -1888,38 +4279,53 @@ + } + ], + "default": null, +- "title": "Repo" ++ "title": "Source Root" + }, +- "retrieval_lane": { ++ "targets": { + "anyOf": [ + { +- "type": "string" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Retrieval Lane" +- }, +- "reverse": { +- "default": false, +- "title": "Reverse", +- "type": "boolean" ++ "title": "Targets" + }, +- "sample": { ++ "until": { + "anyOf": [ + { +- "type": "integer" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Sample" +- }, +- "similar_text": { ++ "title": "Until" ++ } ++ }, ++ "title": "maintenance_executeArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "maintenance_list", ++ "parameters": { ++ "properties": {}, ++ "title": "maintenance_listArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "maintenance_preview", ++ "parameters": { ++ "properties": { ++ "failure_kind": { + "anyOf": [ + { + "type": "string" +@@ -1929,9 +4335,9 @@ + } + ], + "default": null, +- "title": "Similar Text" ++ "title": "Failure Kind" + }, +- "since": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -1941,9 +4347,9 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Origin" + }, +- "since_session": { ++ "parser_version": { + "anyOf": [ + { + "type": "string" +@@ -1953,21 +4359,24 @@ + } + ], + "default": null, +- "title": "Since Session" ++ "title": "Parser Version" + }, +- "since_session_id": { ++ "session_ids": { + "anyOf": [ + { +- "type": "string" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Since Session Id" ++ "title": "Session Ids" + }, +- "sort": { ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -1977,9 +4386,9 @@ + } + ], + "default": null, +- "title": "Sort" ++ "title": "Since" + }, +- "tag": { ++ "source_family": { + "anyOf": [ + { + "type": "string" +@@ -1989,9 +4398,9 @@ + } + ], + "default": null, +- "title": "Tag" ++ "title": "Source Family" + }, +- "title": { ++ "source_root": { + "anyOf": [ + { + "type": "string" +@@ -2001,24 +4410,22 @@ + } + ], + "default": null, +- "title": "Title" ++ "title": "Source Root" + }, +- "tool": { ++ "targets": { + "anyOf": [ + { +- "type": "string" ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Tool" +- }, +- "typed_only": { +- "default": false, +- "title": "Typed Only", +- "type": "boolean" ++ "title": "Targets" + }, + "until": { + "anyOf": [ +@@ -2033,48 +4440,47 @@ + "title": "Until" + } + }, +- "title": "list_sessionsArguments", ++ "title": "maintenance_previewArguments", + "type": "object" + } + }, + { +- "name": "list_corrections", ++ "name": "maintenance_status", + "parameters": { + "properties": { +- "session_id": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Session Id" +- }, +- "kind": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Kind" ++ "operation_id": { ++ "title": "Operation Id", ++ "type": "string" + } + }, +- "title": "list_correctionsArguments", ++ "required": [ ++ "operation_id" ++ ], ++ "title": "maintenance_statusArguments", + "type": "object" + } + }, + { +- "name": "list_marks", ++ "name": "named_source_freshness", + "parameters": { + "properties": { +- "session_id": { ++ "source_path": { ++ "title": "Source Path", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "source_path" ++ ], ++ "title": "named_source_freshnessArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "neighbor_candidates", ++ "parameters": { ++ "properties": { ++ "id": { + "anyOf": [ + { + "type": "string" +@@ -2084,21 +4490,15 @@ + } + ], + "default": null, +- "title": "Session Id" ++ "title": "Id" + }, +- "mark_type": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Mark Type" ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" + }, +- "message_id": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -2108,9 +4508,9 @@ + } + ], + "default": null, +- "title": "Message Id" ++ "title": "Origin" + }, +- "target_id": { ++ "query": { + "anyOf": [ + { + "type": "string" +@@ -2120,9 +4520,33 @@ + } + ], + "default": null, +- "title": "Target Id" ++ "title": "Query" + }, +- "target_type": { ++ "window_hours": { ++ "default": 24, ++ "title": "Window Hours", ++ "type": "integer" ++ } ++ }, ++ "title": "neighbor_candidatesArguments", ++ "type": "object" ++ } ++ }, ++ { ++ "name": "provider_usage", ++ "parameters": { ++ "properties": { ++ "detail": { ++ "default": "full", ++ "title": "Detail", ++ "type": "string" ++ }, ++ "limit": { ++ "default": 25, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -2132,34 +4556,39 @@ + } + ], + "default": null, +- "title": "Target Type" ++ "title": "Origin" + } + }, +- "title": "list_marksArguments", +- "type": "object" +- } +- }, +- { +- "name": "list_recall_packs", +- "parameters": { +- "properties": {}, +- "title": "list_recall_packsArguments", ++ "title": "provider_usageArguments", + "type": "object" + } + }, + { +- "name": "list_saved_views", +- "parameters": { +- "properties": {}, +- "title": "list_saved_viewsArguments", +- "type": "object" +- } +- }, +- { +- "name": "list_tags", ++ "name": "query_completions", + "parameters": { + "properties": { +- "provider": { ++ "field": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Field" ++ }, ++ "incomplete": { ++ "default": "", ++ "title": "Incomplete", ++ "type": "string" ++ }, ++ "kind": { ++ "title": "Kind", ++ "type": "string" ++ }, ++ "unit": { + "anyOf": [ + { + "type": "string" +@@ -2169,46 +4598,33 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Unit" + } + }, +- "title": "list_tagsArguments", +- "type": "object" +- } +- }, +- { +- "name": "list_workspaces", +- "parameters": { +- "properties": {}, +- "title": "list_workspacesArguments", ++ "required": [ ++ "kind" ++ ], ++ "title": "query_completionsArguments", + "type": "object" + } + }, + { +- "name": "maintenance_execute", ++ "name": "query_units", + "parameters": { + "properties": { +- "session_ids": { ++ "action": { + "anyOf": [ + { +- "items": { +- "type": "string" +- }, +- "type": "array" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Session Ids" +- }, +- "dry_run": { +- "default": false, +- "title": "Dry Run", +- "type": "boolean" ++ "title": "Action" + }, +- "failure_kind": { ++ "action_sequence": { + "anyOf": [ + { + "type": "string" +@@ -2218,9 +4634,9 @@ + } + ], + "default": null, +- "title": "Failure Kind" ++ "title": "Action Sequence" + }, +- "parser_version": { ++ "action_text": { + "anyOf": [ + { + "type": "string" +@@ -2230,9 +4646,9 @@ + } + ], + "default": null, +- "title": "Parser Version" ++ "title": "Action Text" + }, +- "provider": { ++ "cwd_prefix": { + "anyOf": [ + { + "type": "string" +@@ -2242,9 +4658,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Cwd Prefix" + }, +- "since": { ++ "exclude_action": { + "anyOf": [ + { + "type": "string" +@@ -2254,9 +4670,9 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Exclude Action" + }, +- "source_family": { ++ "exclude_origin": { + "anyOf": [ + { + "type": "string" +@@ -2266,9 +4682,9 @@ + } + ], + "default": null, +- "title": "Source Family" ++ "title": "Exclude Origin" + }, +- "source_root": { ++ "exclude_tag": { + "anyOf": [ + { + "type": "string" +@@ -2278,24 +4694,40 @@ + } + ], + "default": null, +- "title": "Source Root" ++ "title": "Exclude Tag" + }, +- "targets": { ++ "exclude_tool": { + "anyOf": [ + { +- "items": { +- "type": "string" +- }, +- "type": "array" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Targets" ++ "title": "Exclude Tool" + }, +- "until": { ++ "expression": { ++ "title": "Expression", ++ "type": "string" ++ }, ++ "has_paste_evidence": { ++ "default": false, ++ "title": "Has Paste Evidence", ++ "type": "boolean" ++ }, ++ "has_thinking": { ++ "default": false, ++ "title": "Has Thinking", ++ "type": "boolean" ++ }, ++ "has_tool_use": { ++ "default": false, ++ "title": "Has Tool Use", ++ "type": "boolean" ++ }, ++ "has_type": { + "anyOf": [ + { + "type": "string" +@@ -2305,53 +4737,41 @@ + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "title": "maintenance_executeArguments", +- "type": "object" +- } +- }, +- { +- "name": "maintenance_list", +- "parameters": { +- "properties": {}, +- "title": "maintenance_listArguments", +- "type": "object" +- } +- }, +- { +- "name": "maintenance_preview", +- "parameters": { +- "properties": { +- "session_ids": { ++ "title": "Has Type" ++ }, ++ "limit": { ++ "default": 10, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "max_messages": { + "anyOf": [ + { +- "items": { +- "type": "string" +- }, +- "type": "array" ++ "minimum": 0, ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Session Ids" ++ "title": "Max Messages" + }, +- "failure_kind": { ++ "max_words": { + "anyOf": [ + { +- "type": "string" ++ "minimum": 0, ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Failure Kind" ++ "title": "Max Words" + }, +- "parser_version": { ++ "message_type": { + "anyOf": [ + { + "type": "string" +@@ -2361,33 +4781,41 @@ + } + ], + "default": null, +- "title": "Parser Version" ++ "title": "Message Type" + }, +- "provider": { ++ "min_messages": { + "anyOf": [ + { +- "type": "string" ++ "minimum": 0, ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Provider" ++ "title": "Min Messages" + }, +- "since": { ++ "min_words": { + "anyOf": [ + { +- "type": "string" ++ "minimum": 0, ++ "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Since" ++ "title": "Min Words" + }, +- "source_family": { ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -2397,9 +4825,9 @@ + } + ], + "default": null, +- "title": "Source Family" ++ "title": "Origin" + }, +- "source_root": { ++ "project": { + "anyOf": [ + { + "type": "string" +@@ -2409,24 +4837,21 @@ + } + ], + "default": null, +- "title": "Source Root" ++ "title": "Project" + }, +- "targets": { ++ "referenced_path": { + "anyOf": [ + { +- "items": { +- "type": "string" +- }, +- "type": "array" ++ "type": "string" + }, + { + "type": "null" + } + ], + "default": null, +- "title": "Targets" ++ "title": "Referenced Path" + }, +- "until": { ++ "repo": { + "anyOf": [ + { + "type": "string" +@@ -2436,34 +4861,9 @@ + } + ], + "default": null, +- "title": "Until" +- } +- }, +- "title": "maintenance_previewArguments", +- "type": "object" +- } +- }, +- { +- "name": "maintenance_status", +- "parameters": { +- "properties": { +- "operation_id": { +- "title": "Operation Id", +- "type": "string" +- } +- }, +- "required": [ +- "operation_id" +- ], +- "title": "maintenance_statusArguments", +- "type": "object" +- } +- }, +- { +- "name": "neighbor_candidates", +- "parameters": { +- "properties": { +- "id": { ++ "title": "Repo" ++ }, ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -2473,15 +4873,21 @@ + } + ], + "default": null, +- "title": "Id" ++ "title": "Since" + }, +- "limit": { +- "default": 10, +- "minimum": 1, +- "title": "Limit", +- "type": "integer" ++ "tag": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Tag" + }, +- "provider": { ++ "title": { + "anyOf": [ + { + "type": "string" +@@ -2491,9 +4897,9 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Title" + }, +- "query": { ++ "tool": { + "anyOf": [ + { + "type": "string" +@@ -2503,15 +4909,30 @@ + } + ], + "default": null, +- "title": "Query" ++ "title": "Tool" + }, +- "window_hours": { +- "default": 24, +- "title": "Window Hours", +- "type": "integer" ++ "typed_only": { ++ "default": false, ++ "title": "Typed Only", ++ "type": "boolean" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" + } + }, +- "title": "neighbor_candidatesArguments", ++ "required": [ ++ "expression" ++ ], ++ "title": "query_unitsArguments", + "type": "object" + } + }, +@@ -2519,10 +4940,6 @@ + "name": "raw_artifacts", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, + "limit": { + "default": 50, + "minimum": 1, +@@ -2534,6 +4951,10 @@ + "minimum": 0, + "title": "Offset", + "type": "integer" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" + } + }, + "required": [ +@@ -2587,9 +5008,29 @@ + "name": "record_correction", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" ++ "author_kind": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Kind" ++ }, ++ "author_ref": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Author Ref" + }, + "kind": { + "title": "Kind", +@@ -2613,6 +5054,10 @@ + }, + "title": "Payload", + "type": "object" ++ }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" + } + }, + "required": [ +@@ -2628,10 +5073,6 @@ + "name": "remove_mark", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, + "mark_type": { + "title": "Mark Type", + "type": "string" +@@ -2648,6 +5089,10 @@ + "default": null, + "title": "Message Id" + }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, + "target_id": { + "anyOf": [ + { +@@ -2695,6 +5140,22 @@ + "type": "object" + } + }, ++ { ++ "name": "resolve_ref", ++ "parameters": { ++ "properties": { ++ "ref": { ++ "title": "Ref", ++ "type": "string" ++ } ++ }, ++ "required": [ ++ "ref" ++ ], ++ "title": "resolve_refArguments", ++ "type": "object" ++ } ++ }, + { + "name": "save_annotation", + "parameters": { +@@ -2703,10 +5164,6 @@ + "title": "Annotation Id", + "type": "string" + }, +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, + "message_id": { + "anyOf": [ + { +@@ -2723,6 +5180,10 @@ + "title": "Note Text", + "type": "string" + }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, + "target_id": { + "anyOf": [ + { +@@ -2950,7 +5411,7 @@ + "default": null, + "title": "Exclude Action" + }, +- "exclude_provider": { ++ "exclude_origin": { + "anyOf": [ + { + "type": "string" +@@ -2960,7 +5421,7 @@ + } + ], + "default": null, +- "title": "Exclude Provider" ++ "title": "Exclude Origin" + }, + "exclude_tag": { + "anyOf": [ +@@ -2998,9 +5459,9 @@ + "default": null, + "title": "Exclude Tool" + }, +- "has_paste": { ++ "has_paste_evidence": { + "default": false, +- "title": "Has Paste", ++ "title": "Has Paste Evidence", + "type": "boolean" + }, + "has_thinking": { +@@ -3025,17 +5486,15 @@ + "default": null, + "title": "Has Type" + }, ++ "include_affordances": { ++ "default": false, ++ "title": "Include Affordances", ++ "type": "boolean" ++ }, + "latest": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Latest" ++ "default": false, ++ "title": "Latest", ++ "type": "boolean" + }, + "limit": { + "default": 10, +@@ -3046,6 +5505,7 @@ + "max_messages": { + "anyOf": [ + { ++ "minimum": 0, + "type": "integer" + }, + { +@@ -3055,6 +5515,19 @@ + "default": null, + "title": "Max Messages" + }, ++ "max_words": { ++ "anyOf": [ ++ { ++ "minimum": 0, ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Max Words" ++ }, + "message_type": { + "anyOf": [ + { +@@ -3070,6 +5543,7 @@ + "min_messages": { + "anyOf": [ + { ++ "minimum": 0, + "type": "integer" + }, + { +@@ -3082,6 +5556,7 @@ + "min_words": { + "anyOf": [ + { ++ "minimum": 0, + "type": "integer" + }, + { +@@ -3097,7 +5572,19 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "project": { + "anyOf": [ + { + "type": "string" +@@ -3107,7 +5594,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Project" + }, + "query": { + "title": "Query", +@@ -3166,7 +5653,7 @@ + "default": null, + "title": "Sample" + }, +- "similar_text": { ++ "similar_session_id": { + "anyOf": [ + { + "type": "string" +@@ -3176,9 +5663,9 @@ + } + ], + "default": null, +- "title": "Similar Text" ++ "title": "Similar Session Id" + }, +- "since": { ++ "similar_text": { + "anyOf": [ + { + "type": "string" +@@ -3188,9 +5675,9 @@ + } + ], + "default": null, +- "title": "Since" ++ "title": "Similar Text" + }, +- "since_session": { ++ "since": { + "anyOf": [ + { + "type": "string" +@@ -3200,7 +5687,7 @@ + } + ], + "default": null, +- "title": "Since Session" ++ "title": "Since" + }, + "since_session_id": { + "anyOf": [ +@@ -3291,18 +5778,6 @@ + "name": "session_costs", + "parameters": { + "properties": { +- "session_id": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Session Id" +- }, + "limit": { + "default": 50, + "minimum": 1, +@@ -3327,7 +5802,19 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Origin" ++ }, ++ "session_id": { + "anyOf": [ + { + "type": "string" +@@ -3337,7 +5824,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Session Id" + }, + "since": { + "anyOf": [ +@@ -3400,30 +5887,6 @@ + "name": "session_phases", + "parameters": { + "properties": { +- "session_id": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Session Id" +- }, +- "kind": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Kind" +- }, + "limit": { + "default": 50, + "minimum": 1, +@@ -3436,7 +5899,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -3446,7 +5909,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "session_date_since": { + "anyOf": [ +@@ -3472,6 +5935,18 @@ + "default": null, + "title": "Session Date Until" + }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, + "since": { + "anyOf": [ + { +@@ -3586,7 +6061,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -3596,7 +6071,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "query": { + "anyOf": [ +@@ -3713,7 +6188,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -3723,7 +6198,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "query": { + "anyOf": [ +@@ -3786,18 +6261,6 @@ + "name": "session_work_events", + "parameters": { + "properties": { +- "session_id": { +- "anyOf": [ +- { +- "type": "string" +- }, +- { +- "type": "null" +- } +- ], +- "default": null, +- "title": "Session Id" +- }, + "heuristic_label": { + "anyOf": [ + { +@@ -3822,7 +6285,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -3832,7 +6295,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "query": { + "anyOf": [ +@@ -3870,6 +6333,18 @@ + "default": null, + "title": "Session Date Until" + }, ++ "session_id": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Session Id" ++ }, + "since": { + "anyOf": [ + { +@@ -3903,14 +6378,14 @@ + "name": "set_metadata", + "parameters": { + "properties": { +- "session_id": { +- "title": "Session Id", +- "type": "string" +- }, + "key": { + "title": "Key", + "type": "string" + }, ++ "session_id": { ++ "title": "Session Id", ++ "type": "string" ++ }, + "value": { + "title": "Value", + "type": "string" +@@ -3933,6 +6408,63 @@ + "type": "object" + } + }, ++ { ++ "name": "threads", ++ "parameters": { ++ "properties": { ++ "limit": { ++ "default": 50, ++ "minimum": 1, ++ "title": "Limit", ++ "type": "integer" ++ }, ++ "offset": { ++ "default": 0, ++ "minimum": 0, ++ "title": "Offset", ++ "type": "integer" ++ }, ++ "query": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Query" ++ }, ++ "since": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since" ++ }, ++ "until": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Until" ++ } ++ }, ++ "title": "threadsArguments", ++ "type": "object" ++ } ++ }, + { + "name": "tool_call_latency_distribution", + "parameters": { +@@ -3942,7 +6474,7 @@ + "title": "Limit", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -3952,7 +6484,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "since": { + "anyOf": [ +@@ -4035,7 +6567,7 @@ + "title": "Offset", + "type": "integer" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -4045,7 +6577,19 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" ++ }, ++ "since_ms": { ++ "anyOf": [ ++ { ++ "type": "integer" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Since Ms" + }, + "tool": { + "anyOf": [ +@@ -4084,22 +6628,39 @@ + } + }, + { +- "name": "work_threads", ++ "name": "usage_timeline", + "parameters": { + "properties": { ++ "group_by": { ++ "default": "month-origin-model", ++ "title": "Group By", ++ "type": "string" ++ }, + "limit": { + "default": 50, + "minimum": 1, + "title": "Limit", + "type": "integer" + }, ++ "model": { ++ "anyOf": [ ++ { ++ "type": "string" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "default": null, ++ "title": "Model" ++ }, + "offset": { + "default": 0, + "minimum": 0, + "title": "Offset", + "type": "integer" + }, +- "query": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -4109,7 +6670,7 @@ + } + ], + "default": null, +- "title": "Query" ++ "title": "Origin" + }, + "since": { + "anyOf": [ +@@ -4136,7 +6697,7 @@ + "title": "Until" + } + }, +- "title": "work_threadsArguments", ++ "title": "usage_timelineArguments", + "type": "object" + } + }, +@@ -4149,7 +6710,7 @@ + "title": "Group By", + "type": "string" + }, +- "provider": { ++ "origin": { + "anyOf": [ + { + "type": "string" +@@ -4159,7 +6720,7 @@ + } + ], + "default": null, +- "title": "Provider" ++ "title": "Origin" + }, + "since": { + "anyOf": [ +diff --git a/tests/infra/mcp.py b/tests/infra/mcp.py +index 258ab5e84f088709969191f6152a1c96033cded5..d0bb3aca76e9a3a86c81892d948789c1bdcd773a 100644 +--- a/tests/infra/mcp.py ++++ b/tests/infra/mcp.py +@@ -10,114 +10,10 @@ from unittest.mock import AsyncMock, MagicMock + + from polylogue.archive.models import Session + from polylogue.core.enums import Provider ++from polylogue.mcp.declarations import expected_tool_names + from tests.infra.builders import make_conv, make_msg + +-EXPECTED_TOOL_NAMES = { +- "search", +- "query_units", +- "resolve_ref", +- "explain_import", +- "list_sessions", +- "compile_context", +- "get_context_delivery", +- "build_context_image", +- "blackboard_list", +- "blackboard_post", +- "capture_assertion_candidate", +- "import_annotation_batch", +- "join_typed_annotations", +- "list_assertion_claims", +- "list_assertion_candidates", +- "list_assertion_candidate_reviews", +- "judge_assertion_candidate", +- "judge_assertion_candidates", +- "neighbor_candidates", +- "stats", +- "embedding_status", +- "embedding_preflight", +- "facets", +- "agent_coordination", +- "add_tag", +- "remove_tag", +- "bulk_tag_sessions", +- "list_tags", +- "list_marks", +- "add_mark", +- "remove_mark", +- "list_annotations", +- "save_annotation", +- "delete_annotation", +- "list_saved_views", +- "save_saved_view", +- "delete_saved_view", +- "get_metadata", +- "set_metadata", +- "delete_metadata", +- "delete_session", +- "get_session_summary", +- "get_session_tree", +- "get_session_topology", +- "get_logical_session", +- "get_stats_by", +- "list_read_view_profiles", +- "explain_query_expression", +- "query_completions", +- "readiness_check", +- "named_source_freshness", +- "rebuild_index", +- "update_index", +- "get_postmortem_bundle", +- "get_pathologies", +- "rebuild_session_insights", +- "maintenance_preview", +- "maintenance_execute", +- "maintenance_status", +- "maintenance_list", +- "session_profile", +- "session_latency_profile", +- "tool_call_latency_distribution", +- "find_stuck_sessions", +- "workflow_shape_distribution", +- "find_abandoned_sessions", +- "get_resume_brief", +- "find_resume_candidates", +- "aggregate_sessions", +- "compare_sessions", +- "find_similar_sessions", +- "correlate_session", +- "correlate_sessions", +- "session_tool_timing", +- "cost_outlook", +- "archive_debt", +- "tool_usage", +- "session_costs", +- "cost_rollups", +- "usage_timeline", +- "session_profiles", +- "session_work_events", +- "session_phases", +- "session_tag_rollups", +- "threads", +- "archive_coverage", +- "action_affordances", +- "archive_get_session", +- "archive_list_sessions", +- "archive_search_sessions", +- "clear_corrections", +- "compose_context_preamble", +- "delete_recall_pack", +- "delete_workspace", +- "get_messages", +- "insight_rigor_audit", +- "list_corrections", +- "list_recall_packs", +- "list_workspaces", +- "provider_usage", +- "raw_artifacts", +- "record_correction", +- "save_recall_pack", +- "save_workspace", +-} ++EXPECTED_TOOL_NAMES = set(expected_tool_names()) + + EXPECTED_RESOURCE_URIS = { + "polylogue://stats", +diff --git a/tests/unit/mcp/test_envelope_contracts.py b/tests/unit/mcp/test_envelope_contracts.py +index 5b3d829d15b02992223625a52063dbf48796ce89..eb513be58711b0b28345aa869caf4a202d4cca23 100644 +--- a/tests/unit/mcp/test_envelope_contracts.py ++++ b/tests/unit/mcp/test_envelope_contracts.py +@@ -22,6 +22,7 @@ from typing import Any, cast + import pytest + from pydantic import BaseModel + ++from polylogue.mcp.declarations import MCP_TOOL_REGISTRY + from tests.infra.mcp import EXPECTED_TOOL_NAMES, MCPServerUnderTest + + # --------------------------------------------------------------------------- +@@ -214,6 +215,11 @@ class TestRegistryWideClassification: + unclassified = EXPECTED_TOOL_NAMES - set(TOOL_CONTRACT.keys()) + assert not unclassified, f"EXPECTED_TOOL_NAMES not in TOOL_CONTRACT: {sorted(unclassified)}" + ++ def test_declaration_output_contracts_project_the_independent_matrix(self) -> None: ++ for name, contract in TOOL_CONTRACT.items(): ++ expected = "envelope:" + ",".join(sorted(contract[1])) if isinstance(contract, tuple) else contract ++ assert MCP_TOOL_REGISTRY[name].output_contract == expected, name ++ + + # --------------------------------------------------------------------------- + # Envelope payload shape — Pydantic class fields match the matrix. +diff --git a/tests/unit/mcp/test_read_tool_declarations.py b/tests/unit/mcp/test_read_tool_declarations.py +new file mode 100644 +index 0000000000000000000000000000000000000000..edcfbeb785e482f132175a428108f5408637ccef +--- /dev/null ++++ b/tests/unit/mcp/test_read_tool_declarations.py +@@ -0,0 +1,202 @@ ++"""Executable parity and real-route proofs for the MCP declaration migration.""" ++ ++from __future__ import annotations ++ ++import json ++from pathlib import Path ++from types import SimpleNamespace ++from typing import cast ++from unittest.mock import patch ++ ++import pytest ++ ++from polylogue.api import Polylogue ++from polylogue.archive.message.roles import Role ++from polylogue.core.enums import BlockType, Provider ++from polylogue.mcp.declarations import ( ++ MCP_MIGRATED_READ_TOOL_NAMES, ++ MCP_TOOL_REGISTRY, ++ tool_declaration_rows, ++) ++from polylogue.mcp.declarations.registration import MCP_DECLARATION_ATTRIBUTE ++from polylogue.mcp.server import build_server ++from polylogue.mcp.server_support import MCPRole, role_allows ++from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession ++from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore ++from tests.infra.mcp import EXPECTED_TOOL_NAMES, MCPServerUnderTest, invoke_surface_async ++ ++ROOT = Path(__file__).resolve().parents[3] ++DECLARATION_FIXTURE = ROOT / "tests/data/witnesses/mcp-tool-declarations.json" ++SCHEMA_FIXTURE = ROOT / "tests/data/witnesses/mcp-tool-schemas.json" ++CONTRACT_FIXTURE = ROOT / "tests/data/witnesses/mcp-tool-contracts.json" ++ ++ ++def _load_rows(path: Path) -> list[dict[str, object]]: ++ value = json.loads(path.read_text(encoding="utf-8")) ++ assert isinstance(value, list) ++ return cast(list[dict[str, object]], value) ++ ++ ++async def _live_contract_rows_async() -> list[dict[str, object]]: ++ rows: list[dict[str, object]] = [] ++ for tool in sorted(await build_server(role="admin").list_tools(), key=lambda item: item.name): ++ rows.append( ++ { ++ "name": tool.name, ++ "required_role": MCP_TOOL_REGISTRY[tool.name].required_role, ++ "description": tool.description, ++ "input_schema": tool.inputSchema, ++ "output_schema": tool.outputSchema, ++ "annotations": None if tool.annotations is None else tool.annotations.model_dump(mode="json"), ++ } ++ ) ++ return rows ++ ++ ++def _seed_lineage_archive(archive_root: Path) -> tuple[str, str]: ++ """Plant independent search and topology facts for the real-route proof.""" ++ ++ with ArchiveStore(archive_root) as archive: ++ parent_id = archive.write_parsed( ++ ParsedSession( ++ source_name=Provider.CODEX, ++ provider_session_id="declaration-parent", ++ title="Declaration Parent", ++ messages=[ ++ ParsedMessage( ++ provider_message_id="parent-message", ++ role=Role.USER, ++ text="declaration route needle", ++ blocks=[ ++ ParsedContentBlock( ++ type=BlockType.TEXT, ++ text="declaration route needle", ++ ) ++ ], ++ ) ++ ], ++ ) ++ ) ++ child_id = archive.write_parsed( ++ ParsedSession( ++ source_name=Provider.CODEX, ++ provider_session_id="declaration-child", ++ title="Declaration Child", ++ parent_session_provider_id="declaration-parent", ++ messages=[ ++ ParsedMessage( ++ provider_message_id="child-message", ++ role=Role.ASSISTANT, ++ text="child continuation", ++ blocks=[ParsedContentBlock(type=BlockType.TEXT, text="child continuation")], ++ ) ++ ], ++ ) ++ ) ++ return parent_id, child_id ++ ++ ++def test_declaration_fixture_pins_the_complete_inventory() -> None: ++ fixture_rows = _load_rows(DECLARATION_FIXTURE) ++ ++ assert fixture_rows == list(tool_declaration_rows()) ++ assert {str(row["name"]) for row in fixture_rows} == EXPECTED_TOOL_NAMES ++ assert len(fixture_rows) == len(EXPECTED_TOOL_NAMES) == 104 ++ assert sum(bool(row["registered_from_declaration"]) for row in fixture_rows) == 56 ++ ++ ++@pytest.mark.parametrize("role", ["read", "write", "review", "admin"]) ++def test_role_discovery_is_projected_from_the_fixed_declaration_witness(role: MCPRole) -> None: ++ rows = _load_rows(DECLARATION_FIXTURE) ++ expected = {str(row["name"]) for row in rows if role_allows(role, cast(MCPRole, row["required_role"]))} ++ server = cast(MCPServerUnderTest, build_server(role=role)) ++ ++ assert set(server._tool_manager._tools) == expected ++ ++ ++@pytest.mark.asyncio ++async def test_live_fastmcp_contracts_match_the_committed_wire_witness() -> None: ++ contract_rows = await _live_contract_rows_async() ++ schema_rows = [{"name": row["name"], "parameters": row["input_schema"]} for row in contract_rows] ++ ++ assert contract_rows == _load_rows(CONTRACT_FIXTURE) ++ assert schema_rows == _load_rows(SCHEMA_FIXTURE) ++ ++ ++def test_every_migrated_handler_was_registered_through_its_declaration() -> None: ++ server = cast(MCPServerUnderTest, build_server(role="read")) ++ ++ assert len(MCP_MIGRATED_READ_TOOL_NAMES) == 56 ++ for name in MCP_MIGRATED_READ_TOOL_NAMES: ++ handler = server._tool_manager._tools[name].fn ++ declaration = MCP_TOOL_REGISTRY[name] ++ assert getattr(handler, MCP_DECLARATION_ATTRIBUTE) is declaration ++ assert handler.__name__ == declaration.name ++ assert handler.__doc__ == declaration.description ++ ++ ++@pytest.mark.asyncio ++async def test_declared_query_topology_and_timeline_tools_execute_real_production_routes(tmp_path: Path) -> None: ++ """Exercise FastMCP registration plus ArchiveStore/Polylogue operations. ++ ++ Production dependencies under test: ++ - ``search`` -> ``archive_search_payload`` -> ArchiveStore query/FTS; ++ - ``resolve_ref`` -> Polylogue facade -> canonical ObjectRef resolution; ++ - ``get_session_topology`` -> Polylogue facade -> topology archive reads; ++ - ``usage_timeline`` -> insight registry -> Polylogue usage-timeline operation. ++ """ ++ ++ archive_root = tmp_path / "archive" ++ parent_id, child_id = _seed_lineage_archive(archive_root) ++ config = SimpleNamespace(archive_root=archive_root, db_path=archive_root / "index.db") ++ polylogue = Polylogue(archive_root=archive_root, db_path=archive_root / "index.db") ++ server = cast(MCPServerUnderTest, build_server(role="read")) ++ ++ try: ++ with ( ++ patch("polylogue.mcp.server._get_config", return_value=config), ++ patch("polylogue.mcp.server._get_polylogue", return_value=polylogue), ++ ): ++ search_result = await invoke_surface_async( ++ server._tool_manager._tools["search"].fn, ++ query="declaration route needle", ++ limit=5, ++ ) ++ resolved_ref_result = await invoke_surface_async( ++ server._tool_manager._tools["resolve_ref"].fn, ++ ref=f"session:{parent_id}", ++ ) ++ missing_ref_result = await invoke_surface_async( ++ server._tool_manager._tools["resolve_ref"].fn, ++ ref="session:declaration-missing", ++ ) ++ topology_result = await invoke_surface_async( ++ server._tool_manager._tools["get_session_topology"].fn, ++ session_id=child_id, ++ ) ++ timeline_result = await invoke_surface_async( ++ server._tool_manager._tools["usage_timeline"].fn, ++ limit=1, ++ ) ++ finally: ++ await polylogue.close() ++ ++ search = json.loads(search_result) ++ resolved_ref = json.loads(resolved_ref_result) ++ missing_ref = json.loads(missing_ref_result) ++ topology = json.loads(topology_result) ++ timeline = json.loads(timeline_result) ++ ++ assert search["total"] == 1 ++ assert search["hits"][0]["session"]["id"] == parent_id ++ assert resolved_ref["normalized_ref"] == f"session:{parent_id}" ++ assert resolved_ref["resolved"] is True ++ assert resolved_ref["payload"]["id"] == parent_id ++ assert resolved_ref["object_refs"] == [f"session:{parent_id}"] ++ assert missing_ref["resolved"] is False ++ assert missing_ref["payload"] is None ++ assert missing_ref["caveats"] == ["session not found"] ++ assert topology["target_id"] == child_id ++ assert topology["root_id"] == parent_id ++ assert [node["session_id"] for node in topology["thread"]] == [parent_id, child_id] ++ assert timeline == {"total": 0, "items": []} +diff --git a/tests/unit/mcp/test_tool_contracts.py b/tests/unit/mcp/test_tool_contracts.py +index f3f07e96f2c3a9c6ffb82b463e20ad631a1f7237..f653209b3b0e16df79325f5fd60fabc890f1af27 100644 +--- a/tests/unit/mcp/test_tool_contracts.py ++++ b/tests/unit/mcp/test_tool_contracts.py +@@ -61,9 +61,9 @@ from polylogue.mcp.archive_support import ( + archive_search_payload, + archive_session_list_payload, + ) ++from polylogue.mcp.declarations import MCP_MIGRATED_READ_TOOL_NAMES, MCP_TOOL_REGISTRY + from polylogue.mcp.payloads import MCPRootPayload + from polylogue.mcp.server_support import MCP_RESPONSE_BUDGET_BYTES, _json_payload +-from polylogue.mcp.server_tools import _MCP_READ_TOOL_SPECS + from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + from polylogue.surfaces.payloads import TagMutationResult +@@ -1371,11 +1371,12 @@ class TestGetSessionSummaryTool: + + + class TestReadViewProfilesTool: +- def test_read_tools_use_registered_specs(self, mcp_server: MCPServerUnderTest) -> None: +- for name, spec in _MCP_READ_TOOL_SPECS.items(): ++ def test_migrated_read_tools_use_registered_declarations(self, mcp_server: MCPServerUnderTest) -> None: ++ for name in MCP_MIGRATED_READ_TOOL_NAMES: ++ declaration = MCP_TOOL_REGISTRY[name] + tool = mcp_server._tool_manager._tools[name] +- assert tool.fn.__name__ == spec.name +- assert tool.fn.__doc__ == spec.description ++ assert tool.fn.__name__ == declaration.name ++ assert tool.fn.__doc__ == declaration.description + + def test_list_read_view_profiles_uses_facade_contract(self, mcp_server: MCPServerUnderTest) -> None: + profiles = read_view_profile_payloads() +diff --git a/tests/unit/mcp/test_tool_discovery.py b/tests/unit/mcp/test_tool_discovery.py +index f6e2c16863fce1394c91721d9c08fdcae9104c3a..2207759dcd1b3939204c4286fe58e81af379741b 100644 +--- a/tests/unit/mcp/test_tool_discovery.py ++++ b/tests/unit/mcp/test_tool_discovery.py +@@ -16,6 +16,7 @@ from typing import cast + + import pytest + ++from polylogue.mcp.declarations import MCP_MIGRATED_READ_TOOL_NAMES, MCP_TOOL_REGISTRY + from polylogue.mcp.server import build_server + from tests.infra.mcp import MCPServerUnderTest, invoke_surface + +@@ -26,81 +27,30 @@ _SYNTHETIC_CONV_ID = "test:conv-discovery-nonexistent" + #: with no kwargs and must return a valid JSON envelope (empty results, stats + #: for an empty archive, etc.). + _KNOWN_MINIMAL: dict[str, dict[str, object]] = { +- "search": {"query": "hello", "limit": 1}, +- "query_units": {"expression": "messages where text:hello", "limit": 1}, +- "resolve_ref": {"ref": f"session:{_SYNTHETIC_CONV_ID}"}, +- "compile_context": {"seed_ref": f"session:{_SYNTHETIC_CONV_ID}", "read_views": "messages"}, +- "get_context_delivery": { +- "snapshot_ref": "context-snapshot:test:conv-discovery-nonexistent:explicit-recall", +- "recipient_ref": "agent:discovery", +- }, +- "blackboard_list": {}, +- "get_session_summary": {"id": _SYNTHETIC_CONV_ID}, +- "get_messages": {"session_id": _SYNTHETIC_CONV_ID, "limit": 1}, +- "get_session_tree": {"session_id": _SYNTHETIC_CONV_ID}, +- "get_session_topology": {"session_id": _SYNTHETIC_CONV_ID}, +- "get_logical_session": {"session_id": _SYNTHETIC_CONV_ID}, +- "session_profile": {"session_id": _SYNTHETIC_CONV_ID}, +- "session_latency_profile": {"session_id": _SYNTHETIC_CONV_ID}, +- "get_resume_brief": {"session_id": _SYNTHETIC_CONV_ID}, +- "raw_artifacts": {"session_id": _SYNTHETIC_CONV_ID}, +- "session_costs": {"session_id": _SYNTHETIC_CONV_ID}, +- "cost_outlook": {"plan": "claude-pro"}, +- "find_resume_candidates": {"repo_path": "/tmp/nonexistent"}, +- "aggregate_sessions": {"session_ids": "test:a,test:b"}, +- "compose_context_preamble": {"context": "{}"}, +- "compare_sessions": {"session_ids": "test:a,test:b"}, +- "find_similar_sessions": {"query": "test"}, +- "correlate_session": {"session_id": _SYNTHETIC_CONV_ID}, +- "correlate_sessions": {"session_ids": "test:a,test:b"}, +- "session_tool_timing": {"session_id": _SYNTHETIC_CONV_ID}, +- "facets": {"limit": 1}, +- "neighbor_candidates": {"query": "test"}, +- "insight_rigor_audit": {}, +- "list_sessions": {"limit": 1}, +- "archive_get_session": {"session_id": _SYNTHETIC_CONV_ID}, +- "archive_list_sessions": {"limit": 1}, +- "archive_search_sessions": {"query": "hello", "limit": 1}, +- "session_profiles": {"limit": 1}, +- "session_work_events": {"limit": 1}, +- "session_phases": {"limit": 1}, +- "session_tag_rollups": {"limit": 1}, +- "threads": {"limit": 1}, +- "archive_coverage": {"limit": 1}, +- "archive_debt": {"limit": 1}, +- "explain_import": {"raw_ref": "raw:discovery-nonexistent", "limit": 1}, +- "cost_rollups": {"limit": 1}, +- "tool_usage": {"limit": 1}, +- "tool_call_latency_distribution": {"limit": 1}, +- "workflow_shape_distribution": {"limit": 1}, +- "find_stuck_sessions": {"limit": 1}, +- "find_abandoned_sessions": {"limit": 1}, +- "get_stats_by": {"group_by": "origin"}, +- "list_read_view_profiles": {}, +- "list_assertion_claims": {"target_ref": f"session:{_SYNTHETIC_CONV_ID}", "limit": 1}, +- "list_assertion_candidates": {"limit": 1}, +- "list_assertion_candidate_reviews": {"limit": 1}, +- "join_typed_annotations": { +- "schema_id": "discovery", +- "schema_version": 1, +- "statuses": ["candidate"], +- "limit": 1, +- }, +- "named_source_freshness": {"source_path": "/tmp/polylogue-discovery-nonexistent"}, +- "explain_query_expression": {"expression": "repo:polylogue"}, +- "query_completions": {"kind": "field", "incomplete": "d"}, +- "action_affordances": {}, +- "agent_coordination": {}, +- "usage_timeline": {"limit": 1}, +- "embedding_status": {}, +- "embedding_preflight": {}, +- "build_context_image": {"project_repo": "test"}, +- "readiness_check": {}, +- "stats": {}, +- "get_postmortem_bundle": {}, +- "get_pathologies": {"limit": 1}, +- "provider_usage": {"limit": 1}, ++ name: dict(MCP_TOOL_REGISTRY[name].minimal_arguments) for name in MCP_MIGRATED_READ_TOOL_NAMES + } ++_KNOWN_MINIMAL.update( ++ { ++ "compile_context": {"seed_ref": f"session:{_SYNTHETIC_CONV_ID}", "read_views": "messages"}, ++ "get_context_delivery": { ++ "snapshot_ref": "context-snapshot:test:conv-discovery-nonexistent:explicit-recall", ++ "recipient_ref": "agent:discovery", ++ }, ++ "blackboard_list": {}, ++ "compose_context_preamble": {"context": "{}"}, ++ "list_assertion_claims": {"target_ref": f"session:{_SYNTHETIC_CONV_ID}", "limit": 1}, ++ "list_assertion_candidates": {"limit": 1}, ++ "list_assertion_candidate_reviews": {"limit": 1}, ++ "join_typed_annotations": { ++ "schema_id": "discovery", ++ "schema_version": 1, ++ "statuses": ["candidate"], ++ "limit": 1, ++ }, ++ "agent_coordination": {}, ++ "build_context_image": {"project_repo": "test"}, ++ } ++) + + + def _discover_read_tool_names() -> frozenset[str]: diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/TESTS.md new file mode 100644 index 000000000..ef6ac3929 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-03/a01/extracted/TESTS.md @@ -0,0 +1,211 @@ +# Beads 03 test and verification record + +## Test design + +The tests are split into four independent proof layers. + +1. **Fixed declaration parity.** A committed 104-row declaration witness is compared to the production registry. This prevents the runtime server and expected inventory from drifting together unnoticed. +2. **Live FastMCP wire parity.** The real admin server is listed through FastMCP and compared to a committed witness containing description, input schema, output schema, annotations, and required role. A separate schema witness remains compatible with existing consumers. +3. **Registration anti-bypass.** Every one of the 56 migrated handlers must carry the exact declaration object installed by `register_declared_tool`; direct `@mcp.tool()` registration or a wrapper that bypasses the adapter fails. +4. **Real operations route.** A seeded archive is read through registered FastMCP handlers, not descriptor methods: search reaches ArchiveStore/FTS, ObjectRef resolution reaches the Polylogue facade/archive, topology reaches lineage reads, and usage timeline reaches the insight registry/facade operation. + +Existing contract/discovery/envelope tests remain in place and continue to exercise pagination, continuation, error, absence, role, dynamic signature, and output-shape behavior. + +## Production dependencies exercised + +### `search` + +Route: FastMCP handler -> `server_tools.search` -> `archive_search_payload` / canonical query request -> ArchiveStore/FTS. + +Planted oracle: only the parent session contains `declaration route needle`; expected total is 1 and the returned canonical session ID is independently known from `ArchiveStore.write_parsed`. + +### `resolve_ref` + +Route: FastMCP handler -> `Polylogue.resolve_ref` -> ObjectRef parsing and ArchiveStore point read. + +Planted oracle: `session:` resolves to the planted parent, preserves its canonical normalized ref and object-ref list; `session:declaration-missing` remains a typed unresolved payload with no fabricated object. + +### `get_session_topology` + +Route: FastMCP handler -> `Polylogue.get_session_topology` -> real archive lineage reads. + +Planted oracle: the child was written with the parent's provider identity, so root and ordered thread are known from input facts. + +### `usage_timeline` + +Route: dynamic insight registration -> insight registry -> real Polylogue usage-timeline operation. + +Planted oracle: the archive has no usage rows, so the typed envelope is exactly `{"total": 0, "items": []}`. + +## Anti-vacuity mutations + +- Delete or rename a declaration row: the fixed 104-row witness and exact inventory test fail. +- Change a declaration role: role-scoped discovery fails against the fixed declaration witness. +- Restore a direct decorator or bypass the declaration adapter: the migrated-handler marker test fails. +- Add a generic `*args/**kwargs` wrapper or change a signature: the live FastMCP input/output schema witness fails. +- Change a description, annotation, output model, or role: the full wire-contract witness fails. +- Remove the FTS/query call or return a stubbed search payload: the seeded search assertion fails. +- Normalize ObjectRefs differently or collapse absence into an exception/error: the real resolve-ref assertions fail. +- Remove parent traversal or sort topology incorrectly: the planted root/thread assertions fail. +- Bypass the insight registry/facade for the generic timeline tool: the real-route test or handler declaration marker fails. +- Reintroduce a hidden page cap, wrong cursor, wrong offset origin, or not-found exception: the existing search/message/envelope regression selections fail. +- Change any generated witness without running its renderer: `render mcp-contract-fixtures --check` fails. + +## Commands and results + +### Formatting, lint, and typing + +```text +.venv/bin/ruff format --check <17 changed Python files> +17 files already formatted + +.venv/bin/ruff check <17 changed Python files> +All checks passed! + +.venv/bin/mypy polylogue/mcp/declarations \ + polylogue/mcp/server_tools.py \ + polylogue/mcp/server_insight_tools.py \ + devtools/render_mcp_contract_fixtures.py \ + tests/unit/mcp/test_read_tool_declarations.py +Success: no issues found in 8 source files +``` + +### Declaration, wire, discovery, and legacy MCP contracts + +```text +pytest -q tests/unit/mcp/test_read_tool_declarations.py \ + tests/unit/mcp/test_envelope_contracts.py +43 passed in 10.76s + +pytest -q tests/unit/mcp/test_tool_discovery.py +69 passed in 19.17s + +pytest -q \ + tests/unit/mcp/test_tool_contracts.py::TestReadViewProfilesTool \ + tests/unit/mcp/test_tool_contracts.py::TestInsightTools \ + tests/unit/mcp/test_tool_contracts.py::TestArchiveTools \ + tests/unit/mcp/test_tool_contracts.py::TestStatsTool \ + tests/unit/mcp/test_tool_contracts.py::TestGetSessionSummaryTool \ + tests/unit/mcp/test_tool_contracts.py::TestQueryExplanationTool \ + tests/unit/mcp/test_tool_contracts.py::TestQueryCompletionsTool \ + tests/unit/mcp/test_tool_contracts.py::TestActionAffordancesTool +36 passed in 18.86s + +pytest -q \ + tests/unit/mcp/test_tool_contracts.py::TestQueryTools \ + tests/unit/mcp/test_tool_contracts.py::test_mcp_search_params_match_query_spec +32 passed in 17.60s + +pytest -q tests/unit/mcp/test_tool_contracts.py::TestMutationTools +30 passed in 12.21s +``` + +The three bounded `test_tool_contracts.py` lanes collect all 98 tests in that file and passed. They were split because a combined command exceeded the execution window despite continuing to report passing progress. + +```text +pytest -q \ + tests/unit/mcp/test_server_surfaces.py::TestServerSurfaceRegistration \ + tests/unit/mcp/test_server_surfaces.py::TestArchiveGenericToolSurfaces +29 passed in 13.75s +``` + +Focused pagination/absence selection after the final ObjectRef test change: + +```text +pytest -q \ + tests/unit/mcp/test_tool_contracts.py::TestQueryTools::test_search_total_and_cursor_reflect_more_native_hits \ + tests/unit/mcp/test_tool_contracts.py::TestGetSessionSummaryTool::test_get_messages_rejects_unknown_offset_from \ + tests/unit/mcp/test_tool_contracts.py::TestGetSessionSummaryTool::test_get_not_found \ + tests/unit/mcp/test_tool_contracts.py::TestGetSessionSummaryTool::test_get_messages_large_filtered_offset_returns_actionable_note \ + tests/unit/mcp/test_tool_contracts.py::TestMutationTools::test_summary_not_found +5 passed in 4.06s +``` + +Final direct run of the new real-route/declaration file: + +```text +pytest -q tests/unit/mcp/test_read_tool_declarations.py +8 passed in 4.38s +``` + +### Generated surfaces and docs reachability + +```text +python -m devtools render mcp-contract-fixtures --check +render mcp-contract-fixtures: sync OK + +python -m devtools render mcp-tool-index --check +render mcp-tool-index: sync OK + +python -m devtools render devtools-reference --check +render devtools-reference: sync OK: docs/devtools.md + +python -m devtools render topology-projection --check +Wrote docs/plans/topology-target.yaml with 1011 rows. TBD: 9. + +python -m devtools render topology-status --check +exit 0 + +python -m devtools verify docs-coverage +docs-coverage: every public CLI command, MCP tool, config key, and stable route is reachable + +pytest -q \ + tests/unit/devtools/test_command_catalog.py \ + tests/unit/devtools/test_generated_surfaces.py \ + tests/unit/devtools/test_render_devtools_reference.py \ + tests/unit/devtools/test_lineage_validation.py +18 passed in 2.71s +``` + +The topology projection command rewrites its output even when passed `--check`; a second run produced identical file hashes. The generated changes include the four new declaration modules and current-source line-count refreshes. + +### Exact before/after discovery and wire comparison + +Using the installed FastMCP SDK, a clean base worktree and the patched worktree were listed directly for every role. + +```text +read 66 tools: ordered discovery exact +write 95 tools: ordered discovery exact +review 97 tools: ordered discovery exact +admin 104 tools: ordered discovery exact +``` + +A pre-change surface capture with SHA-256 `f37109eaf1b3fe37c97da43e32a75cbe38133f2cb4be00e8bd5a27c84b40e336` was also compared by name. For every role, descriptions, input schemas, output schemas, and annotations were exactly equal. + +### Patch/apply proof + +Against a fresh detached worktree at `f654480cadb7cc4c194704e24dfd483199547b35`: + +```text +git apply --check --binary PATCH.diff +pass + +git apply --index --binary PATCH.diff +pass + +git diff --cached --check +pass + +regenerated staged binary diff compared with PATCH.diff +exact byte equality: pass + +PYTHONPATH= pytest -q tests/unit/mcp/test_read_tool_declarations.py +8 passed in 4.45s + +PYTHONPATH= python -m devtools render mcp-contract-fixtures --check +render mcp-contract-fixtures: sync OK +``` + +### Aggregate timeout disclosure + +A one-shot command containing all changed MCP/devtools suites was attempted. It emitted 118 passing progress markers and no failure, then was terminated by the execution timeout before completion. No aggregate success is claimed; the relevant files/classes were completed in the bounded lanes above. + +## Not run / unverified + +- Full repository test suite. +- `devtools verify --quick` or `--all` under the repository's canonical Nix environment. +- Live daemon, browser, extension, real archive, secrets, or deployed MCP server. +- Incident-scale memory/cancellation/disconnect/resource-cleanup replay. +- Cold-model discovery trials. +- Verification against the unavailable accepted `beads-02` result package. +- A generated Python semantic-operation parity matrix; current source has no such renderer/artifact and `polylogue-s1kr` remains open. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..f281b9afd --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/EVIDENCE.md @@ -0,0 +1,246 @@ +# Source, Beads, test, and history evidence + +## Authority order used + +1. Supplied mission `04-mcp-context-migration.md`. +2. Supplied Polylogue project-state snapshot and exact current source at `f654480cadb7cc4c194704e24dfd483199547b35`. +3. Current repository guidance (`AGENTS.md` symlinked to `CLAUDE.md`, plus `TESTING.md`). +4. Current `.beads/issues.jsonl`, including later notes that supersede older design wording. +5. Bundled Git history and all refs. +6. The supplied “slightly stale” Testsuite Diet as adversarial architecture/test-design evidence, never as code authority. + +No web source was needed. No live operator system was available. + +## Snapshot evidence + +The supplied `polylogue-manifest.json` records: + +- project `polylogue`; +- source `/realm/project/polylogue`; +- generated at `2026-07-17T043202Z`; +- branch `master`; +- commit `f654480cadb7cc4c194704e24dfd483199547b35`; +- `dirty=true`. + +The bundled Git repository establishes: + +- `HEAD` and `origin/master` are the same commit; +- branch divergence is `0 0`; +- the bundled branch-delta file list, log, and patch are empty; +- commit subject is `chore(beads): file archive-insight benchmark findings wofr/vhjs/r7p6/1wtm`; +- commit timestamp is `2026-07-17T03:45:52+02:00`. + +The working-tree tar was overlaid onto a clone of the all-refs bundle before implementation. The tracked overlay produced no Git diff. Therefore the manifest's dirty state likely came from ignored or untracked local state that the supplied metadata does not identify as an apply-ready patch. This package names the exact commit as the patch base and does not fabricate a dirty-patch identity. + +A separate clean worktree at the base commit proved that the live baseline contains 104 admin tools and that `tests/infra/mcp.py` expects exactly those 104. Role counts at the base are: + +- read: 66; +- write: 95; +- review: 97; +- admin: 104. + +## Repository instructions inspected + +`CLAUDE.md` / `AGENTS.md` established the implementation discipline used here: + +- substrate/domain owners define semantics; MCP stays a leaf adapter; +- existing typed interfaces are preferred over parallel frameworks; +- added Python modules require topology projection/status regeneration; +- `devtools verify --quick` consists of format, lint, MyPy, and generated-surface checks; +- tests must exercise production dependencies and generated surfaces must remain synchronized. + +`TESTING.md` was inspected for pytest/devtools conventions, isolated scratch behavior, and focused-versus-broad verification expectations. + +## Production source inspected + +### MCP composition, roles, and discovery + +- `polylogue/mcp/server.py` +- `polylogue/mcp/server_support.py` +- `polylogue/mcp/server_tools.py` +- `polylogue/mcp/server_context_tools.py` +- `polylogue/mcp/server_mutation_tools.py` +- `polylogue/mcp/server_personal_state_tools.py` +- `polylogue/mcp/server_maintenance_tools.py` +- `polylogue/mcp/server_insight_tools.py` +- `tests/infra/mcp.py` + +Findings: + +- roles are monotonic `read < write < review < admin` through `role_allows`; +- existing family-level gates controlled write/review/admin registration; +- exact public names and handlers were already distributed across the modules above; +- the current baseline is 104 tools, not the 103 in older Bead prose; +- call telemetry is emitted by the existing `ServerCallbacks.async_safe_call` route, so retaining exact handlers preserves per-tool call attribution. + +### Candidate assertion and judgment ownership + +- candidate capture and review handlers in `server_mutation_tools.py`; +- `polylogue/api` assertion facade methods; +- assertion payloads/storage and candidate judgment lifecycle; +- context preamble guidance filtering and candidate safety tests; +- tests for candidate capture, assertion judgment, blackboard, and context preamble. + +Findings: + +- agent-authored assertion material enters candidate state and is non-injecting; +- reviewer judgment is a distinct review-role route carrying an actor and decision; +- active/injecting state is the result of judgment, not candidate authorship; +- current source already has the typed durable lifecycle needed for a real MCP roundtrip test. + +### Annotation schema and batch provenance + +- `polylogue/annotations/schema.py` +- `polylogue/annotations/write.py` +- `polylogue/annotations/importer.py` +- durable user-tier annotation schema/batch repository and migrations; +- MCP annotation import handler and its existing tests. + +Findings: + +- schemas are versioned and immutable by id/version/fingerprint; +- batches are independent provenance containers; assertion rows remain assertions; +- import records schema identity, source result, actor/model/prompt references, counts, and validation outcomes; +- external-agent rows remain candidate/non-injected through the shared assertion write chokepoint. + +### Personal state, corrections, and maintenance + +- user-state facade methods for tags, marks, annotations, views, recall packs, workspaces, metadata, and corrections; +- maintenance planner, envelope, scope filter, registry, and MCP routes; +- operations declarations/action contracts and the current direct adapter paths. + +Findings: + +- these families have materially different result/idempotency contracts and should not be collapsed into one generic handler; +- `maintenance_preview` and `maintenance_execute(dry_run=True)` share real scope-filter/planner semantics; +- ten tools named by `polylogue-jn40` lacked the interim confirmation gate; +- `delete_session` already supplied the local fail-closed pattern; +- the existing operation declarations are not yet executable cross-surface authority. + +## Beads evidence + +### `polylogue-t46.8` — protocol-native MCP algebra + +Status: open, P1. It requires one executable semantic inventory, generated role-scoped discovery/contracts, preservation of result semantics and bounded execution, negative authority tests, telemetry before deletion, and a target 10–15 default read transaction surface. It explicitly says MCP must remain a leaf adapter and must not create a parallel parser, query engine, or policy layer. + +### `polylogue-t46.8.1` — declaration/equivalence kernel + +Status: open, P1. It explicitly names: + +- `polylogue/mcp/declarations/models.py`; +- `polylogue/mcp/declarations/registry.py`; +- fields for verb/object, role, result semantics, canonical plan, minimal invocation, discovery, continuation, resource/prompt alternative, compatibility route, workflow coverage, telemetry, and deprecation. In this provisional implementation, canonical plans are exact existing owner symbols; no resource URI is claimed, and the only prompt alternative names the already registered `resume_context` prompt; +- generated registration/contracts/discovery/expected inventory/equivalence evidence; +- no legacy tool deletion in this kernel slice. + +This is the direct source authorization for the minimal prerequisite included in the patch. + +### `polylogue-t46.8.2` — read migration + +Status: open, P1, and a blocker for `t46.8.3`. It owns query/read/get/explain migration, bounded shared query execution, resources, alias retirement, cancellation/cleanup, cold-model route proof, and the default read-surface budget. It has not landed in the supplied authority, so this patch does not pretend to deliver its read collapse or use it as an available dependency. + +### `polylogue-t46.8.3` — this family migration + +Status: open, P2. It requires thin context/write/judge/run/maintenance adapters over typed owners, resource receipt/status objects, prompts without instruction authority, candidate/judged preservation, actor context, dry-run/authorize/apply/receipt/reconcile lifecycles, idempotency, role isolation, and deletion only after equivalence/authorization/recovery proof. + +The patch implements the strongest compatible subset available before `t46.8.2`: declaration-driven exact-handler registration, trust/provenance/role contracts, real candidate lifecycle proof, and interim maintenance/destructive gates. It does not claim final alias deletion, a resource surface, a new prompt recipe, or operation-executor authority. + +### `polylogue-jn40` — interim confirmation mitigation + +Status: open, P2 bug. It names exactly ten previously unprotected tools and says to apply the same `confirm: bool = False` guard pattern used by `delete_session`. Its 2026-07-16 note is explicit that this is a cheap interim fail-closed mitigation and that `polylogue-t46.9` owns the structural preview-token/receipt solution. + +The patch implements all ten named gates and preserves dry-run planning for `maintenance_execute`. + +### `polylogue-t46.9` — executable operation authority + +Status: open, P1 feature. It requires one `OperationSpec`-to-handler inventory and `OperationExecutor` across CLI/API/MCP/daemon/maintenance, actor/archive/target-digest-bound preview tokens, stale-preview rejection, receipts, and bypass tests. It also says `jn40` booleans are interim compatibility only. + +The patch avoids creating a generic mutation or maintenance executor and documents this residual obligation. + +### `polylogue-rxdo.7.1` and `polylogue-rxdo.7.2` + +Both are closed and establish the current annotation substrate: + +- `.7.1` persisted immutable annotation schemas and independent batch provenance; +- `.7.2` landed candidate-only JSONL import through shared production operation plus CLI/MCP, live target/evidence validation, independent batches, typed query, and adjudication. + +These records justify preserving rather than reimplementing the current importer/storage path. + +## Git history inspected + +Relevant commits and what they establish: + +- `d66f97f43` — split personal-state MCP registration; current module boundary is intentional. +- `5aa34e6c5` — reviewed candidate judgment flow; candidate versus judgment semantics are current production behavior. +- `73959d471` — context delivery receipts; receipt provenance is a real source owner. +- `246c48d08` — durable annotation schema/batch provenance. +- `f4504cb4d` — provenance-stamped JSONL annotation batch import. +- `4ed0cf2dc` — typed annotation joins to structural targets. +- `54af5477c` and `eff7c2abe` — durable MCP call telemetry and delivery; exact handler telemetry identity matters. +- `113d1af97` — bounded MCP response payloads; declarations must preserve bounded result semantics. +- `56eaa2245` — prior duplicate MCP tool retirement, showing deletions are performed when equivalence is known rather than retained indefinitely. +- `fd7b35492` — interruptible/admission-controlled query execution; read transaction mechanics exist but are owned by the unfinished read migration. +- `c2fd1e902` — excision bypass repair; supports the architecture warning that one direct destructive bypass defeats declaration-only safety. + +No bundled Git ref contained the new `polylogue/mcp/declarations/` package or an accepted `beads-02` result. + +## Tests inspected + +The implementation followed dependencies beyond the new test file. Inspected and executed test families included: + +- role/discovery, server surfaces, runtime, error isolation, call telemetry, and contracts; +- candidate capture, assertion judgment, annotation import/join, blackboard, context image/preamble; +- all personal-state and idempotency contracts; +- maintenance envelope and scope parity; +- query/insight/logical-session/tool-timing tests to detect composition regressions; +- generated topology and repository layering/test-infra checks. + +Every one of the 713 MCP unit tests passed in isolated file partitions. The complete command ledger and the aggregate-process limitation are in `TESTS.md`. + +## Slightly stale Testsuite Diet evidence + +The supplied Testsuite Diet was used as adversarial design evidence only. + +`architecture/04-destructive-and-authentication-boundaries.md` recommends an executable `OperationExecutor`, distinguishes reversible writes, judgment/replacement, destructive scoped effects, and broad maintenance, and states that a Boolean `confirm=true` is only a compatibility adapter rather than authority proof. This directly supports keeping the current interim guards separate and refusing to invent the future executor in this patch. + +`architecture/07-evidence-provenance-and-public-algebra.md` requires surfaces to project one canonical domain fact without strengthening authority, and treats agent-declared versus judged authority as distinct. This supports declaration metadata and the real candidate/judgment lifecycle test, not a generic fact store. + +`17-unresolved-architecture-scope.md` says architecture decisions do not make packets executable automatically: current symbols, write sets, historical seeds, and mutation witnesses still must be reconciled. It also ranks destructive/authentication bypass as a high-risk branch and identifies rewrite-native MCP work as still requiring sizing and implementation. + +## Contradictions and resolutions + +### Accepted `beads-02` dependency is absent + +Mission: depends on an accepted kernel. Supplied authority: no result package or landed kernel exists. Resolution: implement only the minimal model/registry/registrar shape explicitly authorized by `t46.8.1`, mark it provisional, and preserve tests/data so it can be adapted rather than discarded. + +### Tool count says 103 in Bead prose, 104 in source + +Current clean source and expected inventory both produce 104 admin tools. Resolution: current source wins; the patch preserves 104 and migrates 41. + +### Mission asks to delete superseded glue; Beads prohibit premature tool deletion + +`t46.8.1` says no legacy tool deletion in the kernel slice, and `t46.8.3` requires telemetry/equivalence/authorization/recovery before deletion. Resolution: delete only superseded family role gates and 41 duplicate expected-name literals. Retain public compatibility routes with explicit metadata. + +### Interim Boolean confirmation versus final authorization architecture + +`jn40` requires Boolean confirmation now; `t46.9` and Testsuite Diet say it is insufficient as structural authority. Resolution: land exact interim gates, test them before backend access, preserve dry-run, and avoid claiming final operation authorization. + +### Manifest dirty marker versus empty tracked delta + +Manifest says dirty; branch delta and exported tracked overlay are clean. Resolution: name the exact commit as patch base and report the unknown dirty state as unavailable rather than inventing a patch. + +### Full aggregate test process versus constituent passes + +One-process/aggregate MCP runs stalled; every collected file passed in partitions, 713/713. Resolution: report both facts. Do not convert the aggregate stall into a pass or infer a product failure without evidence. + +## Evidence that would falsify this design + +The package should be reconsidered if any of the following becomes available: + +- an accepted `beads-02` package with incompatible declaration or registration semantics; +- a current Bead note changing roles/injection/provenance for any of the 41 tools; +- source showing a migrated handler has another registration call site not covered by `register_tools`; +- production telemetry proving a compatibility route is safe to delete or must remain; +- a completed `t46.9` executor that makes the interim Boolean gates obsolete; +- a reproduction showing the full-suite aggregate stall is introduced by this patch rather than an environmental/global-test interaction; +- a live deployed MCP contract showing client schema incompatibility that requires a staged compatibility adapter. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/HANDOFF.md new file mode 100644 index 000000000..4debbd3f1 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/HANDOFF.md @@ -0,0 +1,201 @@ +# beads-04 MCP context and assertion algebra migration — handoff + +## Mission and delivered result + +This package is an apply-ready implementation draft for `beads-04`. It migrates the current MCP context, assertion, personal-state/recall, correction, judgment, and maintenance families onto one executable declaration and role-scoped registration seam while preserving each existing production handler and substrate owner. It also closes the source-authorized interim confirmation gap for ten destructive or rebuild tools, keeps candidate agent material distinct from operator judgment, pins immutable annotation schema/batch provenance, and proves a real candidate-write/review/readback lifecycle through the registered MCP surface and an isolated durable `user.db`. + +The patch deliberately does **not** introduce a generic mutation or maintenance executor. The declaration registrar validates and registers exact handlers; it never wraps, invokes, dispatches, authorizes, or audits their effects. `polylogue-t46.9` remains the owner of the future cross-surface `OperationExecutor`, preview-token, stale-target, and durable receipt authority. + +## Snapshot identity and patch base + +| Field | Value | +|---|---| +| Project | Polylogue | +| Snapshot source | `/realm/project/polylogue` as recorded by the supplied project-state manifest | +| Snapshot generated | `2026-07-17T043202Z` | +| Branch | `master` | +| Base commit | `f654480cadb7cc4c194704e24dfd483199547b35` | +| Commit subject | `chore(beads): file archive-insight benchmark findings wofr/vhjs/r7p6/1wtm` | +| Commit date | `2026-07-17T03:45:52+02:00` | +| Bundled `origin/master` delta | `0 / 0`; branch-delta patch and file list are empty | +| Manifest dirty marker | `true` | +| Recoverable tracked dirty patch | None. The exported tracked working tree overlaid on the bundled commit with an empty Git diff. The manifest does not identify the ignored/untracked source of `dirty=true`, so this package does not claim or reproduce it. | + +`PATCH.diff` is based directly on the named commit. It was checked and applied in a separate detached worktree at that commit, followed by `git diff --check`, an import/discovery smoke, and the new eleven-test declaration/lifecycle suite. + +A clean baseline runtime census found 66 read-role, 95 write-role, 97 review-role, and 104 admin-role tools. This contradicts the stale 103-tool count in `polylogue-t46.8`; current source and `tests/infra/mcp.py` both establish 104, and this patch preserves all 104 names. + +## Dependency condition + +The mission names the accepted `beads-02` algebra/equivalence kernel as a dependency. No `beads-02` result package, landed declaration package, or accepted kernel was present in the supplied archives, all bundled Git refs, current source, or available uploaded-file evidence. The source Bead `polylogue-t46.8.1` nevertheless explicitly authorizes `polylogue/mcp/declarations/models.py` and `registry.py` and defines their required fields. + +Accordingly, this patch includes the smallest source-authorized prerequisite kernel needed for a coherent real migration: + +- immutable typed declaration records; +- a registry for this disjoint 41-tool family; +- deterministic equivalence rendering; +- role-scoped exact-handler registration with fail-closed completeness checks. + +This is the package's largest material limitation. If the accepted `beads-02` kernel exists outside the supplied authority and differs in model shape or registration API, a substantial reconciliation pass will be required rather than a cosmetic repair. + +## Implemented mechanism + +### Executable declarations + +`polylogue/mcp/declarations/` adds an immutable declaration model, a 41-entry registry, deterministic equivalence JSON, and a registration adapter. Every migrated declaration records: + +- public name and exact production module; +- semantic family, verb, object kind, required MCP role, and result semantics; +- canonical substrate owner and canonical plan identifier, both pinned to an exact existing production symbol in this provisional kernel; +- canonical minimal invocation; +- discovery and continuation guidance; +- optional resource/prompt alternatives, with no resource URI claimed until a resolver exists and only the already registered `resume_context` prompt referenced; +- compatibility route, workflow coverage, and per-tool telemetry key; +- injection policy, provenance contract, confirmation policy, and lifecycle. + +The registry contains 3 context, 3 assertion, 23 personal-state, 3 correction, 2 judgment, and 7 maintenance tools. Required-role counts are 3 read, 29 write, 2 review, and 7 admin. + +### Registration and discovery ownership + +`MCPDeclarationRegistrar` replaces the four family-level role gates for the migrated families. Each registration module is now always composed, but each exact handler is registered only when `role_allows(current_role, declaration.required_role)` succeeds. Startup fails if a declaration lacks a handler, a handler lacks a declaration, a handler moves modules, an async handler becomes sync, a minimal invocation drifts from its signature, or a declared confirmation parameter loses its fail-closed default. + +The registrar returns the original function unchanged. FastMCP therefore sees the exact existing handler, public name, signature, documentation, error envelope, telemetry callback, and substrate call path. There is no second execution authority. + +`tests/infra/mcp.py` now owns only the 63 unmigrated literals and unions the 41 declaration names. This removes duplicate discovery-name authority for the migrated family while retaining the exact 104-tool admin inventory. + +### Trust, injection, and provenance contracts + +The declarations and real-route test pin these boundaries: + +- `capture_assertion_candidate`, `import_annotation_batch`, and `blackboard_post` are write-role candidate material and cannot self-promote or inject. +- Candidate capture preserves `author_ref`, `author_kind=agent`, evidence refs, and scope refs. +- Annotation import preserves immutable `batch_id`, `schema_id`, `schema_version`, `source_result_ref`, `actor_ref`, `model_ref`, and `prompt_ref`; rows remain ordinary candidate assertions while batches remain independent provenance containers. +- `judge_assertion_candidate` and `judge_assertion_candidates` remain review-role operator-judgment paths and preserve the reviewer actor. +- Context delivery/image reads remain non-injecting receipts or bounded context; the preamble declaration records that active guidance is operator-judgment-derived rather than candidate-authored authority. +- No resource URI is claimed because the current server has no matching resolver. The only prompt alternative is the already registered read-only `resume_context` prompt for `compose_context_preamble`; this patch creates no prompt/resource path that can acquire mutation authority. + +### Interim destructive and maintenance authorization + +The ten tools named by `polylogue-jn40` now fail closed before session resolution, facade access, planner construction, or mutation unless their distinct handler receives the required confirmation: + +- `remove_tag` +- `remove_mark` +- `delete_annotation` +- `delete_saved_view` +- `delete_recall_pack` +- `delete_workspace` +- `delete_metadata` +- `maintenance_execute` +- `rebuild_index` +- `rebuild_session_insights` + +The existing `delete_session` gate is retained and its error now carries the same typed `confirmation_required` code. `maintenance_execute(dry_run=True)` remains usable without confirmation and calls the real planner with `dry_run=True`; non-dry execution requires `confirm=True`. + +These are intentionally separate checks inside the existing handlers. They are an interim compatibility contract, not the preview-bound authorization proof required by `polylogue-t46.9`. + +### Real user-tier lifecycle proof + +The new production-route test creates an isolated archive session and durable `user.db`, then performs: + +1. write-role MCP `capture_assertion_candidate` by an agent; +2. read-role MCP `list_assertion_candidates`, proving candidate/non-injected readback; +3. review-role MCP `judge_assertion_candidate` with an operator actor and `inject=True`; +4. read-role MCP `list_assertion_claims`, proving the accepted active claim and injection policy. + +The test uses registered FastMCP functions and the real `Polylogue` facade/storage path. Only the server composition root is patched to point at the isolated real facade; candidate, judgment, persistence, and readback operations are not mocked. + +## Changed files + +### New production and generated files + +- `polylogue/mcp/declarations/__init__.py` +- `polylogue/mcp/declarations/models.py` +- `polylogue/mcp/declarations/registry.py` +- `polylogue/mcp/declarations/registration.py` +- `docs/generated/mcp-context-assertion-equivalence.json` + +### Modified production composition and handlers + +- `polylogue/mcp/server_tools.py` +- `polylogue/mcp/server_context_tools.py` +- `polylogue/mcp/server_mutation_tools.py` +- `polylogue/mcp/server_personal_state_tools.py` +- `polylogue/mcp/server_maintenance_tools.py` + +### Modified discovery, generated topology, and tests + +- `tests/infra/mcp.py` +- `tests/unit/mcp/test_context_assertion_declarations.py` +- `tests/unit/mcp/test_per_tool_contracts.py` +- `tests/unit/mcp/test_tag_idempotency.py` +- `tests/unit/mcp/test_tool_contracts.py` +- `tests/unit/mcp/test_user_state_tools.py` +- `tests/unit/maintenance/test_envelope_contracts.py` +- `docs/plans/topology-target.yaml` +- `docs/topology-status.md` + +No existing test or helper was deleted. No complete replacement files are required; `PATCH.diff` is unambiguous, so this package intentionally omits `FILES/`. + +## Acceptance matrix + +| Mission requirement | Result | Evidence | +|---|---|---| +| Migrate context/recall/assertion/correction/maintenance families onto the algebra | Strong coherent subset | 41 executable declarations drive role-scoped registration and discovery inventory. Missing accepted dependency makes the kernel provisional. | +| Preserve materially different write, trust, injection, authorization, and audit contracts | Met for migrated handlers | Exact handlers remain substrate adapters; declaration validation, role discovery tests, existing telemetry path, and lifecycle test cover the distinctions. | +| Keep agent candidates distinct from operator judgment | Met | Candidate-only declarations plus real write/read/review/readback durable test. | +| Preserve immutable annotation schema/batch provenance | Met | Registry validation and tests pin all durable schema/batch provenance fields; importer/storage owner is unchanged. | +| Delete superseded glue and update discovery/contracts | Partially met by safe deletion | Four family role gates and 41 duplicate expected-name literals are removed. Public compatibility tools are retained because deletion telemetry, cold-model equivalence, and the `t46.8.2` prerequisite are absent. | +| Exercise real user-tier writes and readback through MCP | Met | Isolated real `user.db` candidate lifecycle test passes. | +| Do not collapse destructive/maintenance authorization into a generic handler | Met | Eleven independently implemented confirmation gates are tested before backend access; registrar never executes handlers. | +| Preserve maintenance dry-run/apply distinction | Met | Dry-run reaches real planner without confirm; non-dry apply fails closed without confirm. | +| Update generated discovery/equivalence evidence | Met for this family | Deterministic 41-entry JSON artifact, inventory union, topology regeneration, drift test. | +| Remove old tools after equivalence/telemetry | Not attempted by design | Source Beads require telemetry/cold-model proof first; compatibility routes remain explicitly marked `retained_compatibility`. | +| Deliver target 10–15 default read algebra | Outside this disjoint slice | Owned by unfinished `t46.8.2`; this patch supplies only a provisional `t46.8.1`-shaped declaration prerequisite, and current role inventories remain unchanged. | +| Preview-token/receipt authority for destructive actions | Not implemented | Explicitly owned by `polylogue-t46.9`; boolean confirmation is only the authorized interim mitigation. | + +## Apply order + +1. Start from a clean worktree at `f654480cadb7cc4c194704e24dfd483199547b35`. +2. Run `git apply --check PATCH.diff`. +3. Apply with `git apply PATCH.diff`. +4. Run `git diff --check`. +5. Run the declaration/lifecycle test first: `pytest -q tests/unit/mcp/test_context_assertion_declarations.py`. +6. Run the focused contract partitions listed in `TESTS.md`. +7. Run strict lint/type checks and generated-surface checks listed in `TESTS.md`. +8. Before merge, reconcile this provisional declaration model with any accepted `beads-02` package unavailable here. If that kernel is different, preserve the 41 declaration facts and tests while adapting the model/registrar API. + +## Verification performed + +The exact command ledger and anti-vacuity mutations are in `TESTS.md`. The final verified highlights are: + +- patch applied cleanly in an independent detached base worktree; +- applied-tree `git diff --check` passed; +- applied-tree import/discovery smoke reported 41 declarations and 104 exact admin tools; +- applied-tree new declaration/lifecycle suite: 11 passed; +- every one of the 713 tests under `tests/unit/mcp` passed when partitioned by file into isolated pytest processes; +- focused maintenance envelope/scope suite: 49 passed; +- changed-file Ruff format and lint: passed; +- strict MyPy over all nine changed production modules: passed; +- generated equivalence artifact byte-drift test: passed; +- `render all --check`, topology projection/status checks, layering check, topology check, and test-infra currency check: passed. + +One all-in-one `pytest -q tests/unit/mcp` process and one xdist aggregate stalled after partial progress despite every constituent file passing in partitions. No assertion failure was emitted. This is recorded as an unresolved suite-order/resource interaction, not converted into a pass. A full `devtools verify --quick` rerun with the virtual-environment tools on `PATH` passed Ruff format and lint, then did not complete the repository-wide MyPy phase in the available container execution. Targeted strict MyPy and all generated checks passed independently. + +## Risks, limitations, and continuation value + +### Important limitations + +- The accepted `beads-02` dependency was not supplied or landed. This package's declaration kernel may need structural reconciliation. +- The public MCP input schemas of the ten newly guarded tools now include optional `confirm`; clients executing those effects must send `confirm=true`. This is an intentional safety-compatible contract change. +- Boolean confirmation is unbound to actor, archive identity, target digest, expiry, or spec version. It does not close the structural authorization program in `polylogue-t46.9`. +- `clear_corrections` and `update_index` retain their current source contracts because `polylogue-jn40` does not name them. They should be classified by the future operation inventory rather than silently swept into this interim patch. +- No old tool, alias, prompt, or resource was retired. There was no supplied shadow telemetry, observed-use report, cold-model route proof, or completed read-algebra dependency to justify deletion. +- The declaration schema retains future resource/prompt-alternative fields, but this patch claims no resource URI and only points at the already registered `resume_context` prompt. No new resolver, prompt recipe, subscription, or notification route is added. +- No operator live daemon, browser, real archive, secrets, NixOS deployment, or current worktree was available. Live protocol handshake, deployed role credentials, and real-archive migration are unverified. +- The snapshot's manifest says dirty, but the exported tracked tree and branch delta provide no dirty patch to preserve. Unknown ignored/untracked local state is not represented. + +### Additional iteration value + +A **small repair pass** could improve wording, declaration examples, or add a dedicated render command for the equivalence JSON, but would add little behavioral value while the dependency remains unknown. + +A **substantial second pass** is valuable only with one or more missing authorities: the accepted `beads-02` package, completed `t46.8.2` read migration, real MCP usage telemetry/cold-model trials, or the `t46.9` operation executor. With those inputs, the next pass could reconcile the provisional kernel, generate resource/prompt surfaces, retire proven duplicate tools, and replace boolean confirmation with preview-bound authorization and durable receipts. Without them, further broad changes would mostly speculate beyond current source authority. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/PATCH.diff new file mode 100644 index 000000000..4bf338dbf --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/PATCH.diff @@ -0,0 +1,4143 @@ +diff --git a/docs/generated/mcp-context-assertion-equivalence.json b/docs/generated/mcp-context-assertion-equivalence.json +new file mode 100644 +index 0000000000000000000000000000000000000000..98faa762135da1de5655efa1077daeb09a39f228 +--- /dev/null ++++ b/docs/generated/mcp-context-assertion-equivalence.json +@@ -0,0 +1,1544 @@ ++{ ++ "counts_by_family": { ++ "assertion": 3, ++ "context": 3, ++ "correction": 3, ++ "judgment": 2, ++ "maintenance": 7, ++ "personal_state": 23 ++ }, ++ "counts_by_required_role": { ++ "admin": 7, ++ "read": 3, ++ "review": 2, ++ "write": 29 ++ }, ++ "declarations": [ ++ { ++ "canonical_owner": "polylogue.api.Polylogue.get_context_delivery", ++ "canonical_plan": "polylogue.api.Polylogue.get_context_delivery", ++ "compatibility_route": "get_context_delivery", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "snapshot_ref", ++ "recipient_ref" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "context receipt", ++ "recipient-scoped exact bytes" ++ ], ++ "family": "context", ++ "injection_policy": "never_inject", ++ "lifecycle": [ ++ "read_receipt" ++ ], ++ "minimal_invocation": { ++ "recipient_ref": "session:example", ++ "snapshot_ref": "context-snapshot:session:example:explicit-recall" ++ }, ++ "module": "polylogue.mcp.server_context_tools", ++ "name": "get_context_delivery", ++ "object_kind": "context_delivery_receipt", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "snapshot_ref", ++ "recipient_ref", ++ "context_image_sha256" ++ ], ++ "required_role": "read", ++ "resource_alternative": null, ++ "result_semantics": "receipt", ++ "telemetry_key": "mcp.tool.get_context_delivery", ++ "verb": "context", ++ "workflow_coverage": [ ++ "context receipt audit", ++ "recipient mismatch privacy" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.context_image_payload", ++ "canonical_plan": "polylogue.api.Polylogue.context_image_payload", ++ "compatibility_route": "build_context_image", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "bounded context", ++ "query-selected sessions", ++ "token omissions" ++ ], ++ "family": "context", ++ "injection_policy": "never_inject", ++ "lifecycle": [ ++ "select", ++ "compile", ++ "return" ++ ], ++ "minimal_invocation": { ++ "detail_level": "compact", ++ "max_sessions": 5, ++ "project_repo": "polylogue" ++ }, ++ "module": "polylogue.mcp.server_context_tools", ++ "name": "build_context_image", ++ "object_kind": "context_image", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "selected session refs", ++ "context spec", ++ "omission reasons" ++ ], ++ "required_role": "read", ++ "resource_alternative": null, ++ "result_semantics": "bounded_context", ++ "telemetry_key": "mcp.tool.build_context_image", ++ "verb": "context", ++ "workflow_coverage": [ ++ "continuity recall", ++ "bounded evidence assembly" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.context.preamble.build_context_preamble_payload", ++ "canonical_plan": "polylogue.context.preamble.build_context_preamble_payload", ++ "compatibility_route": "compose_context_preamble", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "SessionStart preamble", ++ "lineage", ++ "related sessions" ++ ], ++ "family": "context", ++ "injection_policy": "operator_judgment", ++ "lifecycle": [ ++ "compose", ++ "return" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_context_tools", ++ "name": "compose_context_preamble", ++ "object_kind": "context_preamble", ++ "prompt_alternative": "resume_context", ++ "provenance_contract": [ ++ "source_tool_calls", ++ "successor_session_id" ++ ], ++ "required_role": "read", ++ "resource_alternative": null, ++ "result_semantics": "bounded_context", ++ "telemetry_key": "mcp.tool.compose_context_preamble", ++ "verb": "context", ++ "workflow_coverage": [ ++ "session start continuity" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "canonical_plan": "polylogue.api.Polylogue.capture_assertion_candidate", ++ "compatibility_route": "capture_assertion_candidate", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "agent-authored candidate", ++ "pending review", ++ "non-injected" ++ ], ++ "family": "assertion", ++ "injection_policy": "candidate_only", ++ "lifecycle": [ ++ "capture", ++ "candidate", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "author_ref": "agent:session:example", ++ "body_text": "candidate claim" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "capture_assertion_candidate", ++ "object_kind": "assertion_candidate", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "author_ref", ++ "author_kind=agent", ++ "evidence_refs", ++ "scope_refs" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.capture_assertion_candidate", ++ "verb": "write", ++ "workflow_coverage": [ ++ "candidate capture", ++ "later operator judgment" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.annotations.importer.import_annotation_batch", ++ "canonical_plan": "polylogue.annotations.importer.import_annotation_batch", ++ "compatibility_route": "import_annotation_batch", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "typed JSONL annotations", ++ "immutable schema", ++ "batch provenance" ++ ], ++ "family": "assertion", ++ "injection_policy": "candidate_only", ++ "lifecycle": [ ++ "validate_schema", ++ "validate_refs", ++ "persist_batch", ++ "persist_candidates", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "actor_ref": "agent:example", ++ "batch_id": "batch-example", ++ "jsonl": "{\"row_key\":\"r1\",\"value\":{},\"evidence_refs\":[]}", ++ "model_ref": "agent:model", ++ "prompt_ref": "block:example:0", ++ "schema_id": "delegation.discourse", ++ "schema_version": 1, ++ "source_result_ref": "result-set:example", ++ "target_ref": "delegation:example" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "import_annotation_batch", ++ "object_kind": "annotation_batch", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.import_annotation_batch", ++ "verb": "write", ++ "workflow_coverage": [ ++ "annotation import", ++ "candidate adjudication" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.post_blackboard_note", ++ "canonical_plan": "polylogue.api.Polylogue.post_blackboard_note", ++ "compatibility_route": "blackboard_post", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "persistent agent blackboard", ++ "scoped note" ++ ], ++ "family": "assertion", ++ "injection_policy": "candidate_only", ++ "lifecycle": [ ++ "validate", ++ "persist", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "content": "Evidence-backed note", ++ "kind": "finding", ++ "title": "Finding" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "blackboard_post", ++ "object_kind": "blackboard_note", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "author_ref", ++ "author_kind", ++ "evidence_refs", ++ "scope" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.blackboard_post", ++ "verb": "write", ++ "workflow_coverage": [ ++ "coordination handoff", ++ "candidate note" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.add_tag", ++ "canonical_plan": "polylogue.api.Polylogue.add_tag", ++ "compatibility_route": "add_tag", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "add session tag", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "resolve", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "session_id": "session:example", ++ "tag": "review" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "add_tag", ++ "object_kind": "session_tag", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "session_id", ++ "tag", ++ "author_ref", ++ "author_kind" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.add_tag", ++ "verb": "write", ++ "workflow_coverage": [ ++ "triage" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.remove_tag", ++ "canonical_plan": "polylogue.api.Polylogue.remove_tag", ++ "compatibility_route": "remove_tag", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "remove session tag", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "resolve", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "session_id": "session:example", ++ "tag": "review" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "remove_tag", ++ "object_kind": "session_tag", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.remove_tag", ++ "verb": "write", ++ "workflow_coverage": [ ++ "triage correction" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.bulk_tag_sessions", ++ "canonical_plan": "polylogue.api.Polylogue.bulk_tag_sessions", ++ "compatibility_route": "bulk_tag_sessions", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "bulk tag", ++ "affected/skipped counts" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "validate", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "session_ids": [ ++ "session:example" ++ ], ++ "tags": [ ++ "review" ++ ] ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "bulk_tag_sessions", ++ "object_kind": "session_tag_batch", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.bulk_tag_sessions", ++ "verb": "write", ++ "workflow_coverage": [ ++ "bulk triage" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_tags", ++ "canonical_plan": "polylogue.api.Polylogue.list_tags", ++ "compatibility_route": "list_tags", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "user-tier tags", ++ "counts" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "list_tags", ++ "object_kind": "tag_counts", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_tags", ++ "verb": "write", ++ "workflow_coverage": [ ++ "triage readback" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_marks", ++ "canonical_plan": "polylogue.api.Polylogue.list_marks", ++ "compatibility_route": "list_marks", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "star/pin/hide marks" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "list_marks", ++ "object_kind": "user_mark", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_marks", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader state readback" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.add_mark", ++ "canonical_plan": "polylogue.api.Polylogue.add_mark", ++ "compatibility_route": "add_mark", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "add user mark", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "resolve", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "mark_type": "star", ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "add_mark", ++ "object_kind": "user_mark", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.add_mark", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader state" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.remove_mark", ++ "canonical_plan": "polylogue.api.Polylogue.remove_mark", ++ "compatibility_route": "remove_mark", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "remove user mark", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "resolve", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "mark_type": "star", ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "remove_mark", ++ "object_kind": "user_mark", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.remove_mark", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader state correction" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_annotations", ++ "canonical_plan": "polylogue.api.Polylogue.list_annotations", ++ "compatibility_route": "list_annotations", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "private annotation readback" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "list_annotations", ++ "object_kind": "user_annotation", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_annotations", ++ "verb": "write", ++ "workflow_coverage": [ ++ "annotation workflow" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.save_annotation", ++ "canonical_plan": "polylogue.api.Polylogue.save_annotation", ++ "compatibility_route": "save_annotation", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "save private annotation", ++ "upsert" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "resolve", ++ "upsert", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "annotation_id": "ann-example", ++ "note_text": "note", ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "save_annotation", ++ "object_kind": "user_annotation", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "annotation_id", ++ "session_id", ++ "target" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.save_annotation", ++ "verb": "write", ++ "workflow_coverage": [ ++ "annotation workflow" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_annotation", ++ "canonical_plan": "polylogue.api.Polylogue.delete_annotation", ++ "compatibility_route": "delete_annotation", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete private annotation", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "annotation_id": "ann-example", ++ "confirm": true ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "delete_annotation", ++ "object_kind": "user_annotation", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_annotation", ++ "verb": "write", ++ "workflow_coverage": [ ++ "annotation cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_views", ++ "canonical_plan": "polylogue.api.Polylogue.list_views", ++ "compatibility_route": "list_saved_views", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "saved query views" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "list_saved_views", ++ "object_kind": "saved_view", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_saved_views", ++ "verb": "write", ++ "workflow_coverage": [ ++ "saved query" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.save_view", ++ "canonical_plan": "polylogue.api.Polylogue.save_view", ++ "compatibility_route": "save_saved_view", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "save query view", ++ "validated query spec" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "validate", ++ "upsert", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "name": "Review", ++ "query_json": "{}" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "save_saved_view", ++ "object_kind": "saved_view", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.save_saved_view", ++ "verb": "write", ++ "workflow_coverage": [ ++ "saved query" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_view", ++ "canonical_plan": "polylogue.api.Polylogue.delete_view", ++ "compatibility_route": "delete_saved_view", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete saved view", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "view_id": "view-example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "delete_saved_view", ++ "object_kind": "saved_view", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_saved_view", ++ "verb": "write", ++ "workflow_coverage": [ ++ "saved query cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_recall_packs", ++ "canonical_plan": "polylogue.api.Polylogue.list_recall_packs", ++ "compatibility_route": "list_recall_packs", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "recall pack readback" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "list_recall_packs", ++ "object_kind": "recall_pack", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_recall_packs", ++ "verb": "write", ++ "workflow_coverage": [ ++ "continuity recall" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.create_recall_pack", ++ "canonical_plan": "polylogue.api.Polylogue.create_recall_pack", ++ "compatibility_route": "save_recall_pack", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "save recall pack", ++ "typed items" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "validate", ++ "upsert", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "label": "Example", ++ "pack_id": "pack-example", ++ "payload_json": "{\"items\":[]}" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "save_recall_pack", ++ "object_kind": "recall_pack", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.save_recall_pack", ++ "verb": "write", ++ "workflow_coverage": [ ++ "continuity recall" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_recall_pack", ++ "canonical_plan": "polylogue.api.Polylogue.delete_recall_pack", ++ "compatibility_route": "delete_recall_pack", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete recall pack", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "pack_id": "pack-example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "delete_recall_pack", ++ "object_kind": "recall_pack", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_recall_pack", ++ "verb": "write", ++ "workflow_coverage": [ ++ "continuity cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_workspaces", ++ "canonical_plan": "polylogue.api.Polylogue.list_workspaces", ++ "compatibility_route": "list_workspaces", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "reader workspace readback" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "list_workspaces", ++ "object_kind": "reader_workspace", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_workspaces", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader workspace" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.save_workspace", ++ "canonical_plan": "polylogue.api.Polylogue.save_workspace", ++ "compatibility_route": "save_workspace", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "save reader workspace" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "validate", ++ "upsert", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "name": "Example", ++ "workspace_id": "workspace-example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "save_workspace", ++ "object_kind": "reader_workspace", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.save_workspace", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader workspace" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_workspace", ++ "canonical_plan": "polylogue.api.Polylogue.delete_workspace", ++ "compatibility_route": "delete_workspace", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete reader workspace", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "workspace_id": "workspace-example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "delete_workspace", ++ "object_kind": "reader_workspace", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_workspace", ++ "verb": "write", ++ "workflow_coverage": [ ++ "reader workspace cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.get_metadata", ++ "canonical_plan": "polylogue.api.Polylogue.get_metadata", ++ "compatibility_route": "get_metadata", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "session metadata readback" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": { ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "get_metadata", ++ "object_kind": "session_metadata", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "receipt", ++ "telemetry_key": "mcp.tool.get_metadata", ++ "verb": "write", ++ "workflow_coverage": [ ++ "metadata workflow" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.set_metadata", ++ "canonical_plan": "polylogue.api.Polylogue.set_metadata", ++ "compatibility_route": "set_metadata", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "set metadata key", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "validate", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "key": "note", ++ "session_id": "session:example", ++ "value": "value" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "set_metadata", ++ "object_kind": "session_metadata", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.set_metadata", ++ "verb": "write", ++ "workflow_coverage": [ ++ "metadata workflow" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_metadata", ++ "canonical_plan": "polylogue.api.Polylogue.delete_metadata", ++ "compatibility_route": "delete_metadata", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete metadata key", ++ "idempotent" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "validate", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "key": "note", ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "delete_metadata", ++ "object_kind": "session_metadata", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_metadata", ++ "verb": "write", ++ "workflow_coverage": [ ++ "metadata cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.delete_session_safe", ++ "canonical_plan": "polylogue.api.Polylogue.delete_session_safe", ++ "compatibility_route": "delete_session", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "delete session", ++ "destructive" ++ ], ++ "family": "personal_state", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true, ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "delete_session", ++ "object_kind": "session", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.delete_session", ++ "verb": "write", ++ "workflow_coverage": [ ++ "session deletion" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.record_correction", ++ "canonical_plan": "polylogue.api.Polylogue.record_correction", ++ "compatibility_route": "record_correction", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "record derived-insight correction" ++ ], ++ "family": "correction", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "resolve", ++ "validate", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "kind": "phase_boundary", ++ "payload": {}, ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "record_correction", ++ "object_kind": "insight_correction", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "session_id", ++ "kind", ++ "author_ref", ++ "author_kind" ++ ], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.record_correction", ++ "verb": "write", ++ "workflow_coverage": [ ++ "learning correction" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.list_corrections", ++ "canonical_plan": "polylogue.api.Polylogue.list_corrections", ++ "compatibility_route": "list_corrections", ++ "confirmation_policy": "none", ++ "continuation": [ ++ "offset", ++ "limit" ++ ], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "correction readback" ++ ], ++ "family": "correction", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "list_corrections", ++ "object_kind": "insight_correction", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "exhaustive_page", ++ "telemetry_key": "mcp.tool.list_corrections", ++ "verb": "write", ++ "workflow_coverage": [ ++ "learning correction" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.clear_corrections", ++ "canonical_plan": "polylogue.api.Polylogue.clear_corrections", ++ "compatibility_route": "clear_corrections", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "clear correction records" ++ ], ++ "family": "correction", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "resolve", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "session_id": "session:example" ++ }, ++ "module": "polylogue.mcp.server_personal_state_tools", ++ "name": "clear_corrections", ++ "object_kind": "insight_correction", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "write", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.clear_corrections", ++ "verb": "write", ++ "workflow_coverage": [ ++ "learning correction cleanup" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "canonical_plan": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "compatibility_route": "judge_assertion_candidate", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "operator judgment", ++ "accept/reject/defer/supersede" ++ ], ++ "family": "judgment", ++ "injection_policy": "operator_judgment", ++ "lifecycle": [ ++ "authorize", ++ "judge", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "actor_ref": "user:local", ++ "candidate_ref": "assertion:candidate", ++ "decision": "accept" ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "judge_assertion_candidate", ++ "object_kind": "assertion_candidate", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "candidate_ref", ++ "actor_ref", ++ "decision", ++ "reason" ++ ], ++ "required_role": "review", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.judge_assertion_candidate", ++ "verb": "judge", ++ "workflow_coverage": [ ++ "candidate adjudication" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "canonical_plan": "polylogue.api.Polylogue.judge_assertion_candidates", ++ "compatibility_route": "judge_assertion_candidates", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "bulk operator judgment", ++ "partial success" ++ ], ++ "family": "judgment", ++ "injection_policy": "operator_judgment", ++ "lifecycle": [ ++ "authorize", ++ "validate_each", ++ "judge_each", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "actor_ref": "user:local", ++ "items": [ ++ { ++ "candidate_ref": "assertion:candidate", ++ "decision": "accept" ++ } ++ ] ++ }, ++ "module": "polylogue.mcp.server_mutation_tools", ++ "name": "judge_assertion_candidates", ++ "object_kind": "assertion_judgment_batch", ++ "prompt_alternative": null, ++ "provenance_contract": [ ++ "candidate_ref", ++ "actor_ref", ++ "decision", ++ "reason" ++ ], ++ "required_role": "review", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.judge_assertion_candidates", ++ "verb": "judge", ++ "workflow_coverage": [ ++ "candidate adjudication" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.maintenance.planner.preview_backfill", ++ "canonical_plan": "polylogue.maintenance.planner.preview_backfill", ++ "compatibility_route": "maintenance_preview", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "maintenance dry-run", ++ "exact scope" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "preview", ++ "receipt" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "maintenance_preview", ++ "object_kind": "maintenance_plan", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "maintenance_preview", ++ "telemetry_key": "mcp.tool.maintenance_preview", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance plan" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.maintenance.planner.execute_backfill", ++ "canonical_plan": "polylogue.maintenance.planner.execute_backfill", ++ "compatibility_route": "maintenance_execute", ++ "confirmation_policy": "dry_run_or_explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "maintenance execute", ++ "dry-run or explicit confirmation" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "preview_or_confirm", ++ "apply", ++ "receipt", ++ "status" ++ ], ++ "minimal_invocation": { ++ "dry_run": true ++ }, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "maintenance_execute", ++ "object_kind": "maintenance_operation", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "maintenance_execute", ++ "telemetry_key": "mcp.tool.maintenance_execute", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance apply" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ "canonical_plan": "polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ "compatibility_route": "maintenance_status", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "maintenance status", ++ "persisted receipt" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read_status" ++ ], ++ "minimal_invocation": { ++ "operation_id": "operation-example" ++ }, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "maintenance_status", ++ "object_kind": "maintenance_operation", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "maintenance_status", ++ "telemetry_key": "mcp.tool.maintenance_status", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance recovery" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ "canonical_plan": "polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ "compatibility_route": "maintenance_list", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "maintenance operation list" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "read_status" ++ ], ++ "minimal_invocation": {}, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "maintenance_list", ++ "object_kind": "maintenance_operation", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "maintenance_status", ++ "telemetry_key": "mcp.tool.maintenance_list", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "maintenance recovery" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.rebuild_index", ++ "canonical_plan": "polylogue.api.Polylogue.rebuild_index", ++ "compatibility_route": "rebuild_index", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "full index rebuild" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true ++ }, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "rebuild_index", ++ "object_kind": "search_index", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.rebuild_index", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "index repair" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.update_index", ++ "canonical_plan": "polylogue.api.Polylogue.update_index", ++ "compatibility_route": "update_index", ++ "confirmation_policy": "none", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "incremental index update" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "session_ids": [ ++ "session:example" ++ ] ++ }, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "update_index", ++ "object_kind": "search_index", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.update_index", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "index catch-up" ++ ] ++ }, ++ { ++ "canonical_owner": "polylogue.api.Polylogue.rebuild_insights", ++ "canonical_plan": "polylogue.api.Polylogue.rebuild_insights", ++ "compatibility_route": "rebuild_session_insights", ++ "confirmation_policy": "explicit_confirm", ++ "continuation": [], ++ "deprecation_state": "retained_compatibility", ++ "discovery": [ ++ "derived insight rebuild" ++ ], ++ "family": "maintenance", ++ "injection_policy": "not_applicable", ++ "lifecycle": [ ++ "confirm", ++ "apply", ++ "receipt" ++ ], ++ "minimal_invocation": { ++ "confirm": true ++ }, ++ "module": "polylogue.mcp.server_maintenance_tools", ++ "name": "rebuild_session_insights", ++ "object_kind": "session_insights", ++ "prompt_alternative": null, ++ "provenance_contract": [], ++ "required_role": "admin", ++ "resource_alternative": null, ++ "result_semantics": "mutation", ++ "telemetry_key": "mcp.tool.rebuild_session_insights", ++ "verb": "maintenance", ++ "workflow_coverage": [ ++ "derived repair" ++ ] ++ } ++ ], ++ "dependency": "polylogue-t46.8.1", ++ "migration_owner": "polylogue-t46.8.3", ++ "schema_version": "polylogue.mcp.context-assertion-equivalence.v1", ++ "tool_count": 41 ++} +diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml +index f7f9c974216bc0c67829c306b658e87429bb1728..b6087bae195a621a3355ea5d094b0890a7265ddf 100644 +--- a/docs/plans/topology-target.yaml ++++ b/docs/plans/topology-target.yaml +@@ -2040,6 +2040,22 @@ files: + loc: 34 + target: polylogue/mcp/cli.py + owner: stable ++ - path: polylogue/mcp/declarations/__init__.py ++ loc: 31 ++ target: polylogue/mcp/declarations/__init__.py ++ owner: stable ++ - path: polylogue/mcp/declarations/models.py ++ loc: 121 ++ target: polylogue/mcp/declarations/models.py ++ owner: stable ++ - path: polylogue/mcp/declarations/registration.py ++ loc: 114 ++ target: polylogue/mcp/declarations/registration.py ++ owner: stable ++ - path: polylogue/mcp/declarations/registry.py ++ loc: 868 ++ target: polylogue/mcp/declarations/registry.py ++ owner: stable + - path: polylogue/mcp/insight_tool_contracts.py + loc: 102 + target: polylogue/mcp/insight_tool_contracts.py +@@ -2069,15 +2085,15 @@ files: + target: polylogue/mcp/server_insight_tools.py + owner: stable + - path: polylogue/mcp/server_maintenance_tools.py +- loc: 304 ++ loc: 322 + target: polylogue/mcp/server_maintenance_tools.py + owner: stable + - path: polylogue/mcp/server_mutation_tools.py +- loc: 497 ++ loc: 510 + target: polylogue/mcp/server_mutation_tools.py + owner: stable + - path: polylogue/mcp/server_personal_state_tools.py +- loc: 675 ++ loc: 700 + target: polylogue/mcp/server_personal_state_tools.py + owner: stable + - path: polylogue/mcp/server_prompts.py +@@ -2093,7 +2109,7 @@ files: + target: polylogue/mcp/server_support.py + owner: stable + - path: polylogue/mcp/server_tools.py +- loc: 1325 ++ loc: 1324 + target: polylogue/mcp/server_tools.py + owner: stable + - path: polylogue/operations/__init__.py +@@ -3240,7 +3256,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_readiness.py +- loc: 818 ++ loc: 830 + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_views.py +@@ -3525,7 +3541,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_reconciler.py +- loc: 1411 ++ loc: 1422 + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_retention.py +diff --git a/docs/topology-status.md b/docs/topology-status.md +index d8e0e49740260b6a6a23dd76a91192d6d66d485e..a868467a68e339e778a8083e086124f98800452f 100644 +--- a/docs/topology-status.md ++++ b/docs/topology-status.md +@@ -28,12 +28,12 @@ Generated by `devtools render topology-status`. Reads `docs/plans/topology-targe + + ### Summary + +-- **Stable** (no move scoped): 843 ++- **Stable** (no move scoped): 847 + - **Kernel** (polylogue/ root): 7 + - **Primitives** (storage-root): 18 + - **TBD** (cell needs explicit assignment): 9 +-- **Total declared**: 1007 +-- **Realized polylogue/**/*.py**: 1007 files declared ++- **Total declared**: 1011 ++- **Realized polylogue/**/*.py**: 1011 files declared + + ### TBD cells (require explicit routing) + +diff --git a/polylogue/mcp/declarations/__init__.py b/polylogue/mcp/declarations/__init__.py +new file mode 100644 +index 0000000000000000000000000000000000000000..838cb371bf1ff7ed33e1b2a9547f09bc85ed09cc +--- /dev/null ++++ b/polylogue/mcp/declarations/__init__.py +@@ -0,0 +1,31 @@ ++"""Executable MCP declaration and registration kernel.""" ++ ++from polylogue.mcp.declarations.models import MCPToolDeclaration ++from polylogue.mcp.declarations.registration import ( ++ MCPDeclarationRegistrar, ++ MCPDeclarationRegistrationError, ++) ++from polylogue.mcp.declarations.registry import ( ++ MCP_CONTEXT_ASSERTION_DECLARATIONS, ++ MCP_CONTEXT_ASSERTION_REGISTRY, ++ MCP_CONTEXT_ASSERTION_TOOL_NAMES, ++ declaration_for, ++ declarations_for_role, ++ equivalence_document, ++ render_equivalence_json, ++ validate_registry, ++) ++ ++__all__ = [ ++ "MCP_CONTEXT_ASSERTION_DECLARATIONS", ++ "MCP_CONTEXT_ASSERTION_REGISTRY", ++ "MCP_CONTEXT_ASSERTION_TOOL_NAMES", ++ "MCPDeclarationRegistrar", ++ "MCPDeclarationRegistrationError", ++ "MCPToolDeclaration", ++ "declaration_for", ++ "declarations_for_role", ++ "equivalence_document", ++ "render_equivalence_json", ++ "validate_registry", ++] +diff --git a/polylogue/mcp/declarations/models.py b/polylogue/mcp/declarations/models.py +new file mode 100644 +index 0000000000000000000000000000000000000000..7bbe70303eb4a43b6a7c41cfde2b9c3b40d87b1a +--- /dev/null ++++ b/polylogue/mcp/declarations/models.py +@@ -0,0 +1,121 @@ ++"""Typed MCP declaration records for protocol-facing tool adapters. ++ ++The declarations own discovery and compatibility metadata. They do not execute ++queries or mutations: production handlers remain thin adapters over the typed ++substrate/facade owners named by each declaration. ++""" ++ ++from __future__ import annotations ++ ++import json ++from dataclasses import dataclass ++from typing import Literal, cast ++ ++from polylogue.mcp.server_support import MCPRole ++ ++MCPDeclarationFamily = Literal[ ++ "context", ++ "assertion", ++ "personal_state", ++ "correction", ++ "judgment", ++ "maintenance", ++] ++MCPVerb = Literal["context", "write", "judge", "maintenance"] ++MCPResultSemantics = Literal[ ++ "bounded_context", ++ "receipt", ++ "exhaustive_page", ++ "mutation", ++ "maintenance_preview", ++ "maintenance_execute", ++ "maintenance_status", ++] ++MCPInjectionPolicy = Literal[ ++ "not_applicable", ++ "candidate_only", ++ "operator_judgment", ++ "never_inject", ++] ++MCPConfirmationPolicy = Literal["none", "explicit_confirm", "dry_run_or_explicit_confirm"] ++MCPDeprecationState = Literal["retained_compatibility"] ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPToolDeclaration: ++ """One executable MCP compatibility declaration. ++ ++ ``minimal_invocation_json`` is stored as canonical JSON rather than a ++ mutable mapping so declarations stay immutable and can be rendered without ++ aliasing caller-owned values. ++ """ ++ ++ name: str ++ module: str ++ family: MCPDeclarationFamily ++ verb: MCPVerb ++ object_kind: str ++ required_role: MCPRole ++ result_semantics: MCPResultSemantics ++ canonical_owner: str ++ canonical_plan: str ++ minimal_invocation_json: str ++ discovery: tuple[str, ...] ++ continuation: tuple[str, ...] ++ compatibility_route: str ++ workflow_coverage: tuple[str, ...] ++ telemetry_key: str ++ injection_policy: MCPInjectionPolicy = "not_applicable" ++ provenance_contract: tuple[str, ...] = () ++ confirmation_policy: MCPConfirmationPolicy = "none" ++ lifecycle: tuple[str, ...] = () ++ resource_alternative: str | None = None ++ prompt_alternative: str | None = None ++ deprecation_state: MCPDeprecationState = "retained_compatibility" ++ ++ def minimal_invocation(self) -> dict[str, object]: ++ """Return a fresh validated mapping for generated contracts/tests.""" ++ ++ value = json.loads(self.minimal_invocation_json) ++ if not isinstance(value, dict): ++ raise ValueError(f"{self.name}: minimal invocation must decode to an object") ++ return cast(dict[str, object], value) ++ ++ def to_equivalence_dict(self) -> dict[str, object]: ++ """Render stable, JSON-compatible equivalence metadata.""" ++ ++ return { ++ "name": self.name, ++ "module": self.module, ++ "family": self.family, ++ "verb": self.verb, ++ "object_kind": self.object_kind, ++ "required_role": self.required_role, ++ "result_semantics": self.result_semantics, ++ "canonical_owner": self.canonical_owner, ++ "canonical_plan": self.canonical_plan, ++ "minimal_invocation": self.minimal_invocation(), ++ "discovery": list(self.discovery), ++ "continuation": list(self.continuation), ++ "resource_alternative": self.resource_alternative, ++ "prompt_alternative": self.prompt_alternative, ++ "compatibility_route": self.compatibility_route, ++ "workflow_coverage": list(self.workflow_coverage), ++ "telemetry_key": self.telemetry_key, ++ "injection_policy": self.injection_policy, ++ "provenance_contract": list(self.provenance_contract), ++ "confirmation_policy": self.confirmation_policy, ++ "lifecycle": list(self.lifecycle), ++ "deprecation_state": self.deprecation_state, ++ } ++ ++ ++__all__ = [ ++ "MCPConfirmationPolicy", ++ "MCPDeclarationFamily", ++ "MCPDeprecationState", ++ "MCPInjectionPolicy", ++ "MCPResultSemantics", ++ "MCPToolDeclaration", ++ "MCPVerb", ++] +diff --git a/polylogue/mcp/declarations/registration.py b/polylogue/mcp/declarations/registration.py +new file mode 100644 +index 0000000000000000000000000000000000000000..aa04aaadde56029e7201664906616431bb1995b1 +--- /dev/null ++++ b/polylogue/mcp/declarations/registration.py +@@ -0,0 +1,114 @@ ++"""Role-scoped registration adapter for declared MCP tools.""" ++ ++from __future__ import annotations ++ ++import inspect ++from collections.abc import Awaitable, Callable ++from typing import Any, Protocol, TypeAlias ++ ++from polylogue.mcp.declarations.models import MCPToolDeclaration ++from polylogue.mcp.declarations.registry import ( ++ MCP_CONTEXT_ASSERTION_TOOL_NAMES, ++ declaration_for, ++) ++from polylogue.mcp.server_support import MCPRole, role_allows ++ ++MCPToolHandler: TypeAlias = Callable[..., Awaitable[str]] ++ ++ ++class MCPToolHost(Protocol): ++ def tool(self) -> Callable[[MCPToolHandler], Any]: ++ raise NotImplementedError ++ ++ ++class MCPDeclarationRegistrationError(RuntimeError): ++ """Raised when a production handler and declaration drift apart.""" ++ ++ ++class MCPDeclarationRegistrar: ++ """Register exact handlers from declarations without becoming an executor. ++ ++ The registrar owns discovery/role filtering and static adapter-contract ++ checks only. It never invokes, wraps, or dispatches a handler, so distinct ++ maintenance/destructive implementations and their audit semantics remain ++ visible production code. ++ """ ++ ++ def __init__(self, mcp: MCPToolHost, *, role: MCPRole) -> None: ++ self._mcp = mcp ++ self._role = role ++ self._seen: dict[str, MCPToolDeclaration] = {} ++ self._registered: set[str] = set() ++ ++ @property ++ def registered_names(self) -> frozenset[str]: ++ return frozenset(self._registered) ++ ++ @property ++ def seen_names(self) -> frozenset[str]: ++ return frozenset(self._seen) ++ ++ def tool(self) -> Callable[[MCPToolHandler], MCPToolHandler]: ++ """Validate and role-filter one exact production handler.""" ++ ++ def register(handler: MCPToolHandler) -> MCPToolHandler: ++ name = handler.__name__ ++ try: ++ declaration = declaration_for(name) ++ except KeyError as exc: ++ raise MCPDeclarationRegistrationError(str(exc)) from exc ++ if name in self._seen: ++ raise MCPDeclarationRegistrationError(f"duplicate MCP handler registration: {name}") ++ self._validate_handler(declaration, handler) ++ self._seen[name] = declaration ++ if role_allows(self._role, declaration.required_role): ++ self._mcp.tool()(handler) ++ self._registered.add(name) ++ return handler ++ ++ return register ++ ++ def assert_complete(self) -> None: ++ """Require every migrated declaration to meet one concrete handler.""" ++ ++ missing = MCP_CONTEXT_ASSERTION_TOOL_NAMES - self.seen_names ++ unexpected = self.seen_names - MCP_CONTEXT_ASSERTION_TOOL_NAMES ++ if missing or unexpected: ++ raise MCPDeclarationRegistrationError( ++ "MCP declaration/adapter mismatch: " ++ f"missing_handlers={sorted(missing)}, unexpected_handlers={sorted(unexpected)}" ++ ) ++ ++ @staticmethod ++ def _validate_handler(declaration: MCPToolDeclaration, handler: MCPToolHandler) -> None: ++ if handler.__module__ != declaration.module: ++ raise MCPDeclarationRegistrationError( ++ f"{declaration.name}: declaration module {declaration.module!r} " ++ f"does not match handler module {handler.__module__!r}" ++ ) ++ if not inspect.iscoroutinefunction(handler): ++ raise MCPDeclarationRegistrationError(f"{declaration.name}: MCP handler must be async") ++ signature = inspect.signature(handler) ++ missing_example_args = set(declaration.minimal_invocation()) - set(signature.parameters) ++ if missing_example_args: ++ raise MCPDeclarationRegistrationError( ++ f"{declaration.name}: minimal invocation names missing from handler: {sorted(missing_example_args)}" ++ ) ++ if declaration.confirmation_policy != "none": ++ confirm = signature.parameters.get("confirm") ++ if confirm is None or confirm.default is not False: ++ raise MCPDeclarationRegistrationError( ++ f"{declaration.name}: confirmation policy requires confirm: bool = False" ++ ) ++ if declaration.confirmation_policy == "dry_run_or_explicit_confirm": ++ dry_run = signature.parameters.get("dry_run") ++ if dry_run is None or dry_run.default is not False: ++ raise MCPDeclarationRegistrationError( ++ f"{declaration.name}: dry-run confirmation policy requires dry_run: bool = False" ++ ) ++ ++ ++__all__ = [ ++ "MCPDeclarationRegistrar", ++ "MCPDeclarationRegistrationError", ++] +diff --git a/polylogue/mcp/declarations/registry.py b/polylogue/mcp/declarations/registry.py +new file mode 100644 +index 0000000000000000000000000000000000000000..ec15ad78c96cd0e604166f0c9a3cf898335a3f8c +--- /dev/null ++++ b/polylogue/mcp/declarations/registry.py +@@ -0,0 +1,868 @@ ++"""MCP declarations for context, assertion, correction, and maintenance tools.""" ++ ++from __future__ import annotations ++ ++import json ++from collections.abc import Iterable ++ ++from polylogue.mcp.declarations.models import ( ++ MCPConfirmationPolicy, ++ MCPDeclarationFamily, ++ MCPInjectionPolicy, ++ MCPResultSemantics, ++ MCPToolDeclaration, ++ MCPVerb, ++) ++from polylogue.mcp.server_support import MCPRole, role_allows ++ ++ ++def _canonical_json(value: dict[str, object]) -> str: ++ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) ++ ++ ++def _declaration( ++ name: str, ++ *, ++ module: str, ++ family: MCPDeclarationFamily, ++ verb: MCPVerb, ++ object_kind: str, ++ required_role: MCPRole, ++ result_semantics: MCPResultSemantics, ++ canonical_owner: str, ++ minimal_invocation: dict[str, object] | None = None, ++ discovery: tuple[str, ...] = (), ++ continuation: tuple[str, ...] = (), ++ workflows: tuple[str, ...] = (), ++ injection_policy: MCPInjectionPolicy = "not_applicable", ++ provenance: tuple[str, ...] = (), ++ confirmation: MCPConfirmationPolicy = "none", ++ lifecycle: tuple[str, ...] = (), ++ resource_alternative: str | None = None, ++ prompt_alternative: str | None = None, ++) -> MCPToolDeclaration: ++ return MCPToolDeclaration( ++ name=name, ++ module=module, ++ family=family, ++ verb=verb, ++ object_kind=object_kind, ++ required_role=required_role, ++ result_semantics=result_semantics, ++ canonical_owner=canonical_owner, ++ canonical_plan=canonical_owner, ++ minimal_invocation_json=_canonical_json(minimal_invocation or {}), ++ discovery=discovery, ++ continuation=continuation, ++ resource_alternative=resource_alternative, ++ prompt_alternative=prompt_alternative, ++ compatibility_route=name, ++ workflow_coverage=workflows, ++ telemetry_key=f"mcp.tool.{name}", ++ injection_policy=injection_policy, ++ provenance_contract=provenance, ++ confirmation_policy=confirmation, ++ lifecycle=lifecycle, ++ ) ++ ++ ++_CONTEXT_MODULE = "polylogue.mcp.server_context_tools" ++_MUTATION_MODULE = "polylogue.mcp.server_mutation_tools" ++_PERSONAL_MODULE = "polylogue.mcp.server_personal_state_tools" ++_MAINTENANCE_MODULE = "polylogue.mcp.server_maintenance_tools" ++ ++_CONTEXT_DECLARATIONS = ( ++ _declaration( ++ "get_context_delivery", ++ module=_CONTEXT_MODULE, ++ family="context", ++ verb="context", ++ object_kind="context_delivery_receipt", ++ required_role="read", ++ result_semantics="receipt", ++ canonical_owner="polylogue.api.Polylogue.get_context_delivery", ++ minimal_invocation={ ++ "snapshot_ref": "context-snapshot:session:example:explicit-recall", ++ "recipient_ref": "session:example", ++ }, ++ discovery=("context receipt", "recipient-scoped exact bytes"), ++ continuation=("snapshot_ref", "recipient_ref"), ++ workflows=("context receipt audit", "recipient mismatch privacy"), ++ injection_policy="never_inject", ++ provenance=("snapshot_ref", "recipient_ref", "context_image_sha256"), ++ lifecycle=("read_receipt",), ++ ), ++ _declaration( ++ "build_context_image", ++ module=_CONTEXT_MODULE, ++ family="context", ++ verb="context", ++ object_kind="context_image", ++ required_role="read", ++ result_semantics="bounded_context", ++ canonical_owner="polylogue.api.Polylogue.context_image_payload", ++ minimal_invocation={"project_repo": "polylogue", "max_sessions": 5, "detail_level": "compact"}, ++ discovery=("bounded context", "query-selected sessions", "token omissions"), ++ workflows=("continuity recall", "bounded evidence assembly"), ++ injection_policy="never_inject", ++ provenance=("selected session refs", "context spec", "omission reasons"), ++ lifecycle=("select", "compile", "return"), ++ ), ++ _declaration( ++ "compose_context_preamble", ++ module=_CONTEXT_MODULE, ++ family="context", ++ verb="context", ++ object_kind="context_preamble", ++ required_role="read", ++ result_semantics="bounded_context", ++ canonical_owner="polylogue.context.preamble.build_context_preamble_payload", ++ minimal_invocation={}, ++ discovery=("SessionStart preamble", "lineage", "related sessions"), ++ workflows=("session start continuity",), ++ injection_policy="operator_judgment", ++ provenance=("source_tool_calls", "successor_session_id"), ++ lifecycle=("compose", "return"), ++ prompt_alternative="resume_context", ++ ), ++) ++ ++_ASSERTION_DECLARATIONS = ( ++ _declaration( ++ "capture_assertion_candidate", ++ module=_MUTATION_MODULE, ++ family="assertion", ++ verb="write", ++ object_kind="assertion_candidate", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.capture_assertion_candidate", ++ minimal_invocation={"body_text": "candidate claim", "author_ref": "agent:session:example"}, ++ discovery=("agent-authored candidate", "pending review", "non-injected"), ++ workflows=("candidate capture", "later operator judgment"), ++ injection_policy="candidate_only", ++ provenance=("author_ref", "author_kind=agent", "evidence_refs", "scope_refs"), ++ lifecycle=("capture", "candidate", "receipt"), ++ ), ++ _declaration( ++ "import_annotation_batch", ++ module=_MUTATION_MODULE, ++ family="assertion", ++ verb="write", ++ object_kind="annotation_batch", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.annotations.importer.import_annotation_batch", ++ minimal_invocation={ ++ "jsonl": '{"row_key":"r1","value":{},"evidence_refs":[]}', ++ "batch_id": "batch-example", ++ "schema_id": "delegation.discourse", ++ "schema_version": 1, ++ "target_ref": "delegation:example", ++ "source_result_ref": "result-set:example", ++ "actor_ref": "agent:example", ++ "model_ref": "agent:model", ++ "prompt_ref": "block:example:0", ++ }, ++ discovery=("typed JSONL annotations", "immutable schema", "batch provenance"), ++ workflows=("annotation import", "candidate adjudication"), ++ injection_policy="candidate_only", ++ provenance=( ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref", ++ ), ++ lifecycle=("validate_schema", "validate_refs", "persist_batch", "persist_candidates", "receipt"), ++ ), ++ _declaration( ++ "blackboard_post", ++ module=_MUTATION_MODULE, ++ family="assertion", ++ verb="write", ++ object_kind="blackboard_note", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.post_blackboard_note", ++ minimal_invocation={"kind": "finding", "title": "Finding", "content": "Evidence-backed note"}, ++ discovery=("persistent agent blackboard", "scoped note"), ++ workflows=("coordination handoff", "candidate note"), ++ injection_policy="candidate_only", ++ provenance=("author_ref", "author_kind", "evidence_refs", "scope"), ++ lifecycle=("validate", "persist", "receipt"), ++ ), ++) ++ ++_PERSONAL_DECLARATIONS = ( ++ _declaration( ++ "add_tag", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_tag", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.add_tag", ++ minimal_invocation={"session_id": "session:example", "tag": "review"}, ++ discovery=("add session tag", "idempotent"), ++ workflows=("triage",), ++ provenance=("session_id", "tag", "author_ref", "author_kind"), ++ lifecycle=("resolve", "apply", "receipt"), ++ ), ++ _declaration( ++ "remove_tag", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_tag", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.remove_tag", ++ minimal_invocation={"session_id": "session:example", "tag": "review", "confirm": True}, ++ discovery=("remove session tag", "idempotent"), ++ workflows=("triage correction",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "resolve", "apply", "receipt"), ++ ), ++ _declaration( ++ "bulk_tag_sessions", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_tag_batch", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.bulk_tag_sessions", ++ minimal_invocation={"session_ids": ["session:example"], "tags": ["review"]}, ++ discovery=("bulk tag", "affected/skipped counts"), ++ workflows=("bulk triage",), ++ lifecycle=("validate", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_tags", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="tag_counts", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_tags", ++ discovery=("user-tier tags", "counts"), ++ workflows=("triage readback",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "list_marks", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_mark", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_marks", ++ discovery=("star/pin/hide marks",), ++ workflows=("reader state readback",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "add_mark", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_mark", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.add_mark", ++ minimal_invocation={"session_id": "session:example", "mark_type": "star"}, ++ discovery=("add user mark", "idempotent"), ++ workflows=("reader state",), ++ lifecycle=("resolve", "apply", "receipt"), ++ ), ++ _declaration( ++ "remove_mark", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_mark", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.remove_mark", ++ minimal_invocation={"session_id": "session:example", "mark_type": "star", "confirm": True}, ++ discovery=("remove user mark", "idempotent"), ++ workflows=("reader state correction",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "resolve", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_annotations", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_annotation", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_annotations", ++ discovery=("private annotation readback",), ++ workflows=("annotation workflow",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "save_annotation", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_annotation", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.save_annotation", ++ minimal_invocation={"annotation_id": "ann-example", "session_id": "session:example", "note_text": "note"}, ++ discovery=("save private annotation", "upsert"), ++ workflows=("annotation workflow",), ++ provenance=("annotation_id", "session_id", "target"), ++ lifecycle=("resolve", "upsert", "receipt"), ++ ), ++ _declaration( ++ "delete_annotation", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="user_annotation", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_annotation", ++ minimal_invocation={"annotation_id": "ann-example", "confirm": True}, ++ discovery=("delete private annotation", "idempotent"), ++ workflows=("annotation cleanup",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_saved_views", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="saved_view", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_views", ++ discovery=("saved query views",), ++ workflows=("saved query",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "save_saved_view", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="saved_view", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.save_view", ++ minimal_invocation={"name": "Review", "query_json": "{}"}, ++ discovery=("save query view", "validated query spec"), ++ workflows=("saved query",), ++ lifecycle=("validate", "upsert", "receipt"), ++ ), ++ _declaration( ++ "delete_saved_view", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="saved_view", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_view", ++ minimal_invocation={"view_id": "view-example", "confirm": True}, ++ discovery=("delete saved view", "idempotent"), ++ workflows=("saved query cleanup",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_recall_packs", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="recall_pack", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_recall_packs", ++ discovery=("recall pack readback",), ++ workflows=("continuity recall",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "save_recall_pack", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="recall_pack", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.create_recall_pack", ++ minimal_invocation={"pack_id": "pack-example", "label": "Example", "payload_json": '{"items":[]}'}, ++ discovery=("save recall pack", "typed items"), ++ workflows=("continuity recall",), ++ lifecycle=("validate", "upsert", "receipt"), ++ ), ++ _declaration( ++ "delete_recall_pack", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="recall_pack", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_recall_pack", ++ minimal_invocation={"pack_id": "pack-example", "confirm": True}, ++ discovery=("delete recall pack", "idempotent"), ++ workflows=("continuity cleanup",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_workspaces", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="reader_workspace", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_workspaces", ++ discovery=("reader workspace readback",), ++ workflows=("reader workspace",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "save_workspace", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="reader_workspace", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.save_workspace", ++ minimal_invocation={"workspace_id": "workspace-example", "name": "Example"}, ++ discovery=("save reader workspace",), ++ workflows=("reader workspace",), ++ lifecycle=("validate", "upsert", "receipt"), ++ ), ++ _declaration( ++ "delete_workspace", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="reader_workspace", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_workspace", ++ minimal_invocation={"workspace_id": "workspace-example", "confirm": True}, ++ discovery=("delete reader workspace", "idempotent"), ++ workflows=("reader workspace cleanup",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++ _declaration( ++ "get_metadata", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_metadata", ++ required_role="write", ++ result_semantics="receipt", ++ canonical_owner="polylogue.api.Polylogue.get_metadata", ++ minimal_invocation={"session_id": "session:example"}, ++ discovery=("session metadata readback",), ++ workflows=("metadata workflow",), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "set_metadata", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_metadata", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.set_metadata", ++ minimal_invocation={"session_id": "session:example", "key": "note", "value": "value"}, ++ discovery=("set metadata key", "idempotent"), ++ workflows=("metadata workflow",), ++ lifecycle=("validate", "apply", "receipt"), ++ ), ++ _declaration( ++ "delete_metadata", ++ module=_PERSONAL_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session_metadata", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_metadata", ++ minimal_invocation={"session_id": "session:example", "key": "note", "confirm": True}, ++ discovery=("delete metadata key", "idempotent"), ++ workflows=("metadata cleanup",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "validate", "apply", "receipt"), ++ ), ++ _declaration( ++ "delete_session", ++ module=_MUTATION_MODULE, ++ family="personal_state", ++ verb="write", ++ object_kind="session", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.delete_session_safe", ++ minimal_invocation={"session_id": "session:example", "confirm": True}, ++ discovery=("delete session", "destructive"), ++ workflows=("session deletion",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++) ++ ++_CORRECTION_DECLARATIONS = ( ++ _declaration( ++ "record_correction", ++ module=_PERSONAL_MODULE, ++ family="correction", ++ verb="write", ++ object_kind="insight_correction", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.record_correction", ++ minimal_invocation={"session_id": "session:example", "kind": "phase_boundary", "payload": {}}, ++ discovery=("record derived-insight correction",), ++ workflows=("learning correction",), ++ provenance=("session_id", "kind", "author_ref", "author_kind"), ++ lifecycle=("resolve", "validate", "apply", "receipt"), ++ ), ++ _declaration( ++ "list_corrections", ++ module=_PERSONAL_MODULE, ++ family="correction", ++ verb="write", ++ object_kind="insight_correction", ++ required_role="write", ++ result_semantics="exhaustive_page", ++ canonical_owner="polylogue.api.Polylogue.list_corrections", ++ discovery=("correction readback",), ++ workflows=("learning correction",), ++ continuation=("offset", "limit"), ++ lifecycle=("read",), ++ ), ++ _declaration( ++ "clear_corrections", ++ module=_PERSONAL_MODULE, ++ family="correction", ++ verb="write", ++ object_kind="insight_correction", ++ required_role="write", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.clear_corrections", ++ minimal_invocation={"session_id": "session:example"}, ++ discovery=("clear correction records",), ++ workflows=("learning correction cleanup",), ++ lifecycle=("resolve", "apply", "receipt"), ++ ), ++) ++ ++_JUDGMENT_DECLARATIONS = ( ++ _declaration( ++ "judge_assertion_candidate", ++ module=_MUTATION_MODULE, ++ family="judgment", ++ verb="judge", ++ object_kind="assertion_candidate", ++ required_role="review", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.judge_assertion_candidates", ++ minimal_invocation={"candidate_ref": "assertion:candidate", "decision": "accept", "actor_ref": "user:local"}, ++ discovery=("operator judgment", "accept/reject/defer/supersede"), ++ workflows=("candidate adjudication",), ++ injection_policy="operator_judgment", ++ provenance=("candidate_ref", "actor_ref", "decision", "reason"), ++ lifecycle=("authorize", "judge", "receipt"), ++ ), ++ _declaration( ++ "judge_assertion_candidates", ++ module=_MUTATION_MODULE, ++ family="judgment", ++ verb="judge", ++ object_kind="assertion_judgment_batch", ++ required_role="review", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.judge_assertion_candidates", ++ minimal_invocation={ ++ "items": [{"candidate_ref": "assertion:candidate", "decision": "accept"}], ++ "actor_ref": "user:local", ++ }, ++ discovery=("bulk operator judgment", "partial success"), ++ workflows=("candidate adjudication",), ++ injection_policy="operator_judgment", ++ provenance=("candidate_ref", "actor_ref", "decision", "reason"), ++ lifecycle=("authorize", "validate_each", "judge_each", "receipt"), ++ ), ++) ++ ++_MAINTENANCE_DECLARATIONS = ( ++ _declaration( ++ "maintenance_preview", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="maintenance_plan", ++ required_role="admin", ++ result_semantics="maintenance_preview", ++ canonical_owner="polylogue.maintenance.planner.preview_backfill", ++ discovery=("maintenance dry-run", "exact scope"), ++ workflows=("maintenance plan",), ++ lifecycle=("preview", "receipt"), ++ ), ++ _declaration( ++ "maintenance_execute", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="maintenance_operation", ++ required_role="admin", ++ result_semantics="maintenance_execute", ++ canonical_owner="polylogue.maintenance.planner.execute_backfill", ++ minimal_invocation={"dry_run": True}, ++ discovery=("maintenance execute", "dry-run or explicit confirmation"), ++ workflows=("maintenance apply",), ++ confirmation="dry_run_or_explicit_confirm", ++ lifecycle=("preview_or_confirm", "apply", "receipt", "status"), ++ ), ++ _declaration( ++ "maintenance_status", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="maintenance_operation", ++ required_role="admin", ++ result_semantics="maintenance_status", ++ canonical_owner="polylogue.maintenance.registry.MaintenanceOperationRegistry.get_operation", ++ minimal_invocation={"operation_id": "operation-example"}, ++ discovery=("maintenance status", "persisted receipt"), ++ workflows=("maintenance recovery",), ++ lifecycle=("read_status",), ++ ), ++ _declaration( ++ "maintenance_list", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="maintenance_operation", ++ required_role="admin", ++ result_semantics="maintenance_status", ++ canonical_owner="polylogue.maintenance.registry.MaintenanceOperationRegistry.list_operations", ++ discovery=("maintenance operation list",), ++ workflows=("maintenance recovery",), ++ lifecycle=("read_status",), ++ ), ++ _declaration( ++ "rebuild_index", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="search_index", ++ required_role="admin", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.rebuild_index", ++ minimal_invocation={"confirm": True}, ++ discovery=("full index rebuild",), ++ workflows=("index repair",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++ _declaration( ++ "update_index", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="search_index", ++ required_role="admin", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.update_index", ++ minimal_invocation={"session_ids": ["session:example"]}, ++ discovery=("incremental index update",), ++ workflows=("index catch-up",), ++ lifecycle=("apply", "receipt"), ++ ), ++ _declaration( ++ "rebuild_session_insights", ++ module=_MAINTENANCE_MODULE, ++ family="maintenance", ++ verb="maintenance", ++ object_kind="session_insights", ++ required_role="admin", ++ result_semantics="mutation", ++ canonical_owner="polylogue.api.Polylogue.rebuild_insights", ++ minimal_invocation={"confirm": True}, ++ discovery=("derived insight rebuild",), ++ workflows=("derived repair",), ++ confirmation="explicit_confirm", ++ lifecycle=("confirm", "apply", "receipt"), ++ ), ++) ++ ++MCP_CONTEXT_ASSERTION_DECLARATIONS: tuple[MCPToolDeclaration, ...] = ( ++ *_CONTEXT_DECLARATIONS, ++ *_ASSERTION_DECLARATIONS, ++ *_PERSONAL_DECLARATIONS, ++ *_CORRECTION_DECLARATIONS, ++ *_JUDGMENT_DECLARATIONS, ++ *_MAINTENANCE_DECLARATIONS, ++) ++ ++ ++def _build_registry(declarations: Iterable[MCPToolDeclaration]) -> dict[str, MCPToolDeclaration]: ++ registry: dict[str, MCPToolDeclaration] = {} ++ for declaration in declarations: ++ if declaration.name in registry: ++ raise ValueError(f"duplicate MCP declaration: {declaration.name}") ++ registry[declaration.name] = declaration ++ return registry ++ ++ ++MCP_CONTEXT_ASSERTION_REGISTRY = _build_registry(MCP_CONTEXT_ASSERTION_DECLARATIONS) ++MCP_CONTEXT_ASSERTION_TOOL_NAMES = frozenset(MCP_CONTEXT_ASSERTION_REGISTRY) ++ ++ ++def declaration_for(name: str) -> MCPToolDeclaration: ++ """Return one declaration, failing with an actionable adapter error.""" ++ ++ try: ++ return MCP_CONTEXT_ASSERTION_REGISTRY[name] ++ except KeyError as exc: ++ raise KeyError(f"MCP handler {name!r} has no context/assertion declaration") from exc ++ ++ ++def declarations_for_role(role: MCPRole) -> tuple[MCPToolDeclaration, ...]: ++ """Return declarations visible to one authenticated role.""" ++ ++ return tuple( ++ declaration ++ for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS ++ if role_allows(role, declaration.required_role) ++ ) ++ ++ ++def equivalence_document() -> dict[str, object]: ++ """Return the generated migration/equivalence document.""" ++ ++ by_family: dict[str, int] = {} ++ by_role: dict[str, int] = {} ++ for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS: ++ by_family[declaration.family] = by_family.get(declaration.family, 0) + 1 ++ by_role[declaration.required_role] = by_role.get(declaration.required_role, 0) + 1 ++ return { ++ "schema_version": "polylogue.mcp.context-assertion-equivalence.v1", ++ "migration_owner": "polylogue-t46.8.3", ++ "dependency": "polylogue-t46.8.1", ++ "tool_count": len(MCP_CONTEXT_ASSERTION_DECLARATIONS), ++ "counts_by_family": dict(sorted(by_family.items())), ++ "counts_by_required_role": dict(sorted(by_role.items())), ++ "declarations": [declaration.to_equivalence_dict() for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS], ++ } ++ ++ ++def render_equivalence_json() -> str: ++ """Render the committed equivalence artifact deterministically.""" ++ ++ return json.dumps(equivalence_document(), indent=2, sort_keys=True, ensure_ascii=False) + "\n" ++ ++ ++def validate_registry() -> None: ++ """Fail closed on trust, role, provenance, and maintenance drift.""" ++ ++ if len(MCP_CONTEXT_ASSERTION_TOOL_NAMES) != len(MCP_CONTEXT_ASSERTION_DECLARATIONS): ++ raise ValueError("MCP declaration names must be unique") ++ ++ for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS: ++ if declaration.compatibility_route != declaration.name: ++ raise ValueError(f"{declaration.name}: compatibility route must preserve the public tool name") ++ if declaration.canonical_plan != declaration.canonical_owner: ++ raise ValueError(f"{declaration.name}: canonical plan must name the existing production owner") ++ if declaration.telemetry_key != f"mcp.tool.{declaration.name}": ++ raise ValueError(f"{declaration.name}: telemetry key must preserve per-tool attribution") ++ if not declaration.module.startswith("polylogue.mcp."): ++ raise ValueError(f"{declaration.name}: declaration module must name the production MCP adapter") ++ if declaration.family == "context" and declaration.required_role != "read": ++ raise ValueError(f"{declaration.name}: context reads must remain read-role tools") ++ if declaration.family == "judgment" and declaration.required_role != "review": ++ raise ValueError(f"{declaration.name}: judgment tools require review capability") ++ if declaration.family == "maintenance" and declaration.required_role != "admin": ++ raise ValueError(f"{declaration.name}: maintenance tools require admin capability") ++ if declaration.injection_policy == "candidate_only" and declaration.required_role != "write": ++ raise ValueError(f"{declaration.name}: candidate writers must remain write-role tools") ++ if ( ++ declaration.confirmation_policy not in {"none", "dry_run_or_explicit_confirm"} ++ and "confirm" not in declaration.minimal_invocation() ++ ): ++ raise ValueError(f"{declaration.name}: confirmation declaration needs confirm=true example") ++ ++ capture = declaration_for("capture_assertion_candidate") ++ if capture.injection_policy != "candidate_only" or not {"author_ref", "author_kind=agent"} <= set( ++ capture.provenance_contract ++ ): ++ raise ValueError("capture_assertion_candidate must preserve agent candidate provenance") ++ ++ annotation_import = declaration_for("import_annotation_batch") ++ required_batch_provenance = { ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref", ++ } ++ if not required_batch_provenance <= set(annotation_import.provenance_contract): ++ raise ValueError("import_annotation_batch must preserve immutable schema/batch provenance") ++ ++ for name in ("judge_assertion_candidate", "judge_assertion_candidates"): ++ declaration = declaration_for(name) ++ if declaration.injection_policy != "operator_judgment": ++ raise ValueError(f"{name}: injection remains an operator judgment") ++ ++ for name in ( ++ "delete_annotation", ++ "delete_saved_view", ++ "delete_recall_pack", ++ "delete_workspace", ++ "delete_metadata", ++ "remove_tag", ++ "remove_mark", ++ "rebuild_index", ++ "rebuild_session_insights", ++ ): ++ if declaration_for(name).confirmation_policy != "explicit_confirm": ++ raise ValueError(f"{name}: interim confirmation contract missing") ++ if declaration_for("maintenance_execute").confirmation_policy != "dry_run_or_explicit_confirm": ++ raise ValueError("maintenance_execute must allow dry-run or require explicit confirmation") ++ ++ ++validate_registry() ++ ++__all__ = [ ++ "MCP_CONTEXT_ASSERTION_DECLARATIONS", ++ "MCP_CONTEXT_ASSERTION_REGISTRY", ++ "MCP_CONTEXT_ASSERTION_TOOL_NAMES", ++ "declaration_for", ++ "declarations_for_role", ++ "equivalence_document", ++ "render_equivalence_json", ++ "validate_registry", ++] +diff --git a/polylogue/mcp/server_context_tools.py b/polylogue/mcp/server_context_tools.py +index e0a49bb5e3fb15a04e01aab474a95356c4534005..c34720f6d867482f2065c0909f33900e314f36ba 100644 +--- a/polylogue/mcp/server_context_tools.py ++++ b/polylogue/mcp/server_context_tools.py +@@ -11,9 +11,9 @@ from __future__ import annotations + + from typing import TYPE_CHECKING + +-if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP ++from polylogue.mcp.declarations.registration import MCPDeclarationRegistrar + ++if TYPE_CHECKING: + from polylogue.mcp.server_support import ServerCallbacks + + _DEFAULT_MAX_SESSIONS = 5 +@@ -30,8 +30,8 @@ def _detail_includes_messages(detail_level: str) -> bool: + return _DETAIL_INCLUDES_MESSAGES.get(detail_level, True) + + +-def register_context_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: +- @mcp.tool() ++def register_context_tools(registrar: MCPDeclarationRegistrar, hooks: ServerCallbacks) -> None: ++ @registrar.tool() + async def get_context_delivery(snapshot_ref: str, recipient_ref: str) -> str: + """Read the exact persisted context image for its recorded recipient. + +@@ -58,7 +58,7 @@ def register_context_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_context_delivery", run) + +- @mcp.tool() ++ @registrar.tool() + async def build_context_image( + project_path: str | None = None, + project_repo: str | None = None, +@@ -112,7 +112,7 @@ def register_context_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("build_context_image", run) + +- @mcp.tool() ++ @registrar.tool() + async def compose_context_preamble( + repo_path: str | None = None, + cwd: str | None = None, +diff --git a/polylogue/mcp/server_maintenance_tools.py b/polylogue/mcp/server_maintenance_tools.py +index 36674caa31b83e9409ce37b1daac6c66a983fd8e..c91fcea4f42509b0fc6e9494c1cf0df8ab6372ab 100644 +--- a/polylogue/mcp/server_maintenance_tools.py ++++ b/polylogue/mcp/server_maintenance_tools.py +@@ -8,11 +8,10 @@ from typing import TYPE_CHECKING + + from polylogue.core.enums import Origin + from polylogue.maintenance.scope import MaintenanceScopeFilter ++from polylogue.mcp.declarations.registration import MCPDeclarationRegistrar + from polylogue.mcp.payloads import MCPMutationStatusPayload, MCPRootPayload + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -53,8 +52,8 @@ def _build_mcp_scope_filter( + ) + + +-def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: +- @mcp.tool() ++def register_maintenance_tools(registrar: MCPDeclarationRegistrar, hooks: ServerCallbacks) -> None: ++ @registrar.tool() + async def maintenance_preview( + targets: list[str] | None = None, + session_ids: list[str] | None = None, +@@ -109,10 +108,11 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + session_ids=tuple(session_ids or ()), + ) + +- @mcp.tool() ++ @registrar.tool() + async def maintenance_execute( + targets: list[str] | None = None, + dry_run: bool = False, ++ confirm: bool = False, + session_ids: list[str] | None = None, + origin: str | None = None, + source_family: str | None = None, +@@ -132,6 +132,11 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + """ + + async def run() -> str: ++ if not dry_run and not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to execute maintenance", ++ code="confirmation_required", ++ ) + from polylogue.config import Config + from polylogue.maintenance.envelope import envelope_from_operation + from polylogue.maintenance.planner import execute_backfill +@@ -163,7 +168,7 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + session_ids=tuple(session_ids or ()), + ) + +- @mcp.tool() ++ @registrar.tool() + async def maintenance_status(operation_id: str) -> str: + """Return one persisted maintenance operation snapshot by id (#1197). + +@@ -202,7 +207,7 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("maintenance_status", run) + +- @mcp.tool() ++ @registrar.tool() + async def maintenance_list() -> str: + """List every persisted maintenance operation snapshot (#1197). + +@@ -244,9 +249,14 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("maintenance_list", run) + +- @mcp.tool() +- async def rebuild_index() -> str: ++ @registrar.tool() ++ async def rebuild_index(confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to rebuild the index", ++ code="confirmation_required", ++ ) + poly = hooks.get_polylogue() + success = await poly.rebuild_index() + status_info = await poly.get_index_status() +@@ -263,7 +273,7 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("rebuild_index", run) + +- @mcp.tool() ++ @registrar.tool() + async def update_index(session_ids: list[str]) -> str: + async def run() -> str: + success = await hooks.get_polylogue().update_index(session_ids) +@@ -277,9 +287,17 @@ def register_maintenance_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("update_index", run, session_ids=tuple(session_ids)) + +- @mcp.tool() +- async def rebuild_session_insights(session_ids: list[str] | None = None) -> str: ++ @registrar.tool() ++ async def rebuild_session_insights( ++ session_ids: list[str] | None = None, ++ confirm: bool = False, ++ ) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to rebuild session insights", ++ code="confirmation_required", ++ ) + poly = hooks.get_polylogue() + counts = await poly.rebuild_insights(session_ids=session_ids) + return hooks.json_payload( +diff --git a/polylogue/mcp/server_mutation_tools.py b/polylogue/mcp/server_mutation_tools.py +index ac8a2f903548eb20ae722233424654cd0332571c..8fdca78cf4ec2a5a909deaa2eace2cb2cc4fe1ef 100644 +--- a/polylogue/mcp/server_mutation_tools.py ++++ b/polylogue/mcp/server_mutation_tools.py +@@ -13,6 +13,7 @@ from polylogue.annotations.importer import ( + ) + from polylogue.core.user_state_targets import MARK_TYPE_NAMES, TARGET_SESSION, is_mark_type_supported + from polylogue.mcp.archive_support import blackboard_note_payload ++from polylogue.mcp.declarations.registration import MCPDeclarationRegistrar + from polylogue.mcp.mutation_support import page_items, resolve_session_or_error + from polylogue.mcp.payloads import ( + MCPTagCountsPayload, +@@ -24,8 +25,6 @@ from polylogue.mcp.query_contracts import MCPToolLimit, MCPToolOffset + from polylogue.mcp.server_personal_state_tools import register_personal_state_tools + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -39,8 +38,8 @@ def _none_if_empty(value: str | None) -> str | None: + return value if value else None + + +-def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: +- @mcp.tool() ++def register_mutation_tools(registrar: MCPDeclarationRegistrar, hooks: ServerCallbacks) -> None: ++ @registrar.tool() + async def capture_assertion_candidate( + body_text: str, + author_ref: str, +@@ -74,7 +73,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("capture_assertion_candidate", run) + +- @mcp.tool() ++ @registrar.tool() + async def import_annotation_batch( + jsonl: str, + batch_id: str, +@@ -110,7 +109,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("import_annotation_batch", run) + +- @mcp.tool() ++ @registrar.tool() + async def blackboard_post( + kind: str, + title: str, +@@ -158,7 +157,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("blackboard_post", run, session_id=scope_session) + +- @mcp.tool() ++ @registrar.tool() + async def add_tag( + session_id: str, + tag: str, +@@ -187,9 +186,15 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("add_tag", run, session_id=session_id) + +- @mcp.tool() +- async def remove_tag(session_id: str, tag: str) -> str: ++ @registrar.tool() ++ async def remove_tag(session_id: str, tag: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to remove tag", ++ code="confirmation_required", ++ session_id=session_id, ++ ) + resolved, err = await resolve_session_or_error(hooks, session_id) + if err: + return err +@@ -209,7 +214,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("remove_tag", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def bulk_tag_sessions(session_ids: list[str], tags: list[str]) -> str: + async def run() -> str: + poly = hooks.get_polylogue() +@@ -230,7 +235,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("bulk_tag_sessions", run, session_ids=tuple(session_ids)) + +- @mcp.tool() ++ @registrar.tool() + async def list_tags( + origin: str | None = None, + limit: MCPToolLimit = 50, +@@ -256,7 +261,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_tags", run) + +- @mcp.tool() ++ @registrar.tool() + async def list_marks( + mark_type: str | None = None, + session_id: str | None = None, +@@ -312,7 +317,7 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_marks", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def add_mark( + session_id: str, + mark_type: str, +@@ -349,15 +354,22 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("add_mark", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def remove_mark( + session_id: str, + mark_type: str, + target_type: str = TARGET_SESSION, + target_id: str | None = None, + message_id: str | None = None, ++ confirm: bool = False, + ) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to remove mark", ++ code="confirmation_required", ++ session_id=session_id, ++ ) + mark_error = _mark_type_error(hooks, mark_type) + if mark_error: + return mark_error +@@ -386,14 +398,15 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("remove_mark", run, session_id=session_id) + +- register_personal_state_tools(mcp, hooks) ++ register_personal_state_tools(registrar, hooks) + +- @mcp.tool() ++ @registrar.tool() + async def delete_session(session_id: str, confirm: bool = False) -> str: + async def run() -> str: + if not confirm: + return hooks.error_json( + "Safety guard: set confirm=true to delete", ++ code="confirmation_required", + session_id=session_id, + ) + +@@ -411,10 +424,10 @@ def register_mutation_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + return await hooks.async_safe_call("delete_session", run, session_id=session_id) + + +-def register_assertion_review_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_assertion_review_tools(registrar: MCPDeclarationRegistrar, hooks: ServerCallbacks) -> None: + """Register promotion tools only for authenticated review capability.""" + +- @mcp.tool() ++ @registrar.tool() + async def judge_assertion_candidate( + candidate_ref: str, + decision: Literal["accept", "reject", "defer", "supersede"], +@@ -448,7 +461,7 @@ def register_assertion_review_tools(mcp: FastMCP, hooks: ServerCallbacks) -> Non + + return await hooks.async_safe_call("judge_assertion_candidate", run) + +- @mcp.tool() ++ @registrar.tool() + async def judge_assertion_candidates( + items: list[dict[str, object]], + actor_ref: str = "user:local", +diff --git a/polylogue/mcp/server_personal_state_tools.py b/polylogue/mcp/server_personal_state_tools.py +index 9888418833447224559a28efd0ebe9727d15220c..be3f6fbd40b2e3f4c626ab50db5bd85f36504f66 100644 +--- a/polylogue/mcp/server_personal_state_tools.py ++++ b/polylogue/mcp/server_personal_state_tools.py +@@ -13,6 +13,7 @@ from hashlib import sha256 + from typing import TYPE_CHECKING + + from polylogue.core.user_state_targets import TARGET_SESSION ++from polylogue.mcp.declarations.registration import MCPDeclarationRegistrar + from polylogue.mcp.mutation_support import page_items, resolve_session_or_error + from polylogue.mcp.payloads import ( + MCPMetadataPayload, +@@ -30,8 +31,6 @@ from polylogue.mcp.payloads import ( + from polylogue.mcp.query_contracts import MCPCharacterLimit, MCPToolLimit, MCPToolOffset + + if TYPE_CHECKING: +- from mcp.server.fastmcp import FastMCP +- + from polylogue.mcp.server_support import ServerCallbacks + + +@@ -146,10 +145,10 @@ def _annotation_payload(row: dict[str, str]) -> MCPUserAnnotationPayload: + ) + + +-def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++def register_personal_state_tools(registrar: MCPDeclarationRegistrar, hooks: ServerCallbacks) -> None: + """Register annotations, views, recall packs, workspaces, metadata and corrections.""" + +- @mcp.tool() ++ @registrar.tool() + async def list_annotations( + session_id: str | None = None, + target_type: str | None = None, +@@ -192,7 +191,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_annotations", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def save_annotation( + annotation_id: str, + session_id: str, +@@ -222,9 +221,14 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("save_annotation", run, session_id=session_id) + +- @mcp.tool() +- async def delete_annotation(annotation_id: str) -> str: ++ @registrar.tool() ++ async def delete_annotation(annotation_id: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to delete annotation", ++ code="confirmation_required", ++ ) + deleted = await hooks.get_polylogue().delete_annotation(annotation_id) + return hooks.json_payload( + MutationResultPayload( +@@ -237,7 +241,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("delete_annotation", run) + +- @mcp.tool() ++ @registrar.tool() + async def list_saved_views( + limit: MCPToolLimit = 50, offset: MCPToolOffset = 0, max_chars_per_item: MCPCharacterLimit = None + ) -> str: +@@ -263,7 +267,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_saved_views", run) + +- @mcp.tool() ++ @registrar.tool() + async def save_saved_view(name: str, query_json: str, view_id: str | None = None) -> str: + async def run() -> str: + if not name.strip(): +@@ -292,9 +296,14 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("save_saved_view", run) + +- @mcp.tool() +- async def delete_saved_view(view_id: str) -> str: ++ @registrar.tool() ++ async def delete_saved_view(view_id: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to delete saved view", ++ code="confirmation_required", ++ ) + deleted = await hooks.get_polylogue().delete_view(view_id) + return hooks.json_payload( + MutationResultPayload( +@@ -307,7 +316,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("delete_saved_view", run) + +- @mcp.tool() ++ @registrar.tool() + async def list_recall_packs( + limit: MCPToolLimit = 50, offset: MCPToolOffset = 0, max_chars_per_item: MCPCharacterLimit = None + ) -> str: +@@ -338,7 +347,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_recall_packs", run) + +- @mcp.tool() ++ @registrar.tool() + async def save_recall_pack(pack_id: str, label: str, payload_json: str = "{}") -> str: + async def run() -> str: + if not pack_id.strip(): +@@ -364,9 +373,14 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("save_recall_pack", run) + +- @mcp.tool() +- async def delete_recall_pack(pack_id: str) -> str: ++ @registrar.tool() ++ async def delete_recall_pack(pack_id: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to delete recall pack", ++ code="confirmation_required", ++ ) + deleted = await hooks.get_polylogue().delete_recall_pack(pack_id) + return hooks.json_payload( + MutationResultPayload( +@@ -379,7 +393,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("delete_recall_pack", run) + +- @mcp.tool() ++ @registrar.tool() + async def list_workspaces( + limit: MCPToolLimit = 50, offset: MCPToolOffset = 0, max_chars_per_item: MCPCharacterLimit = None + ) -> str: +@@ -413,7 +427,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_workspaces", run) + +- @mcp.tool() ++ @registrar.tool() + async def save_workspace( + workspace_id: str, + name: str, +@@ -462,9 +476,14 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("save_workspace", run) + +- @mcp.tool() +- async def delete_workspace(workspace_id: str) -> str: ++ @registrar.tool() ++ async def delete_workspace(workspace_id: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to delete workspace", ++ code="confirmation_required", ++ ) + deleted = await hooks.get_polylogue().delete_workspace(workspace_id) + return hooks.json_payload( + MutationResultPayload( +@@ -477,7 +496,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("delete_workspace", run) + +- @mcp.tool() ++ @registrar.tool() + async def get_metadata(session_id: str) -> str: + async def run() -> str: + metadata = await hooks.get_polylogue().get_metadata(session_id) +@@ -485,7 +504,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("get_metadata", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def set_metadata(session_id: str, key: str, value: str) -> str: + async def run() -> str: + from polylogue.api.archive import SessionNotFoundError +@@ -515,9 +534,15 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("set_metadata", run, session_id=session_id) + +- @mcp.tool() +- async def delete_metadata(session_id: str, key: str) -> str: ++ @registrar.tool() ++ async def delete_metadata(session_id: str, key: str, confirm: bool = False) -> str: + async def run() -> str: ++ if not confirm: ++ return hooks.error_json( ++ "Safety guard: set confirm=true to delete metadata", ++ code="confirmation_required", ++ session_id=session_id, ++ ) + from polylogue.api.archive import SessionNotFoundError + from polylogue.surfaces.payloads import MetadataKeyValidationError, validate_metadata_key + +@@ -542,7 +567,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("delete_metadata", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def record_correction( + session_id: str, + kind: str, +@@ -580,7 +605,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("record_correction", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def list_corrections( + session_id: str | None = None, + kind: str | None = None, +@@ -637,7 +662,7 @@ def register_personal_state_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + return await hooks.async_safe_call("list_corrections", run, session_id=session_id) + +- @mcp.tool() ++ @registrar.tool() + async def clear_corrections(session_id: str, kind: str | None = None) -> str: + """Delete one or all corrections for a session.""" + +diff --git a/polylogue/mcp/server_tools.py b/polylogue/mcp/server_tools.py +index 5c08d927da843bda6c12ca39da18cf748f8440dc..a195f8fa9828a0542399b57983cf3e97060b8429 100644 +--- a/polylogue/mcp/server_tools.py ++++ b/polylogue/mcp/server_tools.py +@@ -23,6 +23,7 @@ from polylogue.mcp.archive_support import ( + blackboard_note_payload, + mcp_archive_root, + ) ++from polylogue.mcp.declarations.registration import MCPDeclarationRegistrar + from polylogue.mcp.payloads import ( + MCPArchiveSearchHitPayload, + MCPArchiveSearchPayload, +@@ -57,7 +58,6 @@ from polylogue.mcp.server_context_tools import register_context_tools + from polylogue.mcp.server_insight_tools import register_insight_tools + from polylogue.mcp.server_maintenance_tools import register_maintenance_tools + from polylogue.mcp.server_mutation_tools import register_assertion_review_tools, register_mutation_tools +-from polylogue.mcp.server_support import role_allows + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + + if TYPE_CHECKING: +@@ -1310,16 +1310,15 @@ def register_read_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + + + def register_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: ++ registrar = MCPDeclarationRegistrar(mcp, role=hooks.role) + register_query_tools(mcp, hooks) + register_read_tools(mcp, hooks) +- register_context_tools(mcp, hooks) ++ register_context_tools(registrar, hooks) + register_insight_tools(mcp, hooks) +- if role_allows(hooks.role, "write"): +- register_mutation_tools(mcp, hooks) +- if role_allows(hooks.role, "review"): +- register_assertion_review_tools(mcp, hooks) +- if role_allows(hooks.role, "admin"): +- register_maintenance_tools(mcp, hooks) ++ register_mutation_tools(registrar, hooks) ++ register_assertion_review_tools(registrar, hooks) ++ register_maintenance_tools(registrar, hooks) ++ registrar.assert_complete() + + + __all__ = ["register_query_tools", "register_read_tools", "register_tools"] +diff --git a/tests/infra/mcp.py b/tests/infra/mcp.py +index 258ab5e84f088709969191f6152a1c96033cded5..5c7af1e418d0b38ac1e3bd268ec0e995036186e7 100644 +--- a/tests/infra/mcp.py ++++ b/tests/infra/mcp.py +@@ -10,50 +10,27 @@ from unittest.mock import AsyncMock, MagicMock + + from polylogue.archive.models import Session + from polylogue.core.enums import Provider ++from polylogue.mcp.declarations.registry import MCP_CONTEXT_ASSERTION_TOOL_NAMES + from tests.infra.builders import make_conv, make_msg + +-EXPECTED_TOOL_NAMES = { ++_EXPECTED_UNMIGRATED_TOOL_NAMES = { + "search", + "query_units", + "resolve_ref", + "explain_import", + "list_sessions", + "compile_context", +- "get_context_delivery", +- "build_context_image", + "blackboard_list", +- "blackboard_post", +- "capture_assertion_candidate", +- "import_annotation_batch", + "join_typed_annotations", + "list_assertion_claims", + "list_assertion_candidates", + "list_assertion_candidate_reviews", +- "judge_assertion_candidate", +- "judge_assertion_candidates", + "neighbor_candidates", + "stats", + "embedding_status", + "embedding_preflight", + "facets", + "agent_coordination", +- "add_tag", +- "remove_tag", +- "bulk_tag_sessions", +- "list_tags", +- "list_marks", +- "add_mark", +- "remove_mark", +- "list_annotations", +- "save_annotation", +- "delete_annotation", +- "list_saved_views", +- "save_saved_view", +- "delete_saved_view", +- "get_metadata", +- "set_metadata", +- "delete_metadata", +- "delete_session", + "get_session_summary", + "get_session_tree", + "get_session_topology", +@@ -64,15 +41,8 @@ EXPECTED_TOOL_NAMES = { + "query_completions", + "readiness_check", + "named_source_freshness", +- "rebuild_index", +- "update_index", + "get_postmortem_bundle", + "get_pathologies", +- "rebuild_session_insights", +- "maintenance_preview", +- "maintenance_execute", +- "maintenance_status", +- "maintenance_list", + "session_profile", + "session_latency_profile", + "tool_call_latency_distribution", +@@ -103,21 +73,12 @@ EXPECTED_TOOL_NAMES = { + "archive_get_session", + "archive_list_sessions", + "archive_search_sessions", +- "clear_corrections", +- "compose_context_preamble", +- "delete_recall_pack", +- "delete_workspace", + "get_messages", + "insight_rigor_audit", +- "list_corrections", +- "list_recall_packs", +- "list_workspaces", + "provider_usage", + "raw_artifacts", +- "record_correction", +- "save_recall_pack", +- "save_workspace", + } ++EXPECTED_TOOL_NAMES = _EXPECTED_UNMIGRATED_TOOL_NAMES | set(MCP_CONTEXT_ASSERTION_TOOL_NAMES) + + EXPECTED_RESOURCE_URIS = { + "polylogue://stats", +diff --git a/tests/unit/maintenance/test_envelope_contracts.py b/tests/unit/maintenance/test_envelope_contracts.py +index ddd849995f31d60be705d0921e9d9a3a4d572c45..31571dff2071a52950a385ad60df1781950a5c8a 100644 +--- a/tests/unit/maintenance/test_envelope_contracts.py ++++ b/tests/unit/maintenance/test_envelope_contracts.py +@@ -271,6 +271,32 @@ class TestMaintenanceMCPTools: + assert payload["origin"] == "mcp" + assert payload["mode"] == "preview" + ++ def test_maintenance_execute_dry_run_does_not_require_confirmation(self) -> None: ++ """Dry-run remains observable while execution fails closed without confirm. ++ ++ Production dependency: the distinct ``maintenance_execute`` handler's ++ ``dry_run or confirm`` gate. Requiring confirmation for dry-run would ++ break safe planning; removing the gate would let real execution reach ++ ``execute_backfill`` without explicit authorization. ++ """ ++ from polylogue.mcp.server import build_server ++ ++ operation = _example_operation() ++ server = build_server(role="admin") ++ fn = server._tool_manager._tools["maintenance_execute"].fn ++ ++ with patch( ++ "polylogue.maintenance.planner.execute_backfill", ++ return_value=operation, ++ ) as execute_backfill: ++ result = asyncio.run(fn(dry_run=True)) ++ ++ payload = json.loads(result) ++ assert payload["origin"] == "mcp" ++ assert payload["mode"] == "execute" ++ execute_backfill.assert_called_once() ++ assert execute_backfill.call_args.kwargs["dry_run"] is True ++ + + # --------------------------------------------------------------------------- + # Helpers +diff --git a/tests/unit/mcp/test_context_assertion_declarations.py b/tests/unit/mcp/test_context_assertion_declarations.py +new file mode 100644 +index 0000000000000000000000000000000000000000..fa5e6c99b8aebf6f4c5d3bac087e2954984cbece +--- /dev/null ++++ b/tests/unit/mcp/test_context_assertion_declarations.py +@@ -0,0 +1,253 @@ ++"""Executable contracts for the context/assertion MCP declaration migration.""" ++ ++from __future__ import annotations ++ ++import inspect ++import json ++from pathlib import Path ++from typing import cast ++from unittest.mock import patch ++ ++import pytest ++ ++from polylogue.api import Polylogue ++from polylogue.mcp.declarations.registry import ( ++ MCP_CONTEXT_ASSERTION_DECLARATIONS, ++ MCP_CONTEXT_ASSERTION_TOOL_NAMES, ++ declaration_for, ++ declarations_for_role, ++ render_equivalence_json, ++) ++from polylogue.mcp.server import build_server ++from polylogue.mcp.server_support import MCPRole ++from tests.infra.mcp import EXPECTED_TOOL_NAMES, MCPServerUnderTest, invoke_surface ++from tests.infra.storage_records import SessionBuilder ++ ++_EQUIVALENCE_ARTIFACT = Path("docs/generated/mcp-context-assertion-equivalence.json") ++ ++ ++def _tool_names(role: MCPRole) -> set[str]: ++ server = cast(MCPServerUnderTest, build_server(role=role)) ++ return set(server._tool_manager._tools) ++ ++ ++@pytest.mark.parametrize("role", ("read", "write", "review", "admin")) ++def test_declarations_drive_role_scoped_registration(role: MCPRole) -> None: ++ """Production dependency: ``register_tools`` and ``MCPDeclarationRegistrar``. ++ ++ Removing declaration-owned filtering, restoring a family-level role gate, ++ or changing a declaration's required role makes this exact discovery ++ equivalence fail. ++ """ ++ ++ actual = _tool_names(role) & MCP_CONTEXT_ASSERTION_TOOL_NAMES ++ expected = {declaration.name for declaration in declarations_for_role(role)} ++ assert actual == expected ++ ++ ++def test_declarations_bind_exact_production_handlers_and_input_contracts() -> None: ++ """Production dependency: every registered FastMCP handler and signature. ++ ++ Replacing a handler with a wrapper in another module, deleting a handler, ++ dropping a minimal-invocation parameter, or removing a confirmation default ++ fails this test (and the production registrar's startup validation). ++ """ ++ ++ server = cast(MCPServerUnderTest, build_server(role="admin")) ++ for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS: ++ handler = server._tool_manager._tools[declaration.name].fn ++ assert handler.__module__ == declaration.module, declaration.name ++ assert inspect.iscoroutinefunction(handler), declaration.name ++ signature = inspect.signature(handler) ++ assert set(declaration.minimal_invocation()) <= set(signature.parameters), declaration.name ++ if declaration.confirmation_policy != "none": ++ assert signature.parameters["confirm"].default is False, declaration.name ++ if declaration.confirmation_policy == "dry_run_or_explicit_confirm": ++ assert signature.parameters["dry_run"].default is False, declaration.name ++ ++ ++def test_registry_preserves_trust_injection_and_annotation_provenance() -> None: ++ """Production dependency: candidate, judgment, and annotation declarations. ++ ++ Promoting agent-authored candidates to injectable writes, weakening review ++ role, or omitting immutable schema/batch provenance makes these assertions ++ fail before an adapter can register. ++ """ ++ ++ capture = declaration_for("capture_assertion_candidate") ++ assert capture.required_role == "write" ++ assert capture.injection_policy == "candidate_only" ++ assert {"author_ref", "author_kind=agent"} <= set(capture.provenance_contract) ++ ++ annotation_import = declaration_for("import_annotation_batch") ++ assert annotation_import.injection_policy == "candidate_only" ++ assert { ++ "batch_id", ++ "schema_id", ++ "schema_version", ++ "source_result_ref", ++ "actor_ref", ++ "model_ref", ++ "prompt_ref", ++ } <= set(annotation_import.provenance_contract) ++ ++ for name in ("judge_assertion_candidate", "judge_assertion_candidates"): ++ judgment = declaration_for(name) ++ assert judgment.required_role == "review" ++ assert judgment.injection_policy == "operator_judgment" ++ assert "actor_ref" in judgment.provenance_contract ++ ++ ++def test_generated_equivalence_artifact_matches_executable_registry() -> None: ++ """Production dependency: ``render_equivalence_json`` and the registry. ++ ++ Editing the committed artifact, changing a declaration without regenerating ++ it, or returning unstable declaration ordering makes byte equality fail. ++ """ ++ ++ assert _EQUIVALENCE_ARTIFACT.read_text() == render_equivalence_json() ++ ++ ++def test_declarations_do_not_claim_unimplemented_protocol_routes() -> None: ++ """Production dependency: current MCP resource/prompt registration. ++ ++ Adding an invented URI or naming a prompt that is not actually registered ++ makes this fail. The migration may describe only real compatibility routes ++ until a production resource resolver is implemented. ++ """ ++ ++ assert all(declaration.resource_alternative is None for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS) ++ prompt_alternatives = { ++ declaration.name: declaration.prompt_alternative ++ for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS ++ if declaration.prompt_alternative is not None ++ } ++ assert prompt_alternatives == {"compose_context_preamble": "resume_context"} ++ ++ server = cast(MCPServerUnderTest, build_server(role="admin")) ++ assert "resume_context" in server._prompt_manager._prompts ++ ++ ++def test_expected_tool_inventory_uses_declared_names() -> None: ++ """Production dependency: the shared discovery inventory in ``tests.infra``. ++ ++ Removing the registry union or omitting one migrated declaration makes the ++ expected surface diverge from the executable declaration set. ++ """ ++ ++ assert MCP_CONTEXT_ASSERTION_TOOL_NAMES <= EXPECTED_TOOL_NAMES ++ assert len(MCP_CONTEXT_ASSERTION_TOOL_NAMES) == 41 ++ ++ ++def test_declared_confirmation_contracts_fail_closed_before_backend_access() -> None: ++ """Production dependency: all individually implemented destructive handlers. ++ ++ Removing any per-handler guard (or moving it after session resolution, ++ planner construction, or facade access) permits the patched backend seam to ++ be touched and fails this test. The test intentionally does not use a ++ generic mutation dispatcher. ++ """ ++ ++ guarded = tuple( ++ declaration for declaration in MCP_CONTEXT_ASSERTION_DECLARATIONS if declaration.confirmation_policy != "none" ++ ) ++ assert {declaration.name for declaration in guarded} == { ++ "remove_tag", ++ "remove_mark", ++ "delete_annotation", ++ "delete_saved_view", ++ "delete_recall_pack", ++ "delete_workspace", ++ "delete_metadata", ++ "delete_session", ++ "maintenance_execute", ++ "rebuild_index", ++ "rebuild_session_insights", ++ } ++ ++ with patch("polylogue.mcp.server._get_polylogue") as get_polylogue: ++ server = cast(MCPServerUnderTest, build_server(role="admin")) ++ for declaration in guarded: ++ kwargs = declaration.minimal_invocation() ++ kwargs.pop("confirm", None) ++ if declaration.confirmation_policy == "dry_run_or_explicit_confirm": ++ kwargs["dry_run"] = False ++ payload = json.loads(invoke_surface(server._tool_manager._tools[declaration.name].fn, **kwargs)) ++ assert payload["code"] == "confirmation_required", declaration.name ++ assert payload["is_error"] is True, declaration.name ++ ++ get_polylogue.assert_not_called() ++ ++ ++def test_real_mcp_candidate_write_review_and_readback_preserves_authority( ++ cli_workspace: dict[str, Path], ++) -> None: ++ """Production dependency: FastMCP adapters, facade, and real ``user.db``. ++ ++ Replacing candidate capture with a mock-only path, making write-role tools ++ promote or inject content, bypassing reviewer actor/injection controls, or ++ disconnecting either MCP readback route makes this end-to-end test fail. ++ """ ++ ++ session = SessionBuilder(cli_workspace["db_path"], "mcp-declaration-roundtrip").provider("codex") ++ session.save() ++ session_ref = f"session:{session.native_session_id()}" ++ poly = Polylogue(archive_root=cli_workspace["archive_root"]) ++ ++ with patch("polylogue.mcp.server._get_polylogue", return_value=poly): ++ write_server = cast(MCPServerUnderTest, build_server(role="write")) ++ read_server = cast(MCPServerUnderTest, build_server(role="read")) ++ review_server = cast(MCPServerUnderTest, build_server(role="review")) ++ ++ captured = json.loads( ++ invoke_surface( ++ write_server._tool_manager._tools["capture_assertion_candidate"].fn, ++ body_text="Agent candidate requiring operator judgment", ++ kind="lesson", ++ refs=[session_ref], ++ scope_refs=["repo:polylogue"], ++ author_ref="agent:codex-session:mcp-declaration-roundtrip", ++ ) ++ ) ++ assert captured["status"] == "candidate" ++ assert captured["author_kind"] == "agent" ++ assert captured["context_policy"] == {"inject": False, "promotion_required": True} ++ ++ pending = json.loads( ++ invoke_surface( ++ read_server._tool_manager._tools["list_assertion_candidates"].fn, ++ target_ref=session_ref, ++ limit=10, ++ ) ++ ) ++ assert pending["total"] == 1 ++ assert pending["items"][0]["assertion_id"] == captured["assertion_id"] ++ assert pending["items"][0]["status"] == "candidate" ++ assert pending["items"][0]["context_policy"]["inject"] is False ++ ++ judgment = json.loads( ++ invoke_surface( ++ review_server._tool_manager._tools["judge_assertion_candidate"].fn, ++ candidate_ref=f"assertion:{captured['assertion_id']}", ++ decision="accept", ++ inject=True, ++ actor_ref="user:operator", ++ reason="verified against archived evidence", ++ ) ++ ) ++ assert judgment["applied_count"] == 1 ++ assert judgment["failed_count"] == 0 ++ ++ active = json.loads( ++ invoke_surface( ++ read_server._tool_manager._tools["list_assertion_claims"].fn, ++ target_ref=session_ref, ++ statuses="active", ++ context_inject=True, ++ limit=10, ++ ) ++ ) ++ assert active["total"] == 1 ++ assert active["items"][0]["status"] == "active" ++ assert active["items"][0]["body_text"] == "Agent candidate requiring operator judgment" ++ assert active["items"][0]["context_policy"] == {"inject": True} +diff --git a/tests/unit/mcp/test_per_tool_contracts.py b/tests/unit/mcp/test_per_tool_contracts.py +index 7323932193a4707c88d96289c2beb36a8c924e13..c2bce99713c21be8a5ebf53b94ffa3c332517a2e 100644 +--- a/tests/unit/mcp/test_per_tool_contracts.py ++++ b/tests/unit/mcp/test_per_tool_contracts.py +@@ -1,6 +1,6 @@ + """Per-tool contract coverage for the MCP mutation-tools surface (#1294). + +-Pins three durable contracts for every ``@mcp.tool()`` registered by ++Pins three durable contracts for every declared MCP mutation handler registered by + the modules composed by ``polylogue.mcp.server_mutation_tools``: + + 1. **Argument validation** — each tool has a registered handler, the +@@ -8,16 +8,15 @@ the modules composed by ``polylogue.mcp.server_mutation_tools``: + and at least one input rejection or happy path returns a structured + JSON envelope (no raw tracebacks). + 2. **Authorization** — every mutation tool is absent from a ``read``-role +- server. ``server_tools.register_tools`` gates the whole mutation +- register on ``role_allows(role, "write")``; this test pins that gate +- per tool so a future regression that promoted any tool to +- read-role registration fails loudly. ++ server. Declaration-owned role filtering pins that boundary per tool, ++ so a future regression that promoted any mutation to read-role ++ registration fails loudly. + 3. **Idempotency** — create-twice, delete-missing, update-with-same + payload return the documented ``status``/``outcome`` shape rather + than crashing. + + Tool names are discovered from the admin-role FastMCP server (not a +-hard-coded list) so adding a new ``@mcp.tool()`` without coverage ++hard-coded list) so adding a new declared handler without coverage + fails the discovery test until it is added to ``TOOL_MATRIX``. + """ + +@@ -100,7 +99,7 @@ def _discover_mutation_result_tool_names() -> frozenset[str]: + """Discover mutation tools whose success path emits MutationResultPayload.""" + names: set[str] = set() + for module in _mutation_registration_modules(): +- for chunk in inspect.getsource(module).split(" @mcp.tool()"): ++ for chunk in re.split(r" @(?:mcp|registrar)\.tool\(\)", inspect.getsource(module)): + match = re.search(r"async def ([a-zA-Z_][a-zA-Z0-9_]*)\(", chunk) + if match and "MutationResultPayload(" in chunk: + names.add(match.group(1)) +@@ -178,7 +177,7 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "remove_tag": { + "required": {"session_id", "tag"}, +- "happy": {"session_id": _CONV_ID, "tag": "review"}, ++ "happy": {"session_id": _CONV_ID, "tag": "review", "confirm": True}, + }, + "bulk_tag_sessions": { + "required": {"session_ids", "tags"}, +@@ -199,8 +198,8 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "remove_mark": { + "required": {"session_id", "mark_type"}, +- "happy": {"session_id": _CONV_ID, "mark_type": "star"}, +- "invalid_value": {"session_id": _CONV_ID, "mark_type": "bogus"}, ++ "happy": {"session_id": _CONV_ID, "mark_type": "star", "confirm": True}, ++ "invalid_value": {"session_id": _CONV_ID, "mark_type": "bogus", "confirm": True}, + }, + "list_annotations": { + "required": set(), +@@ -213,7 +212,7 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "delete_annotation": { + "required": {"annotation_id"}, +- "happy": {"annotation_id": "ann-1"}, ++ "happy": {"annotation_id": "ann-1", "confirm": True}, + }, + "list_saved_views": { + "required": set(), +@@ -226,7 +225,7 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "delete_saved_view": { + "required": {"view_id"}, +- "happy": {"view_id": "view-1"}, ++ "happy": {"view_id": "view-1", "confirm": True}, + }, + "list_recall_packs": { + "required": set(), +@@ -239,7 +238,7 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "delete_recall_pack": { + "required": {"pack_id"}, +- "happy": {"pack_id": "pack-1"}, ++ "happy": {"pack_id": "pack-1", "confirm": True}, + }, + "list_workspaces": { + "required": set(), +@@ -252,7 +251,7 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "delete_workspace": { + "required": {"workspace_id"}, +- "happy": {"workspace_id": "ws-1"}, ++ "happy": {"workspace_id": "ws-1", "confirm": True}, + }, + "get_metadata": { + "required": {"session_id"}, +@@ -265,8 +264,8 @@ TOOL_MATRIX: dict[str, dict[str, Any]] = { + }, + "delete_metadata": { + "required": {"session_id", "key"}, +- "happy": {"session_id": _CONV_ID, "key": "note"}, +- "invalid_value": {"session_id": _CONV_ID, "key": ""}, ++ "happy": {"session_id": _CONV_ID, "key": "note", "confirm": True}, ++ "invalid_value": {"session_id": _CONV_ID, "key": "", "confirm": True}, + }, + "delete_session": { + "required": {"session_id"}, +@@ -356,8 +355,9 @@ class TestMutationToolDiscovery: + """ + + def test_personal_state_tools_depend_on_parent_composition_seam(self) -> None: +- """The parent registrar owns the only route into the extracted tools.""" ++ """The parent registrar fails closed when extracted handlers disappear.""" + from polylogue.mcp import server_personal_state_tools ++ from polylogue.mcp.declarations.registration import MCPDeclarationRegistrationError + from polylogue.mcp.server import build_server + + registered = cast(MCPServerUnderTest, build_server(role="admin")) +@@ -371,11 +371,13 @@ class TestMutationToolDiscovery: + } + assert personal_names + +- with patch("polylogue.mcp.server_mutation_tools.register_personal_state_tools") as registrar: +- suppressed = cast(MCPServerUnderTest, build_server(role="admin")) ++ with ( ++ patch("polylogue.mcp.server_mutation_tools.register_personal_state_tools") as registrar, ++ pytest.raises(MCPDeclarationRegistrationError, match="missing_handlers"), ++ ): ++ build_server(role="admin") + + registrar.assert_called_once() +- assert personal_names.isdisjoint(suppressed._tool_manager._tools), sorted(personal_names) + + def test_every_mutation_tool_is_in_matrix(self) -> None: + missing = MUTATION_TOOL_NAMES - set(TOOL_MATRIX.keys()) +@@ -593,7 +595,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "remove_tag", +- {"session_id": _CONV_ID, "tag": "missing"}, ++ {"session_id": _CONV_ID, "tag": "missing", "confirm": True}, + "remove_tag", + TagMutationResult(outcome="not_present", detail="tag_absent"), + TagMutationResult(outcome="not_present", detail="tag_absent"), +@@ -613,7 +615,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "remove_mark", +- {"session_id": _CONV_ID, "mark_type": "star"}, ++ {"session_id": _CONV_ID, "mark_type": "star", "confirm": True}, + "remove_mark", + False, + False, +@@ -637,7 +639,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "delete_annotation", +- {"annotation_id": "missing-ann"}, ++ {"annotation_id": "missing-ann", "confirm": True}, + "delete_annotation", + False, + False, +@@ -647,7 +649,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "delete_saved_view", +- {"view_id": "missing-view"}, ++ {"view_id": "missing-view", "confirm": True}, + "delete_view", + False, + False, +@@ -657,7 +659,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "delete_recall_pack", +- {"pack_id": "missing-pack"}, ++ {"pack_id": "missing-pack", "confirm": True}, + "delete_recall_pack", + False, + False, +@@ -667,7 +669,7 @@ _IDEMPOTENT_CASES = [ + ), + pytest.param( + "delete_workspace", +- {"workspace_id": "missing-ws"}, ++ {"workspace_id": "missing-ws", "confirm": True}, + "delete_workspace", + False, + False, +diff --git a/tests/unit/mcp/test_tag_idempotency.py b/tests/unit/mcp/test_tag_idempotency.py +index 76ad0d977607cafd1ae47a4252782a6e622a3f98..20eb396d115ef92d2b00a61c85b15b5c5f6865c3 100644 +--- a/tests/unit/mcp/test_tag_idempotency.py ++++ b/tests/unit/mcp/test_tag_idempotency.py +@@ -109,6 +109,7 @@ class TestTagIdempotencyOutcomes: + mcp_server._tool_manager._tools[_REMOVE_TAG_TOOL].fn, + session_id=_CONV_ID, + tag=_TAG, ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -128,6 +129,7 @@ class TestTagIdempotencyOutcomes: + mcp_server._tool_manager._tools[_REMOVE_TAG_TOOL].fn, + session_id=_CONV_ID, + tag=_TAG, ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -146,6 +148,7 @@ class TestTagIdempotencyOutcomes: + mcp_server._tool_manager._tools[_REMOVE_TAG_TOOL].fn, + session_id="nonexistent:id", + tag=_TAG, ++ confirm=True, + ) + + parsed = json.loads(result) +diff --git a/tests/unit/mcp/test_tool_contracts.py b/tests/unit/mcp/test_tool_contracts.py +index f3f07e96f2c3a9c6ffb82b463e20ad631a1f7237..4da60ef5c05fe8af663fe43ec26ec2ae0f3b2c36 100644 +--- a/tests/unit/mcp/test_tool_contracts.py ++++ b/tests/unit/mcp/test_tool_contracts.py +@@ -2004,7 +2004,10 @@ class TestMutationTools: + mock_get_polylogue.return_value = mock_poly + + result = invoke_surface( +- mcp_server._tool_manager._tools["remove_tag"].fn, session_id="test:conv-123", tag="important" ++ mcp_server._tool_manager._tools["remove_tag"].fn, ++ session_id="test:conv-123", ++ tag="important", ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -2023,6 +2026,7 @@ class TestMutationTools: + mcp_server._tool_manager._tools["remove_tag"].fn, + session_id="test:conv-123", + tag="important", ++ confirm=True, + ) + + body = json.loads(result) +@@ -2261,6 +2265,7 @@ class TestMutationTools: + mcp_server._tool_manager._tools["delete_metadata"].fn, + session_id="test:conv-123", + key="author", ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -2454,7 +2459,7 @@ class TestMutationTools: + mock_poly.get_index_status = AsyncMock(return_value={"exists": True, "count": 500}) + mock_get_polylogue.return_value = mock_poly + +- result = invoke_surface(mcp_server._tool_manager._tools["rebuild_index"].fn) ++ result = invoke_surface(mcp_server._tool_manager._tools["rebuild_index"].fn, confirm=True) + + parsed = json.loads(result) + assert parsed["status"] == "ok" +@@ -2489,6 +2494,7 @@ class TestMutationTools: + result = invoke_surface( + mcp_server._tool_manager._tools["rebuild_session_insights"].fn, + session_ids=["conv-1", "conv-2"], ++ confirm=True, + ) + + parsed = json.loads(result) +diff --git a/tests/unit/mcp/test_user_state_tools.py b/tests/unit/mcp/test_user_state_tools.py +index c00795fda9e0748c7707627a40a31abacf73f362..de7d398056205dfaa22af82bd8d202fd15da8162 100644 +--- a/tests/unit/mcp/test_user_state_tools.py ++++ b/tests/unit/mcp/test_user_state_tools.py +@@ -93,6 +93,7 @@ def test_remove_mark_reports_missing_mark(mcp_server: MCPServerUnderTest) -> Non + mcp_server._tool_manager._tools["remove_mark"].fn, + session_id="conv-123", + mark_type="star", ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -112,6 +113,7 @@ def test_remove_mark_rejects_unknown_type(mcp_server: MCPServerUnderTest) -> Non + mcp_server._tool_manager._tools["remove_mark"].fn, + session_id="conv-123", + mark_type="later", ++ confirm=True, + ) + + parsed = json.loads(result) +@@ -200,7 +202,11 @@ def test_delete_annotation_reports_status(mcp_server: MCPServerUnderTest) -> Non + mock_poly.delete_annotation = AsyncMock(return_value=False) + mock_get_polylogue.return_value = mock_poly + +- result = invoke_surface(mcp_server._tool_manager._tools["delete_annotation"].fn, annotation_id="ann-1") ++ result = invoke_surface( ++ mcp_server._tool_manager._tools["delete_annotation"].fn, ++ annotation_id="ann-1", ++ confirm=True, ++ ) + + parsed = json.loads(result) + assert parsed["status"] == "not_found" +@@ -291,7 +297,11 @@ def test_delete_saved_view_reports_status(mcp_server: MCPServerUnderTest) -> Non + mock_poly.delete_view = AsyncMock(return_value=True) + mock_get_polylogue.return_value = mock_poly + +- result = invoke_surface(mcp_server._tool_manager._tools["delete_saved_view"].fn, view_id="view-1") ++ result = invoke_surface( ++ mcp_server._tool_manager._tools["delete_saved_view"].fn, ++ view_id="view-1", ++ confirm=True, ++ ) + + parsed = json.loads(result) + assert parsed["status"] == "deleted" +@@ -351,7 +361,11 @@ def test_delete_recall_pack_reports_status(mcp_server: MCPServerUnderTest) -> No + mock_poly.delete_recall_pack = AsyncMock(return_value=False) + mock_get_polylogue.return_value = mock_poly + +- result = invoke_surface(mcp_server._tool_manager._tools["delete_recall_pack"].fn, pack_id="pack-1") ++ result = invoke_surface( ++ mcp_server._tool_manager._tools["delete_recall_pack"].fn, ++ pack_id="pack-1", ++ confirm=True, ++ ) + + parsed = json.loads(result) + assert parsed["status"] == "not_found" +@@ -413,7 +427,11 @@ def test_delete_workspace_reports_status(mcp_server: MCPServerUnderTest) -> None + mock_poly.delete_workspace = AsyncMock(return_value=False) + mock_get_polylogue.return_value = mock_poly + +- result = invoke_surface(mcp_server._tool_manager._tools["delete_workspace"].fn, workspace_id="workspace-1") ++ result = invoke_surface( ++ mcp_server._tool_manager._tools["delete_workspace"].fn, ++ workspace_id="workspace-1", ++ confirm=True, ++ ) + + parsed = json.loads(result) + assert parsed["status"] == "not_found" diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/TESTS.md new file mode 100644 index 000000000..2dc07e8a9 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-04/a01/extracted/TESTS.md @@ -0,0 +1,356 @@ +# Test design and execution ledger + +## Environment + +- Python 3.13.5 +- pytest 9.1.1 +- Ruff 0.15.20 +- MyPy 1.17.1 +- Linux 4.4.0 x86_64 container +- Repository base: `f654480cadb7cc4c194704e24dfd483199547b35` + +The tests use repository fixtures and isolated temporary archives/databases. No operator live daemon, browser, production archive, secret, or deployed receiver was accessed. + +## New production-route tests and anti-vacuity witnesses + +### Declaration-owned role-scoped registration + +Test: `test_declarations_drive_role_scoped_registration` + +Production dependencies exercised: + +- `polylogue.mcp.server_tools.register_tools` +- `polylogue.mcp.declarations.registration.MCPDeclarationRegistrar` +- `polylogue.mcp.server_support.role_allows` +- real FastMCP registration for read, write, review, and admin roles + +The expected subset is independently calculated from declaration roles and compared with the real registered server. Representative changes that make it fail are restoring family-level write/review/admin registration, changing a declaration role, bypassing the registrar for one handler, or registering a privileged tool on the read server. + +### Exact production handler and signature equivalence + +Test: `test_declarations_bind_exact_production_handlers_and_input_contracts` + +Production dependencies exercised: + +- all 41 registered FastMCP handler functions; +- exact `__module__` ownership; +- async function identity; +- concrete signatures, minimal-invocation parameters, and fail-closed defaults. + +Replacing a handler with a wrapper, moving it to a parallel adapter, deleting it, changing a minimal argument name, or dropping a required `confirm: bool = False` / `dry_run: bool = False` default makes the test and startup registrar fail. + +### Trust, injection, and immutable annotation provenance + +Test: `test_registry_preserves_trust_injection_and_annotation_provenance` + +Production dependencies exercised: + +- candidate capture declaration; +- annotation batch import declaration; +- review-only judgment declarations; +- registry fail-closed validation. + +Promoting agent-authored capture/import to active or injectable state, weakening judgment from review to write/read, dropping `author_ref`/`author_kind=agent`, or removing any of `batch_id`, `schema_id`, `schema_version`, `source_result_ref`, `actor_ref`, `model_ref`, or `prompt_ref` makes the test fail. + +### Generated equivalence drift + +Test: `test_generated_equivalence_artifact_matches_executable_registry` + +Production dependencies exercised: + +- deterministic declaration ordering; +- canonical JSON rendering; +- committed `docs/generated/mcp-context-assertion-equivalence.json`. + +Editing a declaration without regenerating the artifact, editing the artifact by hand, changing ordering, or returning non-canonical mutable values makes byte equality fail. + +### No invented protocol alternatives + +Test: `test_declarations_do_not_claim_unimplemented_protocol_routes` + +Production dependencies exercised: + +- current MCP resource registration (none for this family); +- current MCP prompt registration; +- the existing read-only `resume_context` prompt. + +Adding an invented resource URI, naming a prompt that is not actually registered, or removing the real `resume_context` prompt makes the test fail. This prevents the equivalence artifact from presenting future protocol design as landed behavior. + +### Discovery inventory ownership + +Test: `test_expected_tool_inventory_uses_declared_names` + +Production dependencies exercised: + +- `tests.infra.mcp.EXPECTED_TOOL_NAMES`; +- the 41-name declaration registry union. + +Removing the union, restoring a duplicate literal as a competing authority, or omitting one migrated tool makes the expected inventory diverge. + +### Per-handler confirmation before backend access + +Test: `test_declared_confirmation_contracts_fail_closed_before_backend_access` + +Production dependencies exercised: + +- eleven distinct handlers: the ten `polylogue-jn40` tools plus pre-existing `delete_session`; +- FastMCP registered functions; +- each handler's local confirmation check; +- typed `confirmation_required` error envelope. + +The server facade seam is patched only as a tripwire. Every unconfirmed call must return before that seam is touched. Removing any guard, moving it after session resolution/facade access/planner construction, changing a declaration's confirmation class, or routing tools through one generic dispatcher makes this test fail. + +### Real candidate write, review, and readback + +Test: `test_real_mcp_candidate_write_review_and_readback_preserves_authority` + +Production dependencies exercised: + +- real registered write/read/review MCP functions; +- `Polylogue` facade; +- real isolated archive session; +- real durable `user.db` assertion and judgment storage; +- candidate list and active-claim read paths; +- context injection policy. + +Sequence: + +1. `capture_assertion_candidate` stores an agent candidate. +2. `list_assertion_candidates` reads it back as candidate with `inject=false`. +3. `judge_assertion_candidate` accepts it under `actor_ref=user:operator` and requests injection. +4. `list_assertion_claims` reads the resulting active claim with `inject=true`. + +Replacing persistence with a mock-only route, auto-promoting the candidate, letting write role judge, dropping reviewer actor context, disconnecting readback, or failing to apply the accepted injection decision makes the test fail. + +### Maintenance dry-run versus apply authorization + +Test: `test_maintenance_execute_dry_run_does_not_require_confirmation` + +Production dependencies exercised: + +- distinct `maintenance_execute` registered handler; +- real handler argument routing; +- `polylogue.maintenance.planner.execute_backfill` seam; +- shared maintenance envelope. + +The test proves `dry_run=True` reaches the planner with `dry_run=True` and returns an MCP execute envelope without confirmation. Requiring confirmation for safe planning or removing the non-dry confirmation gate violates the paired tests. + +## Existing contract tests updated + +No existing test or helper was deleted. Existing success/idempotency tests now send `confirm=True` only when they intend to exercise the post-authorization branch. Required-argument sets do not mark `confirm` as required because the public safety contract is an optional argument with a fail-closed default. + +`test_personal_state_tools_depend_on_parent_composition_seam` was strengthened: suppressing the extracted personal-state registrar now must make server startup fail with missing declared handlers, rather than quietly producing a reduced server. + +## Final test commands and results + +### All MCP unit tests, partitioned by file + +`pytest --collect-only -q tests/unit/mcp` reported **713 tests in 36 files**. Every file passed in isolated partitions. The newly changed declaration suite was kept in its own process so its result is independently attributable: + +```text +pytest -q \ + tests/unit/mcp/test_agent_coordination.py \ + tests/unit/mcp/test_annotation_import_tool.py \ + tests/unit/mcp/test_assertion_judgment_tools.py \ + tests/unit/mcp/test_blackboard_tools.py \ + tests/unit/mcp/test_candidate_capture_tool.py \ + tests/unit/mcp/test_compose_context_preamble.py \ + tests/unit/mcp/test_context_image.py \ + tests/unit/mcp/test_distilled_bundle_tools.py +``` + +Result: **45 passed in 7.84s**. + +```text +pytest -q tests/unit/mcp/test_context_assertion_declarations.py +``` + +Result: **11 passed in 2.86s**. + +```text +pytest -q \ + tests/unit/mcp/test_mcp_call_log.py \ + tests/unit/mcp/test_mcp_edge_cases.py \ + tests/unit/mcp/test_server_runtime.py \ + tests/unit/mcp/test_tool_error_isolation.py \ + tests/unit/mcp/test_envelope_contracts.py \ + tests/unit/mcp/test_contract_evidence.py +``` + +Result: **102 passed in 18.08s**. + +```text +pytest -q \ + tests/unit/mcp/test_server_surfaces.py \ + tests/unit/mcp/test_tool_discovery.py +``` + +Result: **151 passed in 29.33s**. + +```text +pytest -q -n 8 \ + tests/unit/mcp/test_aggregate_sessions.py \ + tests/unit/mcp/test_analysis_primitives_facade_parity.py \ + tests/unit/mcp/test_annotation_join_tool.py \ + tests/unit/mcp/test_cli.py \ + tests/unit/mcp/test_correlate_session.py \ + tests/unit/mcp/test_cost_outlook_tool.py \ + tests/unit/mcp/test_embedding_retrieval_not_ready.py \ + tests/unit/mcp/test_embedding_status_tool.py \ + tests/unit/mcp/test_facets_tool_contract.py \ + tests/unit/mcp/test_insight_shape_tools.py \ + tests/unit/mcp/test_lineage_completeness_payload.py \ + tests/unit/mcp/test_logical_session_tool.py \ + tests/unit/mcp/test_query_tool_schema_derivation.py \ + tests/unit/mcp/test_session_analysis_primitives.py \ + tests/unit/mcp/test_session_tool_timing.py +``` + +Result: **78 passed in 31.92s**. + +```text +pytest -q -n 4 tests/unit/mcp/test_per_tool_contracts.py +``` + +Result: **199 passed in 23.16s**. + +```text +pytest -q -n 4 tests/unit/mcp/test_tool_contracts.py +``` + +Result: **98 passed in 22.33s**. + +```text +pytest -q -n 4 \ + tests/unit/mcp/test_user_state_tools.py \ + tests/unit/mcp/test_tag_idempotency.py +``` + +Result: **29 passed in 14.91s**. + +The partition totals are exactly **713 passed**, matching collection. This covers every file under `tests/unit/mcp`. + +### Focused maintenance contracts + +```text +pytest -q \ + tests/unit/maintenance/test_envelope_contracts.py \ + tests/unit/maintenance/test_scope_filter.py \ + tests/unit/maintenance/test_scope_filter_envelope_contract.py +``` + +Result: **49 passed in 2.90s**. + +### Independent patch application proof + +A separate detached worktree was reset to the exact base commit, cleaned, and checked: + +```text +git apply --check PATCH.diff +git apply PATCH.diff +git diff --check +``` + +Result: all passed. + +Applied-tree smoke: + +```text +PYTHONPATH= python - <<'PY' +from polylogue.mcp.declarations.registry import MCP_CONTEXT_ASSERTION_DECLARATIONS +from polylogue.mcp.server import build_server +from tests.infra.mcp import EXPECTED_TOOL_NAMES +assert len(MCP_CONTEXT_ASSERTION_DECLARATIONS) == 41 +assert set(build_server(role="admin")._tool_manager._tools) == EXPECTED_TOOL_NAMES +PY +``` + +Result: **41 declarations; 104 exact admin tools**. + +Applied-tree test: + +```text +pytest -q tests/unit/mcp/test_context_assertion_declarations.py +``` + +Result: **11 passed in 3.59s**. + +### Static and generated checks + +Changed-file format and lint: + +```text +ruff format --check <16 changed Python files> +ruff check <16 changed Python files> +``` + +Result: **passed; 16 files formatted; no lint findings**. + +Strict production typing: + +```text +mypy \ + polylogue/mcp/declarations/__init__.py \ + polylogue/mcp/declarations/models.py \ + polylogue/mcp/declarations/registration.py \ + polylogue/mcp/declarations/registry.py \ + polylogue/mcp/server_context_tools.py \ + polylogue/mcp/server_maintenance_tools.py \ + polylogue/mcp/server_mutation_tools.py \ + polylogue/mcp/server_personal_state_tools.py \ + polylogue/mcp/server_tools.py +``` + +Result: **success; no issues in 9 source files**. Repository configuration sets `strict = true`. + +Generated topology: + +```text +python -m devtools render topology-projection +python -m devtools render topology-status +python -m devtools render topology-projection --check +python -m devtools render topology-status --check +``` + +Result: **passed; 1,011 realized and declared Python modules; 9 pre-existing non-blocking topology assignments remain**. + +All generated surfaces: + +```text +python -m devtools render all --check +``` + +Result: **passed** for CLI reference, CLI schemas, OpenAPI, devtools reference, demo corpus datasheet, quality reference, product workflows, docs surface, MCP tool index, topology status, and Pages/local links. + +Focused repository verifiers: + +```text +python -m devtools verify layering +python -m devtools verify topology +python -m devtools verify test-infra-currency +``` + +Results: + +- layering: **no violations**; +- topology: **realized=1011, declared=1011, blocking=False**; +- test-infra currency: **89 schema tables, 8 helper references, 0 unmatched**. + +Patch integrity: + +```text +git diff --check +``` + +Result: **passed**. + +The final patch changes exactly 19 repository paths. It contains no supplied archive/bundle names or `/mnt/data`/snapshot paths. A scan found no placeholder-like added lines and no standalone code-body ellipsis. The only `TBD` text anywhere in the unified diff is unchanged context from the pre-existing generated topology dashboard/assignments; the patch adds no placeholder. + +## Incomplete aggregate commands + +Two aggregate forms did not complete in this container even though every constituent file later passed: + +1. `pytest -q tests/unit/mcp` stalled after partial progress. An xdist aggregate also stalled after partial progress. No assertion failure was emitted. Running all 36 files in isolated partitions produced 713/713 passes. The cause appears to be process-order/global-resource interaction; whether it reproduces on the operator runner is unverified. +2. `PATH="$PWD/.venv/bin:$PATH" python -m devtools verify --quick` passed Ruff format and Ruff lint, then did not complete the repository-wide MyPy phase within the container execution. The changed production modules passed strict MyPy independently, and `render all --check` passed independently. The first attempt without the virtual environment on `PATH` failed immediately because the harness could not locate `ruff`; that invocation is superseded by the corrected one but is recorded for completeness. + +No claim is made that the complete repository test suite, live daemon integration, browser integration, real archive migration, NixOS packaging, or deployed MCP authentication was verified. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/EVIDENCE.md new file mode 100644 index 000000000..d3a3e7a1f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/EVIDENCE.md @@ -0,0 +1,135 @@ +# EVIDENCE — beads-05 semantic transcript rendering + +## Authority order + +1. Mission document `[beads 05] Semantic transcript rendering`. +2. Attached snapshot source at commit `f654480cadb7cc4c194704e24dfd483199547b35`. +3. Current Bead records in `.beads/issues.jsonl`, especially `polylogue-ap7.1` and `polylogue-395j`. +4. Repository instructions and tests. +5. Older plans/history only where they remain consistent with current source. + +## Snapshot findings + +The supplied manifest records: + +- branch `master`; +- commit `f654480cadb7cc4c194704e24dfd483199547b35`; +- generated time `2026-07-17T043202Z`; +- `dirty: true`. + +The same snapshot contains zero-byte branch-delta patch, file-list, and log artifacts. Its human branch-delta report names `f654480cadb7cc4c194704e24dfd483199547b35` as the merge base and contains no diff or commits. This is a contradiction in metadata, not recoverable source evidence. The implementation therefore uses the named commit plus the supplied working-tree archive and does not claim to preserve an unknown tracked dirty patch. + +## Primary Bead findings + +### `polylogue-ap7.1` + +The Bead requires: + +- a generated coverage matrix over normalized tool families and executable Origins; +- file-read/search, web, MCP, write/edit variants, lineage/delegation, attachment, shell, task, and unknown cards; +- structural outcomes, target/path extraction, refs, bounded previews, and explicit missing/unknown behavior; +- one card document consumed by CLI and web; +- bounded database/archive lineage parity without complete-family hydration; +- preservation of the ChatGPT recipient-addressed `TOOL_USE` canary. + +Implementation correspondence: + +- `semantic_card_registry.py` now has exhaustive Origin policy, complete current persisted semantic-family policy, exact aliases, and exact MCP grammar. +- generated JSON/Markdown registry surfaces expose those policies; +- the 20-case fixture corpus covers all ten card kinds and ordered transcript entries; +- CLI and web receive the same card/transcript documents; +- database and archive paths preserve physical source ownership and use the same placement renderer; +- the ChatGPT positive and negative canaries pass. + +The Bead’s strongest remaining gap is proof rather than mechanism: there is no single byte-for-byte database/archive parity test built from one delegation-heavy fixture, and the named live ChatGPT session was not available. + +### `polylogue-395j` + +The Bead explicitly says empty exported thinking is legitimate absence evidence, not a parsing error. It requires one compact marker per contiguous run, provenance/count retention, non-empty thinking folds, and no provider/prose heuristics. + +Implementation correspondence: + +- absence is a first-class typed transcript entry; +- contiguous empty `thinking` and `reasoning` blocks/messages compact once; +- references retain message, block, physical source session, role, message type, and authoredness; +- ordinary prose does not trigger compaction; +- non-empty thinking remains typed and foldable in Markdown and web; +- the mixed fixture freezes empty and non-empty behavior together. + +Manual verification against a real reasoning-model ChatGPT export remains unavailable. + +## Adjacent Bead findings + +- `polylogue-e2yk` is already closed and establishes that recipient-addressed JSON must parse as `TOOL_USE`. This patch preserves and tests that boundary; it does not reimplement parser semantics in the renderer. +- `polylogue-4p1` calls for a future sole Query × Projection × Render read algebra. The current slice reuses existing read/composition seams and does not invent that larger framework. +- `polylogue-37km` requires canonical HTML aesthetics and browser-visible outcome-first tool blocks. This patch provides semantic structure but does not claim that visual program complete. +- `polylogue-1ilk` requires Playwright/component/visual-regression infrastructure. DOM/source tests here are not presented as browser proof. +- `polylogue-1lm` concerns a broader composable projection algebra and remains outside this patch. + +## History findings + +`0f5059068` introduced the original provider-neutral card model, registry, Markdown renderer, CLI integration, generated schema/registry, and 15-case fixture corpus. It established the correct extension point. + +`0c251b600` added the web placement adapter, daemon card payloads, browser-shell card renderer, and visual DOM tests. It established web compatibility but still placed only cards, not one ordered content/card/absence stream. + +Current source therefore won over any stale plan suggesting a new renderer. The patch extends these landed modules and removes no independent test/helper framework. + +## Source findings and production-route consequences + +### Existing split semantics + +At the snapshot, CLI could render semantic cards through `build_semantic_transcript`, while the web reader received per-message card lists and still chose ordinary text/thinking/tool presentation with raw-message heuristics. That split could not express a compact run spanning multiple empty-thinking records or preserve ordered content/cards in one payload. + +Resolution: `semantic-transcript.v1` orders content, absence, and card entries. Per-message placement is derived from that stream; it is not a second classifier. + +### Tool family gaps + +The original registry launched shell/edit and treated several normalized families as model-only or fallback. `polylogue-ap7.1` requires explicit current-family handling. + +Resolution: add file-read, search, web, MCP, and task mappings from persisted semantics/exact aliases, while leaving unsupported names as raw-evidence fallback. Exact MCP parsing rejects malformed lookalikes. + +### Physical ownership loss + +Canonical composed rows already retain their physical `session_id` in `MessageRecord`, but `Message` hydration did not expose it. Archive-envelope rows likewise did not expose physical ownership. This made inherited content appear child-owned in renderer evidence. + +Resolution: add optional `Message.source_session_id`, hydrate from canonical rows, carry through archive rows, and retain separate invocation/result owners. + +### Inherited attachment loss + +Repository full, render, paginated, and batch reads fetched attachments only for the requested logical session. Prefix-sharing inherited messages physically belong to ancestor sessions, so their attachments could disappear. + +Resolution: derive the unique physical owner sessions from the already-composed rows and batch only those attachment lookups. A public-facade test proves full, paginated, and bulk reads all retain the ancestor attachment. + +### Duration and code language loss + +The canonical message SELECT omitted `duration_ms`, and hydration did not promote code language from block metadata. Archive conversion also omitted block language. + +Resolution: select/map/hydrate duration and language and freeze them in typed semantic content/source tests. + +### Lineage completeness asymmetry + +Archive envelopes already carry `lineage_complete` and truncation reason; normal topology objects do not. Claiming normal-route completeness would invent evidence. + +Resolution: archive transcripts emit concrete completeness/truncation state. Normal topology lineage cards explicitly caveat that composition completeness was not supplied. + +### Pagination boundary + +A paginated CLI/read response cannot pair a tool result or compact an absence run outside the current page without an additional read. + +Resolution: keep the renderer pure and mark both pairing and absence-compaction scope as `page`; Markdown displays the warning. + +## Contract reconciliation + +`polylogue-ap7.1` says CLI and web consume `SemanticCard.to_document()`/schema, while `polylogue-395j` requires ordered non-card content and compact absence. Replacing the card schema would break the landed contract. The patch reconciles them by retaining `semantic-card.v1` unchanged as each card document and adding `semantic-transcript.v1` as an ordered envelope. The web still exposes `semantic_cards` for compatibility, but semantic presentation prefers `semantic_entries`. + +The mission also says to remove superseded renderer duplication. The patch removes semantic reclassification from the new typed web path but retains old raw/card-only paths as backward compatibility for payloads that do not yet carry semantic entries. Deleting those fallbacks would be a separate compatibility decision and is not proposed here. + +## Files and artifacts inspected but not copied + +- supplied project-state TAR archives and their manifest/audit/branch-delta artifacts; +- all-ref Git bundle and working-tree archive; +- `.beads/issues.jsonl` and extracted primary/adjacent Bead records; +- prior commit diffs and stats; +- repository source/tests listed in `HANDOFF.md`. + +None of the supplied archives, XML context slices, Git bundle, operator paths, or snapshot packaging files are included in the result ZIP or patch. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/HANDOFF.md new file mode 100644 index 000000000..6ab82f167 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/HANDOFF.md @@ -0,0 +1,274 @@ +# HANDOFF — beads-05 semantic transcript rendering + +## Result + +This package implements the semantic transcript slice requested by `beads-05` against the attached Polylogue snapshot. The patch creates one provider-neutral, ordered transcript model containing typed content, compact typed absence evidence, and `semantic-card.v1` cards. CLI Markdown and daemon/web session reads consume that same model, with presentation-specific rendering only at the leaf. + +The implementation deliberately extends the semantic-card work already present at the snapshot rather than creating another renderer. It closes the main gaps in `polylogue-ap7.1` and `polylogue-395j`: normalized file-read/search/web/MCP coverage, exhaustive Origin policy, ordered content/card/absence output, honest empty-thinking compaction, inherited physical ownership, attachment propagation through composed reads, and equivalent semantic placement for canonical database and archive-envelope routes. + +## Snapshot identity + +- Project: `polylogue` +- Snapshot generated: `2026-07-17T043202Z` +- Snapshot branch: `master` +- Snapshot commit: `f654480cadb7cc4c194704e24dfd483199547b35` +- Commit timestamp: `2026-07-17T03:45:52+02:00` +- Commit subject: `chore(beads): file archive-insight benchmark findings wofr/vhjs/r7p6/1wtm` +- Snapshot manifest dirty flag: `true` +- Recoverable tracked branch delta: none. `polylogue-branch-delta.patch`, `polylogue-branch-delta-files.txt`, and `polylogue-branch-delta-log.txt` are all zero bytes, and the branch-delta report names the same commit as merge base. The patch therefore targets the named commit exactly rather than inventing an unavailable dirty delta. + +The repository was reconstructed from `polylogue-all-refs.bundle` and `polylogue-working-tree.tar.gz` in the supplied archive. No operator worktree, live daemon, secrets, browser session, or NixOS deployment was accessed. + +## Evidence inspected + +Repository instructions and test policy: + +- `CLAUDE.md` +- `CONTRIBUTING.md` +- `TESTING.md` +- `pyproject.toml` + +Primary and adjacent Beads: + +- `polylogue-ap7.1` — complete semantic-card family coverage and bounded lineage parity +- `polylogue-395j` — compact empty-thinking treatment in web and CLI +- `polylogue-e2yk` — ChatGPT recipient-addressed JSON tool-call canary +- `polylogue-4p1` — longer-term Query × Projection × Render authority +- `polylogue-37km`, `polylogue-1ilk`, and `polylogue-1lm` — later canonical visual/browser/projection work + +Relevant history: + +- `0f5059068 feat(rendering): add semantic transcript evidence cards (#2700)` +- `0c251b600 feat(rendering): wire semantic transcript cards into the web reader (#2736)` + +Production paths followed: + +- semantic models, registry, pure renderer, Markdown renderer, and web placement adapter +- CLI `messages` read route +- daemon normal session-detail and archive-envelope session-detail routes +- canonical SQLite message query, row mapper, hydrator, and composed repository reads +- archive-tier envelope composition and archive-to-domain conversion +- attachment storage/hydration and lineage topology projection +- ChatGPT parser recipient-addressed tool-use path +- generated card registry, public JSON schema, frozen fixtures, visual shell tests, and public facade tests + +## Mechanism + +### One ordered semantic stream + +`SemanticTranscript` now serializes as `semantic-transcript.v1` and contains ordered `entry_type` values: + +- `content`: typed `text`, `thinking`, or `code`, retaining message/block identity, physical source session, authoredness, timing, and code language; +- `absence`: a compact `thinking_unavailable` record with every contributing message/block coordinate; +- `card`: the existing `semantic-card.v1` document for tools, attachments, or lineage. + +`semantic_cards` remains as a compatibility projection for older web payload consumers, but new readers prefer `semantic_entries` and do not reclassify raw blocks when those entries are present. + +### Structural semantics only + +Tool classification uses, in order: + +1. persisted normalized `semantic_type`; +2. the exact structural `mcp__server__method` grammar; +3. an explicit provider-family/tool alias registry grounded in parser fixtures or classifier contracts; +4. a generic fallback card retaining raw input/result evidence. + +No provider-name prose heuristic, command-text heuristic, or result-text regex establishes tool family or outcome. Outcome comes only from `tool_result_is_error` and `tool_result_exit_code`. + +### Tool pairing and source ownership + +Tool uses and results pair exactly by `tool_id`, FIFO for duplicate IDs. Invocation and result preserve separate physical source-session IDs, messages, blocks, roles, message types, authoredness, timestamps, and durations. Orphan results remain explicit fallback cards. + +### Empty thinking + +Typed `THINKING`/`REASONING` records with empty or whitespace-only content become one compact absence marker per contiguous run. Every source coordinate remains in the marker. Ordinary prose such as “Thinking unavailable” has no semantic effect. Non-empty thinking remains foldable typed content. + +### Lineage and attachments + +Canonical database hydration and archive-envelope conversion retain the physical `source_session_id` of each composed message. Full, paginated, and bulk canonical reads fetch attachments only from the physical owner sessions represented by the composed message rows, preventing inherited-prefix attachments from disappearing without hydrating an entire family. + +Archive envelopes expose lineage completeness and truncation reason. Normal topology reads do not currently carry composition completeness; the lineage card says that completeness was not supplied rather than asserting completeness. + +### Leaf rendering + +- CLI Markdown renders the shared transcript directly and discloses page-bounded pairing/absence compaction. +- Daemon session detail emits the full transcript and per-message placed entries. +- Web JavaScript dispatches by `entry_type`, rendering code and non-empty thinking as folds, compact absence markers, and cards from the shared document. Raw-block/card-only logic remains solely as backward compatibility for older payloads. + +## Decisions + +1. Preserve absence as evidence rather than silently omitting it. +2. Keep `semantic-card.v1` stable for cards and add `semantic-transcript.v1` as the ordered wrapper needed by `polylogue-395j`. +3. Retain historical `TranscriptProse` as a source-compatible Python class name while serializing it as typed `content`. +4. Keep the provider namespace open. Exhaustive Origin policy means every current Origin has an explicit provider/fallback policy, not that unknown future tools are guessed. +5. Treat pagination as a semantic boundary. Pairing and absence compaction are page-bounded and disclosed instead of looking beyond the requested page. +6. Suppress a result-only message row only when all of its evidence is represented on the invocation card. A result row that also owns content or an attachment remains visible. +7. Fetch ancestor attachments from represented physical owners rather than traversing/hydrating complete families. + +## Changed files + +Patch summary: + +```text + devtools/render_semantic_card_fixtures.py | 49 ++ + devtools/render_semantic_card_registry.py | 51 +- + docs/generated/semantic-card-tool-map.json | Bin 12592 -> 16991 bytes + docs/generated/semantic-card-tool-map.md | Bin 5902 -> 7048 bytes + docs/schemas/semantic-card-v1.schema.json | 231 +++++-- + polylogue/api/archive.py | 2 + + polylogue/archive/message/models.py | 2 + + polylogue/cli/messages.py | 1 + + polylogue/daemon/http.py | 52 +- + polylogue/daemon/web_shell_reader.py | 17 +- + polylogue/daemon/web_shell_semantic_cards.py | 81 ++- + polylogue/rendering/semantic_card_models.py | 154 ++++- + polylogue/rendering/semantic_card_placement.py | 100 +-- + polylogue/rendering/semantic_card_registry.py | 197 +++--- + polylogue/rendering/semantic_cards.py | 750 +++++++++++++++++---- + polylogue/rendering/semantic_markdown.py | 78 ++- + polylogue/storage/hydrators.py | 3 + + polylogue/storage/repository/archive/sessions.py | 69 +- + polylogue/storage/sqlite/archive_tiers/write.py | 2 + + .../storage/sqlite/queries/mappers_archive.py | 1 + + .../storage/sqlite/queries/message_query_reads.py | 3 +- + .../data/semantic_cards/cases/attachment-card.json | 46 +- + .../semantic_cards/cases/chatgpt-canmore-edit.json | 95 ++- + .../semantic_cards/cases/chatgpt-web-fallback.json | 127 ---- + tests/data/semantic_cards/cases/chatgpt-web.json | 195 ++++++ + .../cases/claude-bash-explicit-success.json | 84 ++- + .../semantic_cards/cases/claude-edit-diff.json | 95 ++- + .../semantic_cards/cases/claude-file-read.json | 214 ++++++ + .../cases/claude-interleaved-subagents.json | 178 ++++- + tests/data/semantic_cards/cases/claude-search.json | 199 ++++++ + .../cases/codex-apply-patch-missing-result.json | 62 ++ + .../cases/codex-conflicting-outcome.json | 86 ++- + .../codex-exec-failure-ten-thousand-lines.json | 86 ++- + tests/data/semantic_cards/cases/codex-mcp.json | 216 ++++++ + .../cases/gemini-shell-null-outcome.json | 84 ++- + .../semantic_cards/cases/gemini-write-file.json | 94 ++- + .../cases/hermes-shell-non-utf8.json | 86 ++- + .../cases/hermes-truncated-unknown-input.json | 56 ++ + .../cases/lineage-unknown-boundary.json | 56 +- + .../semantic_cards/cases/message-attachment.json | 139 ++++ + .../cases/mixed-content-thinking-code.json | 279 ++++++++ + tests/data/semantic_cards/cases/orphan-result.json | 61 +- + tests/unit/api/test_facade_contracts.py | 94 ++- + tests/unit/core/test_models.py | 32 + + tests/unit/daemon/test_web_reader.py | 13 + + tests/unit/rendering/test_semantic_cards.py | 101 ++- + tests/unit/rendering/test_semantic_transcript.py | 243 +++++++ + tests/unit/storage/test_lineage_normalization.py | 6 + + tests/visual/test_reader_semantic_cards.py | 35 +- + 49 files changed, 4384 insertions(+), 521 deletions(-) +``` + +Path inventory (`M` modified, `D` deleted, `A`/new paths appear as additions when applied): + +- `M devtools/render_semantic_card_fixtures.py` +- `M devtools/render_semantic_card_registry.py` +- `M docs/generated/semantic-card-tool-map.json` +- `M docs/generated/semantic-card-tool-map.md` +- `M docs/schemas/semantic-card-v1.schema.json` +- `M polylogue/api/archive.py` +- `M polylogue/archive/message/models.py` +- `M polylogue/cli/messages.py` +- `M polylogue/daemon/http.py` +- `M polylogue/daemon/web_shell_reader.py` +- `M polylogue/daemon/web_shell_semantic_cards.py` +- `M polylogue/rendering/semantic_card_models.py` +- `M polylogue/rendering/semantic_card_placement.py` +- `M polylogue/rendering/semantic_card_registry.py` +- `M polylogue/rendering/semantic_cards.py` +- `M polylogue/rendering/semantic_markdown.py` +- `M polylogue/storage/hydrators.py` +- `M polylogue/storage/repository/archive/sessions.py` +- `M polylogue/storage/sqlite/archive_tiers/write.py` +- `M polylogue/storage/sqlite/queries/mappers_archive.py` +- `M polylogue/storage/sqlite/queries/message_query_reads.py` +- `M tests/data/semantic_cards/cases/attachment-card.json` +- `M tests/data/semantic_cards/cases/chatgpt-canmore-edit.json` +- `D tests/data/semantic_cards/cases/chatgpt-web-fallback.json` +- `A tests/data/semantic_cards/cases/chatgpt-web.json` +- `M tests/data/semantic_cards/cases/claude-bash-explicit-success.json` +- `M tests/data/semantic_cards/cases/claude-edit-diff.json` +- `A tests/data/semantic_cards/cases/claude-file-read.json` +- `M tests/data/semantic_cards/cases/claude-interleaved-subagents.json` +- `A tests/data/semantic_cards/cases/claude-search.json` +- `M tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json` +- `M tests/data/semantic_cards/cases/codex-conflicting-outcome.json` +- `M tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json` +- `A tests/data/semantic_cards/cases/codex-mcp.json` +- `M tests/data/semantic_cards/cases/gemini-shell-null-outcome.json` +- `M tests/data/semantic_cards/cases/gemini-write-file.json` +- `M tests/data/semantic_cards/cases/hermes-shell-non-utf8.json` +- `M tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json` +- `M tests/data/semantic_cards/cases/lineage-unknown-boundary.json` +- `A tests/data/semantic_cards/cases/message-attachment.json` +- `A tests/data/semantic_cards/cases/mixed-content-thinking-code.json` +- `M tests/data/semantic_cards/cases/orphan-result.json` +- `M tests/unit/api/test_facade_contracts.py` +- `M tests/unit/core/test_models.py` +- `M tests/unit/daemon/test_web_reader.py` +- `M tests/unit/rendering/test_semantic_cards.py` +- `A tests/unit/rendering/test_semantic_transcript.py` +- `M tests/unit/storage/test_lineage_normalization.py` +- `M tests/visual/test_reader_semantic_cards.py` + +`chatgpt-web-fallback.json` is superseded by `chatgpt-web.json` because the landed ChatGPT parser canary and the completed registry now classify the exact recipient-addressed `web` invocation structurally as a web card rather than fallback. + +## Acceptance matrix + +| Requirement | Result | Evidence | +|---|---|---| +| Every normalized semantic family and executable Origin covered or explicitly fallback | Met for the current enums/registry | Generated Origin and semantic-type policies; completeness tests fail when an enum mapping is removed. | +| Shell, edit/write, file read/search, task/delegation, web, MCP, attachment, lineage, and unknown fixtures | Met in automated scope | 20 frozen cases validate complete ordered entries; card documents validate against the public schema. | +| CLI and web share structure, with no backend classifier fork | Met | Both consume `SemanticTranscript`/`SemanticCard.to_document()`; web route and DOM contract tests assert typed dispatch. Legacy raw heuristics are compatibility-only when typed entries are absent. | +| Database and archive readers emit bounded lineage/delegation semantics without family hydration | Mostly met | Both routes use the same placement renderer; archive route, lineage ownership, hydration, and composed attachment tests pass. No single byte-for-byte DB-versus-archive delegation-heavy parity fixture was added. | +| ChatGPT recipient-addressed JSON remains `TOOL_USE`, without raw JSON leak | Met for synthetic canary | Positive JSON and negative non-JSON parser canaries pass. The operator’s named live export was not available and remains unverified. | +| Empty typed thinking is compact in CLI and web; non-empty thinking remains foldable | Met in automated scope | Mixed frozen fixture, renderer tests, Markdown assertions, and typed web dispatch pass. Manual browser verification against a real ChatGPT export remains unverified. | + +## Apply order + +From a clean checkout at `f654480cadb7cc4c194704e24dfd483199547b35`: + +```bash +git apply --check PATCH.diff +git apply PATCH.diff +python devtools/render_semantic_card_fixtures.py --check +python devtools/render_semantic_card_registry.py --check +pytest -q tests/unit/rendering/test_semantic_cards.py tests/unit/rendering/test_semantic_transcript.py +``` + +The patch contains generated registry documents and frozen expected fixture output, so there is no separate generation step required before application. The `--check` commands certify that committed generated output matches its source declarations. + +## Verification performed + +- Current patch applied with `git apply --check` and `git apply` to a detached clean worktree at `f654480cadb7cc4c194704e24dfd483199547b35`. +- `git diff --check` passed in both implementation and apply-check worktrees. +- Apply-check worktree regenerated and verified 20 frozen cases and two registry surfaces. +- Apply-check worktree renderer suite: 84 passed. +- Consolidated implementation regression command: 317 passed across renderer, transcript, visual, core hydration, message-query, lineage, archive-tier, public-facade attachment, daemon session/workspace, CLI, and ChatGPT canary tests. +- Post-change route/docstring checks: 3 passed. +- Ruff: all changed Python files passed. +- Mypy: no issues in 18 changed production/dev modules. +- Combined semantic-card and reader JavaScript: `node --check` passed. +- Patch contains 49 paths and is 268,567 bytes before ZIP compression. + +Detailed commands, anti-vacuity mutations, and the non-aggregate interpretation of test counts are in `TESTS.md`. + +## Risks and limitations + +- The complete repository test suite was not run. The focused scope is broad, but unrelated integration failures remain possible. +- A prior attempt to run all of `tests/unit/daemon/test_web_reader.py` hit a 300-second execution limit after progressing without a reported failure. The relevant session and workspace classes pass independently. +- The default project environment initially lacked the declared test extras, including Hypothesis. Verification used the repository’s dev/test dependency group; this environment event is recorded in `TESTS.md`. +- No Playwright or real-browser interaction was run. DOM/source contracts and JavaScript syntax pass, but visual weight, focus, keyboard behavior, and responsive presentation are unverified. +- No live ChatGPT export, live daemon, operator archive, credentials, or deployment was accessed. +- Normal topology projection does not expose lineage-composition completeness. The renderer explicitly marks that state unavailable; adding it would require a topology contract extension. +- Pairing and compaction cannot cross a paginated response boundary. The transcript exposes `pairing_scope=page` and `absence_compaction_scope=page` so consumers cannot mistake a page for a complete session. +- Exact MCP grammar intentionally sends malformed or provider-variant lookalikes to fallback. Future normalized aliases must be added with evidence rather than inferred. + +## Value of another iteration + +A small repair pass could add roughly 5–10% confidence by running the full suite, adding one byte-for-byte database/archive delegation parity fixture, and performing Playwright plus a sanitized real-export check. Those are verification additions, not a redesign. + +A substantial second pass would be a different scope: moving all read surfaces under the broader `polylogue-4p1` Query × Projection × Render algebra and adopting the canonical HTML/Playwright work in `polylogue-37km` and `polylogue-1ilk`. That could add materially more architectural and visual value, but it should not be folded into this patch as incidental cleanup. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/PATCH.diff new file mode 100644 index 000000000..2a65ac60a --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/PATCH.diff @@ -0,0 +1,7126 @@ +diff --git a/devtools/render_semantic_card_fixtures.py b/devtools/render_semantic_card_fixtures.py +index 68391372759e0d51d7a3fc1f6f391a4de7feaa3e..e12799baddb5b5f149804b1e7728d117ac6d086a 100644 +--- a/devtools/render_semantic_card_fixtures.py ++++ b/devtools/render_semantic_card_fixtures.py +@@ -76,6 +76,13 @@ def render_case(case: Mapping[str, object], *, case_path: Path) -> list[JSONDocu + return card_json_documents(build_case_transcript(case, case_path=case_path).cards) + + ++def render_entries(case: Mapping[str, object], *, case_path: Path) -> list[JSONDocument]: ++ """Render the ordered semantic stream frozen by the same fixture.""" ++ ++ transcript = build_case_transcript(case, case_path=case_path) ++ return [entry.to_document() for entry in transcript.entries] ++ ++ + def _assert_compact_contract(case: Mapping[str, object], actual: list[JSONDocument], *, case_path: Path) -> list[str]: + contract = case.get("expected_contract") + if not isinstance(contract, Mapping): +@@ -98,6 +105,42 @@ def _assert_compact_contract(case: Mapping[str, object], actual: list[JSONDocume + return errors + + ++def _assert_entry_contract( ++ case: Mapping[str, object], ++ entries: list[JSONDocument], ++ *, ++ case_path: Path, ++) -> list[str]: ++ contract = case.get("expected_contract") ++ if not isinstance(contract, Mapping): ++ return [] ++ errors: list[str] = [] ++ if "entry_types" in contract: ++ actual_types = [entry.get("entry_type") for entry in entries] ++ expected_types = contract.get("entry_types") ++ if expected_types != actual_types: ++ errors.append(f"{case_path}: entry_types expected {expected_types!r}, got {actual_types!r}") ++ if "content_kinds" in contract: ++ content_kinds = [ ++ content.get("content_kind") ++ for entry in entries ++ if isinstance((content := entry.get("content")), dict) ++ ] ++ expected_kinds = contract.get("content_kinds") ++ if expected_kinds != content_kinds: ++ errors.append(f"{case_path}: content_kinds expected {expected_kinds!r}, got {content_kinds!r}") ++ if "absence_counts" in contract: ++ absence_counts = [ ++ absence.get("count") ++ for entry in entries ++ if isinstance((absence := entry.get("absence")), dict) ++ ] ++ expected_counts = contract.get("absence_counts") ++ if expected_counts != absence_counts: ++ errors.append(f"{case_path}: absence_counts expected {expected_counts!r}, got {absence_counts!r}") ++ return errors ++ ++ + def _case_files(root: Path, explicit: Sequence[str]) -> list[Path]: + if explicit: + return [Path(item) for item in explicit] +@@ -130,14 +173,20 @@ def main(argv: list[str] | None = None) -> int: + try: + case = _load_case(path) + actual = render_case(case, case_path=path) ++ entries = render_entries(case, case_path=path) + failures.extend(_assert_compact_contract(case, actual, case_path=path)) ++ failures.extend(_assert_entry_contract(case, entries, case_path=path)) + if args.write: + case["expected_cards"] = actual ++ case["expected_entries"] = entries + path.write_text(json.dumps(case, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + else: + expected = case.get("expected_cards") + if expected != actual: + failures.append(f"{path}: full expected_cards contract differs from renderer output") ++ expected_entries = case.get("expected_entries") ++ if expected_entries != entries: ++ failures.append(f"{path}: full expected_entries contract differs from renderer output") + except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc: + failures.append(f"{path}: {exc}") + +diff --git a/devtools/render_semantic_card_registry.py b/devtools/render_semantic_card_registry.py +index 923d0bfcd46d1b1a3ce221f91a6d3dea2e66a665..34b8914e1ac2bb3b549d9291ca7e9ead9227532a 100644 +--- a/devtools/render_semantic_card_registry.py ++++ b/devtools/render_semantic_card_registry.py +@@ -11,6 +11,8 @@ from pathlib import Path + from polylogue.rendering.semantic_card_models import CARD_SCHEMA_VERSION, SemanticCardKind + from polylogue.rendering.semantic_card_registry import ( + ToolMapping, ++ mcp_pattern_document, ++ origin_policy_documents, + provider_namespace_documents, + semantic_type_policy_documents, + tool_mapping_rows, +@@ -29,7 +31,12 @@ def build_json_document() -> dict[str, object]: + "schema_version": MAP_SCHEMA_VERSION, + "card_schema_version": CARD_SCHEMA_VERSION, + "classification_policy": { +- "precedence": ["persisted_semantic_type", "exact_provider_tool_alias", "fallback"], ++ "precedence": [ ++ "persisted_semantic_type", ++ "exact_mcp_tool_name", ++ "exact_provider_tool_alias", ++ "fallback", ++ ], + "unknown_tool": "fallback_raw_evidence", + "outcome_policy": "structural_fields_only", + "null_outcome": "unknown", +@@ -38,6 +45,8 @@ def build_json_document() -> dict[str, object]: + "card_kinds": [item.value for item in SemanticCardKind], + "status_counts": dict(sorted(counts.items())), + "provider_namespace_policies": provider_namespace_documents(), ++ "origin_policies": origin_policy_documents(), ++ "mcp_pattern_policy": mcp_pattern_document(), + "semantic_type_policies": semantic_type_policy_documents(), + "provider_semantic_coverage": [ + { +@@ -61,13 +70,13 @@ def build_markdown() -> str: + "# Semantic card tool map", + "", + "This is the review surface for the provider-neutral `semantic-card.v1` registry. ", +- "Persisted semantic type wins, then a repository-grounded exact provider/tool alias. ", +- "Provider namespaces are open: an unlisted tool always becomes a raw fallback card, ", ++ "Persisted semantic type wins, then the exact `mcp__server__method` grammar, then a ", ++ "repository-grounded exact provider/tool alias. Provider namespaces are open: an ", ++ "unlisted tool always becomes a raw fallback card, ", + "and names or prose are never fuzzily classified.", + "", +- "`launch` means the CLI launch slice has a specialized card. `model_only` means the ", +- "shared card model exists but public-surface completion is a follow-on. `fallback` ", +- "is intentional raw evidence, not a missing hidden heuristic.", ++ "`launch` means the shared CLI/web renderer has a specialized card. `fallback` is ", ++ "intentional raw evidence, not a missing hidden heuristic.", + "", + ] + lines.extend( +@@ -84,7 +93,32 @@ def build_markdown() -> str: + ) + for policy in semantic_type_policy_documents(): + lines.append(f"| `{policy['semantic_type']}` | `{policy['card_kind']}` | `{policy['rendering_status']}` |") +- lines.extend(["", "## Provider exact-alias census", ""]) ++ lines.extend( ++ [ ++ "", ++ "## Origin coverage", ++ "", ++ "| Origin | Provider family | Exact aliases | Fallback reason |", ++ "|---|---|---|---|", ++ ] ++ ) ++ for policy in origin_policy_documents(): ++ lines.append( ++ f"| `{policy['origin']}` | `{policy['provider_family']}` | " ++ f"`{str(policy['exact_alias_registry']).lower()}` | " ++ f"{policy['fallback_reason'] or '—'} |" ++ ) ++ lines.extend( ++ [ ++ "", ++ "## MCP structural grammar", ++ "", ++ "Only an exact `mcp__server__method` name becomes an MCP card. Malformed lookalikes remain fallback evidence.", ++ "", ++ "## Provider exact-alias census", ++ "", ++ ] ++ ) + + for provider in sorted(grouped): + lines.extend( +@@ -108,8 +142,9 @@ def build_markdown() -> str: + "- Every provider namespace is treated as open; the finite table is evidence-backed rather than aspirational.", + "- A provider can emit tool names not listed here. Those cards preserve exact raw input and result evidence.", + "- A persisted `semantic_type` can safely specialize a provider-private name because classification already happened upstream.", ++ "- Exact `mcp__server__method` structure is recognized independently of provider; malformed lookalikes are fallback cards.", + "- `NULL` structural result fields remain `unknown`; a success-like sentence is not a success signal.", +- "- Web visual wiring, task deep links, richer attachment actions, and streaming cross-page pairing are bounded follow-ons.", ++ "- Cross-page pairing and absence compaction are explicitly page-bounded when a CLI read is paginated.", + "", + ] + ) +diff --git a/docs/generated/semantic-card-tool-map.json b/docs/generated/semantic-card-tool-map.json +index b00a7ebad44074f1bfa7a3231bc03f6e48866245..ea5ae379825b14b9c25effa12ef439814b753ff3 100644 +--- a/docs/generated/semantic-card-tool-map.json ++++ b/docs/generated/semantic-card-tool-map.json +@@ -4,6 +4,7 @@ + "classification_policy": { + "precedence": [ + "persisted_semantic_type", ++ "exact_mcp_tool_name", + "exact_provider_tool_alias", + "fallback" + ], +@@ -20,49 +21,167 @@ + ], + "card_kinds": [ + "shell", ++ "file_read", + "file_edit", ++ "search", ++ "web", ++ "mcp", + "task", + "lineage", + "attachment", + "fallback" + ], + "status_counts": { +- "fallback": 14, +- "launch": 12, +- "model_only": 2 ++ "fallback": 1, ++ "launch": 27 + }, + "provider_namespace_policies": [ ++ { ++ "provider_family": "antigravity", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ }, ++ { ++ "provider_family": "beads", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ }, + { + "provider_family": "chatgpt", + "namespace": "open", + "grounded_exact_aliases": 3, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ }, ++ { ++ "provider_family": "claude-ai", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" + }, + { + "provider_family": "claude-code", + "namespace": "open", + "grounded_exact_aliases": 14, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" + }, + { + "provider_family": "codex", + "namespace": "open", + "grounded_exact_aliases": 8, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" + }, + { + "provider_family": "gemini-cli", + "namespace": "open", + "grounded_exact_aliases": 1, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ }, ++ { ++ "provider_family": "grok", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" + }, + { + "provider_family": "hermes", + "namespace": "open", + "grounded_exact_aliases": 2, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ }, ++ { ++ "provider_family": "unknown", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ } ++ ], ++ "origin_policies": [ ++ { ++ "origin": "claude-code-session", ++ "provider_family": "claude-code", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "codex-session", ++ "provider_family": "codex", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "gemini-cli-session", ++ "provider_family": "gemini-cli", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "hermes-session", ++ "provider_family": "hermes", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "antigravity-session", ++ "provider_family": "antigravity", ++ "exact_alias_registry": false, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": "origin has no grounded exact tool aliases in the committed parser evidence corpus" ++ }, ++ { ++ "origin": "beads-issue", ++ "provider_family": "beads", ++ "exact_alias_registry": false, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": "origin has no grounded exact tool aliases in the committed parser evidence corpus" ++ }, ++ { ++ "origin": "grok-export", ++ "provider_family": "grok", ++ "exact_alias_registry": false, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": "origin has no grounded exact tool aliases in the committed parser evidence corpus" ++ }, ++ { ++ "origin": "chatgpt-export", ++ "provider_family": "chatgpt", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "claude-ai-export", ++ "provider_family": "claude-ai", ++ "exact_alias_registry": false, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": "origin has no grounded exact tool aliases in the committed parser evidence corpus" ++ }, ++ { ++ "origin": "aistudio-drive", ++ "provider_family": "gemini-cli", ++ "exact_alias_registry": true, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": null ++ }, ++ { ++ "origin": "unknown-export", ++ "provider_family": "unknown", ++ "exact_alias_registry": false, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": "origin has no grounded exact tool aliases in the committed parser evidence corpus" + } + ], ++ "mcp_pattern_policy": { ++ "grammar": "mcp__server__method", ++ "card_kind": "mcp", ++ "classification_basis": "exact_structural_tool_name", ++ "malformed_behavior": "fallback" ++ }, + "semantic_type_policies": [ + { + "semantic_type": "other", +@@ -72,8 +191,8 @@ + }, + { + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "file_read", ++ "rendering_status": "launch", + "classification_basis": "persisted_semantic_type" + }, + { +@@ -102,26 +221,26 @@ + }, + { + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "classification_basis": "persisted_semantic_type" + }, + { + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "classification_basis": "persisted_semantic_type" + }, + { + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "launch", + "classification_basis": "persisted_semantic_type" + }, + { + "semantic_type": "subagent", + "card_kind": "task", +- "rendering_status": "model_only", ++ "rendering_status": "launch", + "classification_basis": "persisted_semantic_type" + }, + { +@@ -223,7 +342,7 @@ + "tool_name": "Task", + "semantic_type": "subagent", + "card_kind": "task", +- "rendering_status": "model_only", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -231,8 +350,8 @@ + "provider_family": "claude-code", + "tool_name": "Read", + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "file_read", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -240,8 +359,8 @@ + "provider_family": "claude-code", + "tool_name": "Grep", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -249,8 +368,8 @@ + "provider_family": "claude-code", + "tool_name": "Glob", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -267,8 +386,8 @@ + "provider_family": "claude-code", + "tool_name": "WebFetch", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -276,8 +395,8 @@ + "provider_family": "claude-code", + "tool_name": "WebSearch", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -285,8 +404,8 @@ + "provider_family": "claude-code", + "tool_name": "AskUserQuestion", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -294,8 +413,8 @@ + "provider_family": "claude-code", + "tool_name": "EnterPlanMode", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -303,8 +422,8 @@ + "provider_family": "claude-code", + "tool_name": "ExitPlanMode", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -339,8 +458,8 @@ + "provider_family": "codex", + "tool_name": "search", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_codex.py" + }, +@@ -357,8 +476,8 @@ + "provider_family": "codex", + "tool_name": "tool_search_call", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "parser_record_type", + "evidence": "polylogue/sources/parsers/codex.py" + }, +@@ -366,8 +485,8 @@ + "provider_family": "codex", + "tool_name": "web_search_call", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "parser_record_type", + "evidence": "polylogue/sources/parsers/codex.py" + }, +@@ -376,7 +495,7 @@ + "tool_name": "spawn_agent", + "semantic_type": "subagent", + "card_kind": "task", +- "rendering_status": "model_only", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -384,8 +503,8 @@ + "provider_family": "gemini-cli", + "tool_name": "read_file", + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "file_read", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_local_agent.py" + }, +@@ -420,8 +539,8 @@ + "provider_family": "chatgpt", + "tool_name": "web", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_chatgpt.py" + }, +diff --git a/docs/generated/semantic-card-tool-map.md b/docs/generated/semantic-card-tool-map.md +index 8075373970a065406e345553791d79dae4655dab..19f431c673766db0e8dbfb283cbc1cfb6a6b3a31 100644 +--- a/docs/generated/semantic-card-tool-map.md ++++ b/docs/generated/semantic-card-tool-map.md +@@ -3,13 +3,13 @@ + # Semantic card tool map + + This is the review surface for the provider-neutral `semantic-card.v1` registry. +-Persisted semantic type wins, then a repository-grounded exact provider/tool alias. +-Provider namespaces are open: an unlisted tool always becomes a raw fallback card, ++Persisted semantic type wins, then the exact `mcp__server__method` grammar, then a ++repository-grounded exact provider/tool alias. Provider namespaces are open: an ++unlisted tool always becomes a raw fallback card, + and names or prose are never fuzzily classified. + +-`launch` means the CLI launch slice has a specialized card. `model_only` means the +-shared card model exists but public-surface completion is a follow-on. `fallback` +-is intentional raw evidence, not a missing hidden heuristic. ++`launch` means the shared CLI/web renderer has a specialized card. `fallback` is ++intentional raw evidence, not a missing hidden heuristic. + + ## Persisted semantic-family policy + +@@ -20,17 +20,37 @@ that cannot be enumerated in the exact-alias tables. + | Persisted semantic type | Card kind | Status | + |---|---|---| + | `other` | `fallback` | `fallback` | +-| `file_read` | `fallback` | `fallback` | ++| `file_read` | `file_read` | `launch` | + | `file_write` | `file_edit` | `launch` | + | `file_edit` | `file_edit` | `launch` | + | `shell` | `shell` | `launch` | + | `git` | `shell` | `launch` | +-| `search` | `fallback` | `fallback` | +-| `web` | `fallback` | `fallback` | +-| `agent` | `fallback` | `fallback` | +-| `subagent` | `task` | `model_only` | ++| `search` | `search` | `launch` | ++| `web` | `web` | `launch` | ++| `agent` | `task` | `launch` | ++| `subagent` | `task` | `launch` | + | `thinking` | `fallback` | `fallback` | + ++## Origin coverage ++ ++| Origin | Provider family | Exact aliases | Fallback reason | ++|---|---|---|---| ++| `claude-code-session` | `claude-code` | `true` | — | ++| `codex-session` | `codex` | `true` | — | ++| `gemini-cli-session` | `gemini-cli` | `true` | — | ++| `hermes-session` | `hermes` | `true` | — | ++| `antigravity-session` | `antigravity` | `false` | origin has no grounded exact tool aliases in the committed parser evidence corpus | ++| `beads-issue` | `beads` | `false` | origin has no grounded exact tool aliases in the committed parser evidence corpus | ++| `grok-export` | `grok` | `false` | origin has no grounded exact tool aliases in the committed parser evidence corpus | ++| `chatgpt-export` | `chatgpt` | `true` | — | ++| `claude-ai-export` | `claude-ai` | `false` | origin has no grounded exact tool aliases in the committed parser evidence corpus | ++| `aistudio-drive` | `gemini-cli` | `true` | — | ++| `unknown-export` | `unknown` | `false` | origin has no grounded exact tool aliases in the committed parser evidence corpus | ++ ++## MCP structural grammar ++ ++Only an exact `mcp__server__method` name becomes an MCP card. Malformed lookalikes remain fallback evidence. ++ + ## Provider exact-alias census + + ## chatgpt +@@ -39,25 +59,25 @@ that cannot be enumerated in the exact-alias tables. + |---|---|---|---|---|---| + | `canmore.update_textdoc` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | + | `dalle.text2im` | `other` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | +-| `web` | `web` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | ++| `web` | `web` | `web` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | + + ## claude-code + + | Exact tool name | Semantic type | Card kind | Status | Basis | Evidence | + |---|---|---|---|---|---| +-| `AskUserQuestion` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `AskUserQuestion` | `agent` | `task` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `Bash` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + | `Edit` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `EnterPlanMode` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `ExitPlanMode` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `Glob` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `Grep` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `EnterPlanMode` | `agent` | `task` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `ExitPlanMode` | `agent` | `task` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `Glob` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `Grep` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + | `MultiEdit` | `file_edit` | `file_edit` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `NotebookEdit` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `Read` | `file_read` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `Task` | `subagent` | `task` | `model_only` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `WebFetch` | `web` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `WebSearch` | `web` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `Read` | `file_read` | `file_read` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `Task` | `subagent` | `task` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `WebFetch` | `web` | `web` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `WebSearch` | `web` | `web` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `Write` | `file_write` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + + ## codex +@@ -67,17 +87,17 @@ that cannot be enumerated in the exact-alias tables. + | `apply_patch` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | + | `exec_command` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_codex_event_stream_contract.py` | + | `local_shell_call` | `shell` | `shell` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | +-| `search` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | ++| `search` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | + | `shell` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | +-| `spawn_agent` | `subagent` | `task` | `model_only` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `tool_search_call` | `search` | `fallback` | `fallback` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | +-| `web_search_call` | `web` | `fallback` | `fallback` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | ++| `spawn_agent` | `subagent` | `task` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `tool_search_call` | `search` | `search` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | ++| `web_search_call` | `web` | `web` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | + + ## gemini-cli + + | Exact tool name | Semantic type | Card kind | Status | Basis | Evidence | + |---|---|---|---|---|---| +-| `read_file` | `file_read` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_local_agent.py` | ++| `read_file` | `file_read` | `file_read` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_local_agent.py` | + + ## hermes + +@@ -91,5 +111,6 @@ that cannot be enumerated in the exact-alias tables. + - Every provider namespace is treated as open; the finite table is evidence-backed rather than aspirational. + - A provider can emit tool names not listed here. Those cards preserve exact raw input and result evidence. + - A persisted `semantic_type` can safely specialize a provider-private name because classification already happened upstream. ++- Exact `mcp__server__method` structure is recognized independently of provider; malformed lookalikes are fallback cards. + - `NULL` structural result fields remain `unknown`; a success-like sentence is not a success signal. +-- Web visual wiring, task deep links, richer attachment actions, and streaming cross-page pairing are bounded follow-ons. ++- Cross-page pairing and absence compaction are explicitly page-bounded when a CLI read is paginated. +diff --git a/docs/schemas/semantic-card-v1.schema.json b/docs/schemas/semantic-card-v1.schema.json +index c02d3f634a9a5e36181ed7781bbf03f7787417f8..de360e18d55f3af495d7ba36640efaa4a1e1efbb 100644 +--- a/docs/schemas/semantic-card-v1.schema.json ++++ b/docs/schemas/semantic-card-v1.schema.json +@@ -3,84 +3,239 @@ + "$id": "https://polylogue.local/schemas/semantic-card-v1.schema.json", + "title": "Polylogue semantic transcript card", + "type": "object", +- "required": ["schema_version", "kind", "title", "source", "fields", "previews", "caveats"], ++ "required": [ ++ "schema_version", ++ "kind", ++ "title", ++ "source", ++ "fields", ++ "previews", ++ "caveats" ++ ], + "additionalProperties": false, + "properties": { +- "schema_version": {"const": "semantic-card.v1"}, +- "kind": {"enum": ["shell", "file_edit", "task", "lineage", "attachment", "fallback"]}, +- "title": {"type": "string", "minLength": 1}, +- "summary": {"type": "string"}, ++ "schema_version": { ++ "const": "semantic-card.v1" ++ }, ++ "kind": { ++ "enum": [ ++ "shell", ++ "file_read", ++ "file_edit", ++ "search", ++ "web", ++ "mcp", ++ "task", ++ "lineage", ++ "attachment", ++ "fallback" ++ ] ++ }, ++ "title": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "summary": { ++ "type": "string" ++ }, + "source": { + "type": "object", +- "required": ["session_id", "provider_family"], ++ "required": [ ++ "session_id", ++ "provider_family" ++ ], + "additionalProperties": false, + "properties": { +- "session_id": {"type": "string", "minLength": 1}, +- "provider_family": {"type": "string", "minLength": 1}, +- "message_id": {"type": "string"}, +- "block_id": {"type": "string"}, +- "block_index": {"type": "integer", "minimum": 0}, +- "tool_name": {"type": "string"}, +- "tool_id": {"type": "string"}, +- "result_message_id": {"type": "string"}, +- "result_block_id": {"type": "string"}, +- "result_block_index": {"type": "integer", "minimum": 0} ++ "session_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "provider_family": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "message_id": { ++ "type": "string" ++ }, ++ "block_id": { ++ "type": "string" ++ }, ++ "block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "tool_name": { ++ "type": "string" ++ }, ++ "tool_id": { ++ "type": "string" ++ }, ++ "result_message_id": { ++ "type": "string" ++ }, ++ "result_block_id": { ++ "type": "string" ++ }, ++ "result_block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "source_session_id": { ++ "type": "string" ++ }, ++ "role": { ++ "type": "string" ++ }, ++ "message_type": { ++ "type": "string" ++ }, ++ "material_origin": { ++ "type": "string" ++ }, ++ "timestamp": { ++ "type": "string" ++ }, ++ "duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "result_source_session_id": { ++ "type": "string" ++ }, ++ "result_role": { ++ "type": "string" ++ }, ++ "result_message_type": { ++ "type": "string" ++ }, ++ "result_material_origin": { ++ "type": "string" ++ }, ++ "result_timestamp": { ++ "type": "string" ++ }, ++ "result_duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ } + } + }, + "outcome": { + "type": "object", +- "required": ["state"], ++ "required": [ ++ "state" ++ ], + "additionalProperties": false, + "properties": { +- "state": {"enum": ["succeeded", "failed", "unknown"]}, +- "is_error": {"type": "boolean"}, +- "exit_code": {"type": "integer"} ++ "state": { ++ "enum": [ ++ "succeeded", ++ "failed", ++ "unknown" ++ ] ++ }, ++ "is_error": { ++ "type": "boolean" ++ }, ++ "exit_code": { ++ "type": "integer" ++ } + } + }, + "fields": { + "type": "array", + "items": { + "type": "object", +- "required": ["label", "value"], ++ "required": [ ++ "label", ++ "value" ++ ], + "additionalProperties": false, + "properties": { +- "label": {"type": "string"}, +- "value": {"type": "string"} ++ "label": { ++ "type": "string" ++ }, ++ "value": { ++ "type": "string" ++ } + } + } + }, + "previews": { + "type": "array", +- "items": {"$ref": "#/$defs/preview"} ++ "items": { ++ "$ref": "#/$defs/preview" ++ } + }, + "raw_evidence": { + "type": "object", + "additionalProperties": false, + "properties": { +- "tool_input": {"type": "object"}, +- "tool_input_raw": {"type": "string"}, +- "result_preview": {"$ref": "#/$defs/preview"} ++ "tool_input": { ++ "type": "object" ++ }, ++ "tool_input_raw": { ++ "type": "string" ++ }, ++ "result_preview": { ++ "$ref": "#/$defs/preview" ++ } + } + }, +- "caveats": {"type": "array", "items": {"type": "string"}} ++ "caveats": { ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } + }, + "$defs": { + "preview": { + "type": "object", + "required": [ +- "kind", "text", "line_count", "omitted_lines", "omitted_characters", +- "truncated", "strategy", "encoding_replacements" ++ "kind", ++ "text", ++ "line_count", ++ "omitted_lines", ++ "omitted_characters", ++ "truncated", ++ "strategy", ++ "encoding_replacements" + ], + "additionalProperties": false, + "properties": { +- "kind": {"type": "string"}, +- "text": {"type": "string"}, +- "line_count": {"type": "integer", "minimum": 0}, +- "omitted_lines": {"type": "integer", "minimum": 0}, +- "omitted_characters": {"type": "integer", "minimum": 0}, +- "truncated": {"type": "boolean"}, +- "strategy": {"enum": ["full", "head_tail", "character_bounded"]}, +- "encoding_replacements": {"type": "integer", "minimum": 0} ++ "kind": { ++ "type": "string" ++ }, ++ "text": { ++ "type": "string" ++ }, ++ "line_count": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_lines": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_characters": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "truncated": { ++ "type": "boolean" ++ }, ++ "strategy": { ++ "enum": [ ++ "full", ++ "head_tail", ++ "character_bounded" ++ ] ++ }, ++ "encoding_replacements": { ++ "type": "integer", ++ "minimum": 0 ++ } + } + } + } +diff --git a/polylogue/api/archive.py b/polylogue/api/archive.py +index 80d786f1a0793e19087b22938a94c1ebab776532..ac7779ac82aeb610fac9437ab92036e96583629f 100644 +--- a/polylogue/api/archive.py ++++ b/polylogue/api/archive.py +@@ -1466,6 +1466,7 @@ def _archive_message_to_domain(message: ArchiveMessageRow, *, origin: Origin) -> + "metadata": _maybe_parse_json_object(block.metadata), + "tool_result_is_error": block.tool_result_is_error, + "tool_result_exit_code": block.tool_result_exit_code, ++ "language": block.language, + }.items() + if value is not None + } +@@ -1473,6 +1474,7 @@ def _archive_message_to_domain(message: ArchiveMessageRow, *, origin: Origin) -> + ] + return Message( + id=message.message_id, ++ source_session_id=message.source_session_id, + role=Role.normalize(message.role), + text=text, + timestamp=parse_archive_datetime(message.occurred_at), +diff --git a/polylogue/archive/message/models.py b/polylogue/archive/message/models.py +index b66b2ec1878cd2a8c813af29cf5d18ac64224a56..46fb70f33631ab83a739d1cf4e8a5649827a9f97 100644 +--- a/polylogue/archive/message/models.py ++++ b/polylogue/archive/message/models.py +@@ -15,6 +15,8 @@ from polylogue.core.enums import BlockType, MaterialOrigin, Origin + + class Message(MessageRuntimeMixin, BaseModel): + id: str ++ # Physical owner retained when this message is inherited into a composed session. ++ source_session_id: str | None = None + role: Role + text: str | None = None + timestamp: datetime | None = None +diff --git a/polylogue/cli/messages.py b/polylogue/cli/messages.py +index 7288f4859243f83d208a272c8fe8aa417c52b187..75c59c88bd1d4eccad2c1d2445ef8c9c089046ca 100644 +--- a/polylogue/cli/messages.py ++++ b/polylogue/cli/messages.py +@@ -115,6 +115,7 @@ def run_messages( + session_id=session_id, + lineage=lineage, + provider_family=session.origin if session is not None else None, ++ page_bounded=offset > 0 or offset + len(messages) < total, + ) + rendered = render_semantic_transcript_markdown(transcript) + if rendered: +diff --git a/polylogue/daemon/http.py b/polylogue/daemon/http.py +index 63da0ce1d2f7af51ecc38669cf3afbe54e5b4f8b..c05b60c3d342f59b22a17058338d9b14ce915a75 100644 +--- a/polylogue/daemon/http.py ++++ b/polylogue/daemon/http.py +@@ -69,10 +69,12 @@ from polylogue.daemon.write_coordinator import ( + DaemonWriteThreadBridge, + ) + from polylogue.logging import get_logger ++from polylogue.rendering.semantic_card_models import LineageDescriptor + from polylogue.rendering.semantic_card_placement import ( + SemanticCardPlacement, + semantic_card_placement_for_messages, + ) ++from polylogue.rendering.semantic_cards import lineage_descriptor_from_topology + from polylogue.storage.sqlite.archive_tiers import ARCHIVE_VERSION_BY_TIER + from polylogue.storage.sqlite.archive_tiers.types import ArchiveTier + from polylogue.surfaces.payloads import ( +@@ -2846,11 +2848,21 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + for msg in conv.messages: + for att in msg.attachments or []: + session_attachments.append(attachment_to_envelope(att, session_id=session_id, message_id=msg.id)) +- # Semantic transcript cards (#ap7): the same provider-neutral shell / +- # file-edit / task / attachment registry the CLI renders to Markdown +- # (``cli/messages.py``), projected per-message for the web reader. ++ # Build one ordered semantic transcript for both the CLI vocabulary and ++ # the message-at-a-time web leaf. Topology is read at this orchestration ++ # seam; the pure renderer receives only a bounded descriptor. ++ topology_reader = getattr(poly, "get_session_topology", None) ++ topology = await topology_reader(session_id) if callable(topology_reader) else None ++ lineage = ( ++ lineage_descriptor_from_topology(topology, session_id=session_id) ++ if topology is not None ++ else None ++ ) + card_placement = semantic_card_placement_for_messages( +- conv.messages.to_list(), session_id=session_id, provider_family=conv.origin ++ conv.messages.to_list(), ++ session_id=session_id, ++ provider_family=conv.origin, ++ lineage=lineage, + ) + return { + "id": session_id, +@@ -2883,8 +2895,11 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + msg.text, + has_paste=bool(msg.has_paste) if hasattr(msg, "has_paste") else False, + ), ++ "semantic_entries": card_placement.entries_for(str(msg.id)), + "semantic_cards": card_placement.cards_for(str(msg.id)), + "semantic_card_suppressed": card_placement.is_suppressed(str(msg.id)), ++ "source_session_id": msg.source_session_id, ++ "duration_ms": msg.duration_ms, + "attachments": [ + attachment_to_envelope(att, session_id=session_id, message_id=msg.id) + for att in (msg.attachments or []) +@@ -2893,6 +2908,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + for msg in conv.messages + ], + "attachments": session_attachments, ++ "semantic_transcript": card_placement.transcript.to_document(), + "tags": conv.tags, + "branch_type": str(conv.branch_type) if conv.branch_type else None, + "parent_id": str(conv.parent_id) if conv.parent_id else None, +@@ -2926,6 +2942,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + self._archive_message_payload( + session_id, + message, ++ semantic_entries=card_placement.entries_for(str(message.message_id)), + semantic_cards=card_placement.cards_for(str(message.message_id)), + semantic_card_suppressed=card_placement.is_suppressed(str(message.message_id)), + ) +@@ -2963,9 +2980,12 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "word_count": word_count, + "messages": messages, + "attachments": session_attachments, ++ "semantic_transcript": card_placement.transcript.to_document(), ++ "lineage_complete": envelope.lineage_complete, ++ "lineage_truncation_reason": envelope.lineage_truncation_reason, + "tags": list(summary.tags), +- "branch_type": None, +- "parent_id": None, ++ "branch_type": envelope.branch_type, ++ "parent_id": envelope.parent_session_id, + "repo": None, + "cwd_display": None, + "model": None, +@@ -2988,8 +3008,22 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + + origin = Origin.from_string(envelope.origin) + messages = [_archive_message_to_domain(message, origin=origin) for message in envelope.messages] ++ lineage = None ++ if envelope.parent_session_id or envelope.root_session_id or not envelope.lineage_complete: ++ lineage = LineageDescriptor( ++ session_id=envelope.session_id, ++ root_session_id=envelope.root_session_id or envelope.session_id, ++ parent_session_id=envelope.parent_session_id, ++ relation=envelope.branch_type or "unknown", ++ resolved=envelope.parent_session_id is not None or envelope.root_session_id == envelope.session_id, ++ complete=envelope.lineage_complete, ++ truncation_reason=envelope.lineage_truncation_reason, ++ ) + return semantic_card_placement_for_messages( +- messages, session_id=envelope.session_id, provider_family=envelope.origin ++ messages, ++ session_id=envelope.session_id, ++ provider_family=envelope.origin, ++ lineage=lineage, + ) + + def _archive_message_attachments(self, session_id: str, message: ArchiveMessageRow) -> list[dict[str, object]]: +@@ -3009,6 +3043,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + session_id: str, + message: ArchiveMessageRow, + *, ++ semantic_entries: Sequence[JSONDocument] = (), + semantic_cards: Sequence[JSONDocument] = (), + semantic_card_suppressed: bool = False, + ) -> dict[str, object]: +@@ -3030,8 +3065,11 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "has_thinking": bool(message.has_thinking), + "has_paste_evidence": has_paste, + "paste_spans": envelope_paste_spans(text, has_paste=has_paste), ++ "semantic_entries": list(semantic_entries), + "semantic_cards": list(semantic_cards), + "semantic_card_suppressed": semantic_card_suppressed, ++ "source_session_id": message.source_session_id, ++ "duration_ms": message.duration_ms, + "attachments": self._archive_message_attachments(session_id, message), + } + +diff --git a/polylogue/daemon/web_shell_reader.py b/polylogue/daemon/web_shell_reader.py +index 939ab221e042804c384edd3e20e2a6f5835385e9..a6142dd367fb9a46b34f186f3ca4e1f25909687a 100644 +--- a/polylogue/daemon/web_shell_reader.py ++++ b/polylogue/daemon/web_shell_reader.py +@@ -399,6 +399,15 @@ function _polyIsThinking(m) { + || m.has_thinking === true; + } + ++function _polySemanticEntriesForMessage(m) { ++ // Preferred semantic-transcript payload: typed content, cards, and compact ++ // absence markers in one source-ordered stream. No raw-block heuristics run ++ // when this payload is present. ++ if (!m || !Array.isArray(m.semantic_entries) || !m.semantic_entries.length) return ''; ++ if (typeof _polySemanticEntriesHtml !== 'function') return ''; ++ return _polySemanticEntriesHtml(m); ++} ++ + function _polySemanticCardsForMessage(m) { + // Semantic transcript cards (#ap7): the daemon session-detail routes + // attach ``semantic_cards``/``semantic_card_suppressed`` to every message +@@ -423,7 +432,8 @@ function renderMessageBlocks(messages) { + var role = (m.role || '').toLowerCase(); + var isTool = _polyIsTool(m); + var isThinking = _polyIsThinking(m); +- var semanticCardsHtml = _polySemanticCardsForMessage(m); ++ var semanticEntriesHtml = _polySemanticEntriesForMessage(m); ++ var semanticCardsHtml = semanticEntriesHtml ? '' : _polySemanticCardsForMessage(m); + var tsHtml = m.timestamp + ? '' + + new Date(m.timestamp).toLocaleTimeString() + '' +@@ -435,7 +445,8 @@ function renderMessageBlocks(messages) { + if (isTool) blockClass += ' tool-block'; + var anchor = m.anchor || ('message-' + (m.id || '')); + var body; +- if (semanticCardsHtml) body = semanticCardsHtml; ++ if (semanticEntriesHtml) body = semanticEntriesHtml; ++ else if (semanticCardsHtml) body = semanticCardsHtml; + else if (isTool) body = _polyToolFoldHtml(m); + else if (isThinking) body = _polyThinkingFoldHtml(m); + else if (typeof _polyHasPaste === 'function' && _polyHasPaste(m)) { +@@ -446,7 +457,7 @@ function renderMessageBlocks(messages) { + // MK3 attachment strip (#1199). Prepended above the body so the + // cards stay visible regardless of fold variant. The hook stays + // optional because not every message renderer includes attachments. +- var attachmentStrip = (typeof _polyAttachmentStripHtml === 'function') ++ var attachmentStrip = (!semanticEntriesHtml && typeof _polyAttachmentStripHtml === 'function') + ? _polyAttachmentStripHtml(m) : ''; + if (attachmentStrip) body = attachmentStrip + body; + var rail = _polyActionRailHtml(m, convId); +diff --git a/polylogue/daemon/web_shell_semantic_cards.py b/polylogue/daemon/web_shell_semantic_cards.py +index b059b2d09d6e109062c1e2fc0d0e02908c725b74..a89ddf07b8e33d0b39f406307360bd7638b18306 100644 +--- a/polylogue/daemon/web_shell_semantic_cards.py ++++ b/polylogue/daemon/web_shell_semantic_cards.py +@@ -27,6 +27,10 @@ SEMANTIC_CARD_CSS = r""" + border-left: 3px solid var(--role-tool); + } + .sem-card.sem-card-shell { border-left-color: var(--accent); } ++.sem-card.sem-card-file_read { border-left-color: var(--accent); } ++.sem-card.sem-card-search { border-left-color: var(--accent); } ++.sem-card.sem-card-web { border-left-color: var(--active); } ++.sem-card.sem-card-mcp { border-left-color: var(--warn); } + .sem-card.sem-card-file_edit { border-left-color: var(--active); } + .sem-card.sem-card-task { border-left-color: var(--warn); } + .sem-card.sem-card-lineage { border-left-color: var(--text-dim); } +@@ -76,6 +80,19 @@ SEMANTIC_CARD_CSS = r""" + margin: 4px 0 0; padding-left: 16px; font-size: var(--small); color: var(--text-dim); + } + .sem-card-caveats li { margin: 1px 0; } ++.sem-entries { display: flex; flex-direction: column; gap: 6px; margin: 4px 0; } ++.sem-content { min-width: 0; } ++.sem-content-meta { ++ color: var(--text-dim); font-family: var(--font-mono); font-size: 10px; margin-bottom: 3px; ++} ++.sem-content-code pre, .sem-content-thinking pre { ++ margin: 0; padding: 6px 10px; white-space: pre-wrap; word-break: break-word; ++ font-family: var(--font-mono); font-size: var(--code); color: var(--text); ++} ++.sem-absence { ++ border-left: 2px solid var(--border-strong); padding: 4px 8px; color: var(--text-dim); ++ background: var(--panel-subtle); font-size: var(--small); font-family: var(--font-mono); ++} + """ + + +@@ -84,7 +101,8 @@ SEMANTIC_CARD_CSS = r""" + # slice looks up) plus its private helpers. + SEMANTIC_CARD_JS = r""" + var SEM_CARD_LABEL = { +- shell: 'shell', file_edit: 'edit', task: 'task', lineage: 'lineage', ++ shell: 'shell', file_read: 'read', file_edit: 'edit', search: 'search', ++ web: 'web', mcp: 'mcp', task: 'task', lineage: 'lineage', + attachment: 'attachment', fallback: 'tool' + }; + +@@ -181,7 +199,66 @@ function _polySemanticCardHtml(card) { + + ''; + } + +-// Exposed under the public hook name renderMessageBlocks looks up. ++function _polySemanticContentMeta(content) { ++ var bits = []; ++ if (content.source_session_id) bits.push('source ' + content.source_session_id); ++ if (content.material_origin) bits.push(content.material_origin); ++ if (content.block_id) bits.push('block ' + content.block_id); ++ if (typeof content.duration_ms === 'number' && content.duration_ms > 0) bits.push(content.duration_ms + ' ms'); ++ return bits.length ? '' : ''; ++} ++ ++function _polySemanticContentHtml(content) { ++ content = content || {}; ++ var kind = content.content_kind || 'text'; ++ var meta = _polySemanticContentMeta(content); ++ if (kind === 'code') { ++ var lang = content.language || 'code'; ++ var lines = String(content.text || '').split('\n').length; ++ return '
' ++ + '
' ++ + '' ++ + esc(lang) + '' + lines + ' lines
' ++ + '
' + meta + '
' + esc(content.text || '') + '
'; ++ } ++ if (kind === 'thinking') { ++ return '
' ++ + '
' ++ + '[thinking]
' ++ + '
' + meta + '
' + esc(content.text || '') + '
'; ++ } ++ return '
' + meta + '
' + esc(content.text || '') + '
'; ++} ++ ++function _polySemanticAbsenceHtml(absence) { ++ absence = absence || {}; ++ var count = Number(absence.count || 0); ++ var refs = Array.isArray(absence.references) ? absence.references : []; ++ var title = refs.map(function(ref) { ++ var value = 'message:' + String(ref.message_id || '?'); ++ if (ref.block_id) value += '/block:' + ref.block_id; ++ if (ref.source_session_id) value += '@source-session:' + ref.source_session_id; ++ return value; ++ }).join(', '); ++ return '
thinking unavailable · ' ++ + count + ' contiguous typed ' + (count === 1 ? 'entry' : 'entries') + '
'; ++} ++ ++function _polySemanticEntryHtml(entry) { ++ if (!entry) return ''; ++ if (entry.entry_type === 'card' && entry.card) return _polySemanticCardHtml(entry.card); ++ if (entry.entry_type === 'content' && entry.content) return _polySemanticContentHtml(entry.content); ++ if (entry.entry_type === 'absence' && entry.absence) return _polySemanticAbsenceHtml(entry.absence); ++ return ''; ++} ++ ++// Preferred hook: typed content/cards/absence in source order. ++function _polySemanticEntriesHtml(m) { ++ if (!m || !Array.isArray(m.semantic_entries) || !m.semantic_entries.length) return ''; ++ return '
' + m.semantic_entries.map(_polySemanticEntryHtml).join('') + '
'; ++} ++ ++// Compatibility hook for payloads produced before typed semantic_entries. + function _polySemanticCardsHtml(m) { + if (!m || !Array.isArray(m.semantic_cards) || !m.semantic_cards.length) return ''; + return '
' + m.semantic_cards.map(_polySemanticCardHtml).join('') + '
'; +diff --git a/polylogue/rendering/semantic_card_models.py b/polylogue/rendering/semantic_card_models.py +index c629ab91c3c13d1a8f59b1ad1f5a8af2f8625a62..b81559585abd1536fd4220c16dacdb532575dd9f 100644 +--- a/polylogue/rendering/semantic_card_models.py ++++ b/polylogue/rendering/semantic_card_models.py +@@ -1,4 +1,4 @@ +-"""Pure data contracts for provider-neutral semantic transcript cards.""" ++"""Pure data contracts for provider-neutral semantic transcript entries.""" + + from __future__ import annotations + +@@ -9,13 +9,18 @@ from typing import Literal + from polylogue.core.json import JSONDocument, is_json_value + + CARD_SCHEMA_VERSION: Literal["semantic-card.v1"] = "semantic-card.v1" ++TRANSCRIPT_SCHEMA_VERSION: Literal["semantic-transcript.v1"] = "semantic-transcript.v1" + + + class SemanticCardKind(str, Enum): + """Closed card vocabulary shared by CLI and web payloads.""" + + SHELL = "shell" ++ FILE_READ = "file_read" + FILE_EDIT = "file_edit" ++ SEARCH = "search" ++ WEB = "web" ++ MCP = "mcp" + TASK = "task" + LINEAGE = "lineage" + ATTACHMENT = "attachment" +@@ -38,34 +43,72 @@ class PreviewStrategy(str, Enum): + CHARACTER_BOUNDED = "character_bounded" + + ++class TranscriptContentKind(str, Enum): ++ """Typed content families that are not semantic evidence cards.""" ++ ++ TEXT = "text" ++ THINKING = "thinking" ++ CODE = "code" ++ ++ + @dataclass(frozen=True, slots=True) + class SemanticCardSource: +- """Exact source coordinates retained by a semantic card.""" ++ """Exact source coordinates retained by a semantic card. ++ ++ ``session_id`` is the logical session being rendered. ``source_session_id`` ++ preserves the physical owner of inherited prefix evidence when available. ++ Invocation and result authoredness/timing remain separate rather than being ++ collapsed into the card's presentation summary. ++ """ + + session_id: str + provider_family: str = "unknown" ++ source_session_id: str | None = None + message_id: str | None = None + block_id: str | None = None + block_index: int | None = None ++ role: str | None = None ++ message_type: str | None = None ++ material_origin: str | None = None ++ timestamp: str | None = None ++ duration_ms: int | None = None + tool_name: str | None = None + tool_id: str | None = None ++ result_source_session_id: str | None = None + result_message_id: str | None = None + result_block_id: str | None = None + result_block_index: int | None = None ++ result_role: str | None = None ++ result_message_type: str | None = None ++ result_material_origin: str | None = None ++ result_timestamp: str | None = None ++ result_duration_ms: int | None = None + + def to_document(self) -> JSONDocument: + return _without_none( + { + "session_id": self.session_id, + "provider_family": self.provider_family, ++ "source_session_id": self.source_session_id, + "message_id": self.message_id, + "block_id": self.block_id, + "block_index": self.block_index, ++ "role": self.role, ++ "message_type": self.message_type, ++ "material_origin": self.material_origin, ++ "timestamp": self.timestamp, ++ "duration_ms": self.duration_ms, + "tool_name": self.tool_name, + "tool_id": self.tool_id, ++ "result_source_session_id": self.result_source_session_id, + "result_message_id": self.result_message_id, + "result_block_id": self.result_block_id, + "result_block_index": self.result_block_index, ++ "result_role": self.result_role, ++ "result_message_type": self.result_message_type, ++ "result_material_origin": self.result_material_origin, ++ "result_timestamp": self.result_timestamp, ++ "result_duration_ms": self.result_duration_ms, + } + ) + +@@ -187,11 +230,17 @@ class LineageDescriptor: + relation: str = "unknown" + resolved: bool = True + cycle_detected: bool = False ++ complete: bool | None = None ++ truncation_reason: str | None = None + + + @dataclass(frozen=True, slots=True) + class TranscriptProse: +- """Prose entry retained alongside semantic cards.""" ++ """Typed content entry retained alongside semantic evidence cards. ++ ++ The historical class name remains for source compatibility. Serialized ++ entries use ``entry_type=content`` and include the typed content family. ++ """ + + message_id: str + role: str +@@ -199,6 +248,13 @@ class TranscriptProse: + text: str + block_id: str | None = None + block_type: str | None = None ++ content_kind: TranscriptContentKind = TranscriptContentKind.TEXT ++ language: str | None = None ++ foldable: bool = False ++ source_session_id: str | None = None ++ material_origin: str | None = None ++ timestamp: str | None = None ++ duration_ms: int | None = None + + def to_document(self) -> JSONDocument: + return _without_none( +@@ -209,43 +265,119 @@ class TranscriptProse: + "text": self.text, + "block_id": self.block_id, + "block_type": self.block_type, ++ "content_kind": self.content_kind.value, ++ "language": self.language, ++ "foldable": self.foldable, ++ "source_session_id": self.source_session_id, ++ "material_origin": self.material_origin, ++ "timestamp": self.timestamp, ++ "duration_ms": self.duration_ms, + } + ) + + ++@dataclass(frozen=True, slots=True) ++class TranscriptAbsenceReference: ++ """One typed empty-thinking evidence coordinate inside a compact run.""" ++ ++ message_id: str ++ block_id: str | None = None ++ source_session_id: str | None = None ++ role: str | None = None ++ message_type: str | None = None ++ material_origin: str | None = None ++ ++ def to_document(self) -> JSONDocument: ++ return _without_none( ++ { ++ "message_id": self.message_id, ++ "block_id": self.block_id, ++ "source_session_id": self.source_session_id, ++ "role": self.role, ++ "message_type": self.message_type, ++ "material_origin": self.material_origin, ++ } ++ ) ++ ++ ++@dataclass(frozen=True, slots=True) ++class TranscriptAbsence: ++ """Compact marker for a contiguous run of typed but empty thinking.""" ++ ++ references: tuple[TranscriptAbsenceReference, ...] ++ absence_kind: Literal["thinking_unavailable"] = "thinking_unavailable" ++ presentation: Literal["compact"] = "compact" ++ ++ @property ++ def count(self) -> int: ++ return len(self.references) ++ ++ def to_document(self) -> JSONDocument: ++ return { ++ "absence_kind": self.absence_kind, ++ "presentation": self.presentation, ++ "count": self.count, ++ "references": [reference.to_document() for reference in self.references], ++ } ++ ++ + @dataclass(frozen=True, slots=True) + class SemanticTranscriptEntry: +- """Exactly one ordered prose or card entry.""" ++ """Exactly one ordered content, absence, or card entry.""" + + prose: TranscriptProse | None = None + card: SemanticCard | None = None ++ absence: TranscriptAbsence | None = None + + def __post_init__(self) -> None: +- if (self.prose is None) == (self.card is None): +- raise ValueError("semantic transcript entry requires exactly one of prose or card") ++ present = sum(item is not None for item in (self.prose, self.card, self.absence)) ++ if present != 1: ++ raise ValueError("semantic transcript entry requires exactly one content, absence, or card value") ++ ++ @property ++ def primary_message_id(self) -> str | None: ++ if self.card is not None: ++ return self.card.source.message_id or self.card.source.result_message_id ++ if self.prose is not None: ++ return self.prose.message_id ++ assert self.absence is not None ++ return self.absence.references[0].message_id if self.absence.references else None + + def to_document(self) -> JSONDocument: + if self.card is not None: + return {"entry_type": "card", "card": self.card.to_document()} ++ if self.absence is not None: ++ return {"entry_type": "absence", "absence": self.absence.to_document()} + assert self.prose is not None +- return {"entry_type": "prose", "prose": self.prose.to_document()} ++ return {"entry_type": "content", "content": self.prose.to_document()} + + + @dataclass(frozen=True, slots=True) + class SemanticTranscript: + session_id: str + entries: tuple[SemanticTranscriptEntry, ...] = field(default_factory=tuple) ++ pairing_scope: Literal["complete_session", "page"] = "complete_session" ++ absence_compaction_scope: Literal["complete_session", "page"] = "complete_session" ++ lineage_complete: bool | None = None ++ lineage_truncation_reason: str | None = None + + @property + def cards(self) -> tuple[SemanticCard, ...]: + return tuple(entry.card for entry in self.entries if entry.card is not None) + + def to_document(self) -> JSONDocument: +- return { +- "schema_version": "semantic-transcript.v1", ++ result: JSONDocument = { ++ "schema_version": TRANSCRIPT_SCHEMA_VERSION, + "session_id": self.session_id, ++ "pairing_scope": self.pairing_scope, ++ "absence_compaction_scope": self.absence_compaction_scope, + "entries": [entry.to_document() for entry in self.entries], + } ++ if self.lineage_complete is not None: ++ result["lineage_complete"] = self.lineage_complete ++ if self.lineage_truncation_reason is not None: ++ result["lineage_truncation_reason"] = self.lineage_truncation_reason ++ return result + + + def card_json_documents(cards: tuple[SemanticCard, ...] | list[SemanticCard]) -> list[JSONDocument]: +@@ -262,6 +394,7 @@ def _without_none(values: dict[str, object]) -> JSONDocument: + + __all__ = [ + "CARD_SCHEMA_VERSION", ++ "TRANSCRIPT_SCHEMA_VERSION", + "card_json_documents", + "CardOutcomeState", + "LineageDescriptor", +@@ -275,5 +408,8 @@ __all__ = [ + "SemanticCardSource", + "SemanticTranscript", + "SemanticTranscriptEntry", ++ "TranscriptAbsence", ++ "TranscriptAbsenceReference", ++ "TranscriptContentKind", + "TranscriptProse", + ] +diff --git a/polylogue/rendering/semantic_card_placement.py b/polylogue/rendering/semantic_card_placement.py +index aff24fae9ebc8a4f4be934286dba9007fa8fed23..b78987802ee72ac92f6a8e090ddf43dbc38ef620 100644 +--- a/polylogue/rendering/semantic_card_placement.py ++++ b/polylogue/rendering/semantic_card_placement.py +@@ -1,17 +1,8 @@ +-"""Per-message semantic-card placement shared by every card-consuming surface. +- +-``rendering/semantic_cards.py`` builds one ordered :class:`SemanticTranscript` +-(pure, no archive access) that the CLI already renders to Markdown +-(``cli/messages.py``). This module derives a second, equally pure projection +-of that same transcript: a lookup from message id to the cards whose primary +-evidence lives on that message, plus the set of messages whose entire content +-was already absorbed into a card (a paired tool-result message, most often). +- +-Surfaces that render one message at a time — the daemon web reader today — +-consume this instead of re-deriving tool semantics from raw block flags. This +-keeps CLI Markdown and web HTML two renderers over one shared registry +-(``rendering/semantic_card_registry.py``) rather than two independent +-tool-classification implementations drifting apart. ++"""Per-message placement of one shared semantic transcript. ++ ++The CLI renders :class:`SemanticTranscript` directly. Message-at-a-time ++surfaces consume this pure placement adapter, which preserves transcript order ++and carries the same typed entry documents instead of reclassifying raw blocks. + """ + + from __future__ import annotations +@@ -21,73 +12,88 @@ from dataclasses import dataclass + + from polylogue.archive.message.models import Message + from polylogue.core.json import JSONDocument ++from polylogue.rendering.semantic_card_models import LineageDescriptor, SemanticTranscript + from polylogue.rendering.semantic_cards import build_semantic_transcript + + + @dataclass(frozen=True, slots=True) + class SemanticCardPlacement: +- """Card documents and suppression, keyed by the message that owns them.""" ++ """Typed entries, compatibility card lists, and absorbed message IDs.""" + ++ transcript: SemanticTranscript ++ entries_by_message_id: Mapping[str, tuple[JSONDocument, ...]] + cards_by_message_id: Mapping[str, tuple[JSONDocument, ...]] + suppressed_message_ids: frozenset[str] + ++ def entries_for(self, message_id: str) -> list[JSONDocument]: ++ return list(self.entries_by_message_id.get(message_id, ())) ++ + def cards_for(self, message_id: str) -> list[JSONDocument]: +- """Cards whose primary evidence is on *message_id*, in transcript order.""" ++ """Compatibility projection for older card-only web payload consumers.""" + + return list(self.cards_by_message_id.get(message_id, ())) + + def is_suppressed(self, message_id: str) -> bool: +- """Whether *message_id* is fully absorbed into another message's card. +- +- A shell/edit/task card pairs a tool-use message with its tool-result +- message by exact ``tool_id``. The card carries both message ids +- (``source.message_id`` and ``source.result_message_id``); the result +- message renders nothing of its own, or the operator would see the +- same evidence twice — once inside the card, once as a bare +- tool-result block. +- """ ++ """Whether all evidence on *message_id* is represented elsewhere.""" + + return message_id in self.suppressed_message_ids + + +-_EMPTY_PLACEMENT = SemanticCardPlacement(cards_by_message_id={}, suppressed_message_ids=frozenset()) +- + + def semantic_card_placement_for_messages( + messages: Sequence[Message | Mapping[str, object] | object], + *, + session_id: str, + provider_family: str | None = None, ++ lineage: LineageDescriptor | None = None, ++ page_bounded: bool = False, + ) -> SemanticCardPlacement: +- """Index a session's semantic cards by the message that should render them. +- +- Accepts the same message shapes as +- :func:`polylogue.rendering.semantic_cards.build_semantic_transcript` +- (domain ``Message`` objects or message-shaped mappings) so DB-backed and +- archive-backed session reads can share one call. +- """ ++ """Index the shared semantic stream by the message that should render it.""" + +- if not messages: +- return _EMPTY_PLACEMENT + transcript = build_semantic_transcript( + messages, + session_id=session_id, + provider_family=provider_family, ++ lineage=lineage, ++ page_bounded=page_bounded, + ) ++ message_ids = [ ++ str(getattr(message, "id", getattr(message, "message_id", ""))) ++ if not isinstance(message, Mapping) ++ else str(message.get("id", message.get("message_id", ""))) ++ for message in messages ++ ] ++ first_message_id = next((message_id for message_id in message_ids if message_id), None) ++ ++ entries_by_message: dict[str, list[JSONDocument]] = {} + cards_by_message: dict[str, list[JSONDocument]] = {} +- suppressed: set[str] = set() +- for card in transcript.cards: +- primary_id = card.source.message_id or card.source.result_message_id +- if primary_id is not None: +- cards_by_message.setdefault(primary_id, []).append(card.to_document()) +- result_id = card.source.result_message_id +- if result_id is not None and result_id != primary_id: +- suppressed.add(result_id) +- if not cards_by_message and not suppressed: +- return _EMPTY_PLACEMENT ++ absorbed_candidates: set[str] = set() ++ ++ for entry in transcript.entries: ++ primary_id = entry.primary_message_id or first_message_id ++ if primary_id is None: ++ continue ++ document = entry.to_document() ++ entries_by_message.setdefault(primary_id, []).append(document) ++ if entry.card is not None: ++ cards_by_message.setdefault(primary_id, []).append(entry.card.to_document()) ++ result_id = entry.card.source.result_message_id ++ if result_id is not None and result_id != primary_id: ++ absorbed_candidates.add(result_id) ++ elif entry.absence is not None: ++ for reference in entry.absence.references[1:]: ++ if reference.message_id != primary_id: ++ absorbed_candidates.add(reference.message_id) ++ ++ # Never hide a message that owns another visible content/card entry. This ++ # matters for mixed tool-result + context/attachment rows. ++ visible_message_ids = set(entries_by_message) ++ suppressed = frozenset(absorbed_candidates - visible_message_ids) + return SemanticCardPlacement( ++ transcript=transcript, ++ entries_by_message_id={key: tuple(value) for key, value in entries_by_message.items()}, + cards_by_message_id={key: tuple(value) for key, value in cards_by_message.items()}, +- suppressed_message_ids=frozenset(suppressed), ++ suppressed_message_ids=suppressed, + ) + + +diff --git a/polylogue/rendering/semantic_card_registry.py b/polylogue/rendering/semantic_card_registry.py +index 95ebafa6949726403ce0a5518f942eeb9af2e4e0..82b0c2d422ad44d6e41b5c4a79fa9add4fb182f4 100644 +--- a/polylogue/rendering/semantic_card_registry.py ++++ b/polylogue/rendering/semantic_card_registry.py +@@ -1,23 +1,22 @@ + """Explicit provider/tool registry for semantic transcript cards. + +-Classification is exact and reviewable. Persisted ``semantic_type`` wins; +-otherwise only aliases listed here specialize. Unknown names remain fallback +-cards with raw evidence. The registry records only aliases grounded in the +-repository's parser fixtures, parser record types, or standing classifier +-contract; it does not speculate about a provider's open tool namespace. ++Classification is structural and reviewable. Persisted ``semantic_type`` wins; ++otherwise only exact aliases or the exact ``mcp__server__method`` grammar ++specialize. Unknown names remain fallback cards with raw evidence. + """ + + from __future__ import annotations + ++import re + from dataclasses import dataclass + from typing import Literal + +-from polylogue.core.enums import SemanticBlockType ++from polylogue.core.enums import Origin, SemanticBlockType + from polylogue.core.json import JSONDocument + from polylogue.rendering.semantic_card_models import SemanticCardKind + + MappingEvidenceKind = Literal["fixture_observed", "parser_record_type", "classifier_contract"] +-RenderingStatus = Literal["launch", "model_only", "fallback"] ++RenderingStatus = Literal["launch", "fallback"] + + + @dataclass(frozen=True, slots=True) +@@ -42,12 +41,10 @@ class ToolMapping: + } + + +-# These are exact aliases found in committed parser fixtures, parser record +-# vocabulary, or the existing provider-neutral classifier. Provider tool-name +-# spaces remain open: a name absent here is a raw fallback unless upstream has +-# already persisted a trusted semantic_type. ++# Exact aliases grounded in committed parser fixtures, parser record types, or ++# the existing provider-neutral classifier. Provider namespaces stay open: an ++# unlisted name is a raw fallback unless upstream persisted semantic_type. + _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( +- # Claude Code: observed parser fixtures. + ToolMapping( + "claude-code", + "Bash", +@@ -89,7 +86,7 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "Task", + "subagent", + SemanticCardKind.TASK, +- "model_only", ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -97,8 +94,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Read", + "file_read", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.FILE_READ, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -106,8 +103,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Grep", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -115,13 +112,11 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Glob", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +- # Existing cross-provider classifier aliases that are meaningful for +- # Claude Code even where the parser fixture corpus has no dedicated row. + ToolMapping( + "claude-code", + "MultiEdit", +@@ -135,8 +130,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "WebFetch", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -144,8 +139,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "WebSearch", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -153,8 +148,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "AskUserQuestion", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -162,8 +157,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "EnterPlanMode", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -171,13 +166,11 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "ExitPlanMode", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +- # Codex: exact function names observed in fixtures plus explicit envelope +- # record types whose fallback name is defined by the parser. + ToolMapping( + "codex", + "exec_command", +@@ -209,8 +202,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "search", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_codex.py", + ), +@@ -227,8 +220,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "tool_search_call", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "parser_record_type", + "polylogue/sources/parsers/codex.py", + ), +@@ -236,8 +229,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "web_search_call", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "parser_record_type", + "polylogue/sources/parsers/codex.py", + ), +@@ -246,19 +239,16 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "spawn_agent", + "subagent", + SemanticCardKind.TASK, +- "model_only", ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +- # Gemini CLI and Hermes use open function-name shapes. Only exact names +- # observed in committed fixtures are listed; persisted semantic_type is the +- # safe path for every other provider-private name. + ToolMapping( + "gemini-cli", + "read_file", + "file_read", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.FILE_READ, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_local_agent.py", + ), +@@ -280,8 +270,6 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "fixture_observed", + "tests/unit/sources/test_parsers_local_agent.py", + ), +- # ChatGPT recipient names form an open namespace. These are the exact +- # recipient-addressed tools present in parser regressions. + ToolMapping( + "chatgpt", + "canmore.update_textdoc", +@@ -295,8 +283,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "chatgpt", + "web", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_chatgpt.py", + ), +@@ -316,14 +304,33 @@ _TOOL_MAPPING_INDEX = { + } + + _SEMANTIC_CARD_KIND: dict[str, SemanticCardKind] = { ++ SemanticBlockType.FILE_READ.value: SemanticCardKind.FILE_READ, ++ SemanticBlockType.FILE_WRITE.value: SemanticCardKind.FILE_EDIT, ++ SemanticBlockType.FILE_EDIT.value: SemanticCardKind.FILE_EDIT, + SemanticBlockType.SHELL.value: SemanticCardKind.SHELL, + SemanticBlockType.GIT.value: SemanticCardKind.SHELL, +- SemanticBlockType.FILE_EDIT.value: SemanticCardKind.FILE_EDIT, +- SemanticBlockType.FILE_WRITE.value: SemanticCardKind.FILE_EDIT, ++ SemanticBlockType.SEARCH.value: SemanticCardKind.SEARCH, ++ SemanticBlockType.WEB.value: SemanticCardKind.WEB, ++ SemanticBlockType.AGENT.value: SemanticCardKind.TASK, + SemanticBlockType.SUBAGENT.value: SemanticCardKind.TASK, + } + +-_PROVIDER_FAMILIES = ("chatgpt", "claude-code", "codex", "gemini-cli", "hermes") ++_MCP_SEGMENT_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]*$") ++ ++ ++_ORIGIN_PROVIDER_FAMILY: dict[Origin, str] = { ++ Origin.CLAUDE_CODE_SESSION: "claude-code", ++ Origin.CODEX_SESSION: "codex", ++ Origin.GEMINI_CLI_SESSION: "gemini-cli", ++ Origin.HERMES_SESSION: "hermes", ++ Origin.ANTIGRAVITY_SESSION: "antigravity", ++ Origin.BEADS_ISSUE: "beads", ++ Origin.GROK_EXPORT: "grok", ++ Origin.CHATGPT_EXPORT: "chatgpt", ++ Origin.CLAUDE_AI_EXPORT: "claude-ai", ++ Origin.AISTUDIO_DRIVE: "gemini-cli", ++ Origin.UNKNOWN_EXPORT: "unknown", ++} + + + def normalize_provider_family(value: object) -> str: +@@ -331,16 +338,28 @@ def normalize_provider_family(value: object) -> str: + + raw = getattr(value, "value", value) + token = str(raw or "unknown").strip().lower().replace("_", "-") +- aliases = { +- "claude-code-session": "claude-code", +- "codex-session": "codex", +- "gemini-cli-session": "gemini-cli", +- "hermes-session": "hermes", +- "chatgpt-export": "chatgpt", +- "claude-ai-export": "claude-ai", +- "aistudio-drive": "gemini-cli", +- } +- return aliases.get(token, token) ++ try: ++ origin = Origin(token) ++ except ValueError: ++ aliases = { ++ "gemini": "gemini-cli", ++ "drive": "gemini-cli", ++ } ++ return aliases.get(token, token) ++ return _ORIGIN_PROVIDER_FAMILY[origin] ++ ++ ++def parse_mcp_tool_name(tool_name: str | None) -> tuple[str, str] | None: ++ """Return exact MCP server/method parts; reject malformed lookalikes.""" ++ ++ if not tool_name: ++ return None ++ if not tool_name.startswith("mcp__"): ++ return None ++ parts = tool_name.removeprefix("mcp__").split("__") ++ if len(parts) != 2 or not all(_MCP_SEGMENT_RE.fullmatch(part) for part in parts): ++ return None ++ return parts[0], parts[1] + + + def tool_mapping_rows() -> tuple[ToolMapping, ...]: +@@ -350,32 +369,64 @@ def tool_mapping_rows() -> tuple[ToolMapping, ...]: + def provider_namespace_documents() -> list[JSONDocument]: + """Describe the honest behavior outside the finite exact-alias table.""" + +- counts = dict.fromkeys(_PROVIDER_FAMILIES, 0) ++ providers = sorted(set(_ORIGIN_PROVIDER_FAMILY.values())) ++ counts = dict.fromkeys(providers, 0) + for row in _TOOL_MAPPINGS: +- counts[row.provider_family] += 1 ++ counts[row.provider_family] = counts.get(row.provider_family, 0) + 1 + return [ + { + "provider_family": provider, + "namespace": "open", + "grounded_exact_aliases": counts[provider], +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence", ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", + } +- for provider in _PROVIDER_FAMILIES ++ for provider in providers + ] + + ++def origin_policy_documents() -> list[JSONDocument]: ++ """Map every executable/persisted Origin to an explicit rendering policy.""" ++ ++ mapped_providers = {row.provider_family for row in _TOOL_MAPPINGS} ++ return [ ++ { ++ "origin": origin.value, ++ "provider_family": provider, ++ "exact_alias_registry": provider in mapped_providers, ++ "unlisted_behavior": "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence", ++ "fallback_reason": ( ++ None ++ if provider in mapped_providers ++ else "origin has no grounded exact tool aliases in the committed parser evidence corpus" ++ ), ++ } ++ for origin, provider in _ORIGIN_PROVIDER_FAMILY.items() ++ ] ++ ++ ++def mcp_pattern_document() -> JSONDocument: ++ return { ++ "grammar": "mcp__server__method", ++ "card_kind": SemanticCardKind.MCP.value, ++ "classification_basis": "exact_structural_tool_name", ++ "malformed_behavior": SemanticCardKind.FALLBACK.value, ++ } ++ ++ + def card_kind_for_tool( + *, + provider_family: str, + tool_name: str | None, + semantic_type: str | None, + ) -> SemanticCardKind: +- """Resolve a card kind from persisted semantics or exact aliases.""" ++ """Resolve a card kind from persisted semantics or exact structural names.""" + + if semantic_type: + persisted = _SEMANTIC_CARD_KIND.get(semantic_type.strip().lower()) + if persisted is not None: + return persisted ++ if parse_mcp_tool_name(tool_name) is not None: ++ return SemanticCardKind.MCP + if tool_name: + row = _TOOL_MAPPING_INDEX.get((normalize_provider_family(provider_family), tool_name.casefold())) + if row is not None: +@@ -386,18 +437,11 @@ def card_kind_for_tool( + def semantic_type_policy_documents() -> list[JSONDocument]: + """Map every persisted semantic family to its current honest card behavior.""" + +- status_by_type: dict[str, RenderingStatus] = { +- SemanticBlockType.FILE_WRITE.value: "launch", +- SemanticBlockType.FILE_EDIT.value: "launch", +- SemanticBlockType.SHELL.value: "launch", +- SemanticBlockType.GIT.value: "launch", +- SemanticBlockType.SUBAGENT.value: "model_only", +- } + return [ + { + "semantic_type": semantic_type.value, + "card_kind": _SEMANTIC_CARD_KIND.get(semantic_type.value, SemanticCardKind.FALLBACK).value, +- "rendering_status": status_by_type.get(semantic_type.value, "fallback"), ++ "rendering_status": ("launch" if semantic_type.value in _SEMANTIC_CARD_KIND else "fallback"), + "classification_basis": "persisted_semantic_type", + } + for semantic_type in SemanticBlockType +@@ -411,7 +455,10 @@ def tool_mapping_documents() -> list[JSONDocument]: + __all__ = [ + "card_kind_for_tool", + "MappingEvidenceKind", ++ "mcp_pattern_document", + "normalize_provider_family", ++ "origin_policy_documents", ++ "parse_mcp_tool_name", + "provider_namespace_documents", + "RenderingStatus", + "semantic_type_policy_documents", +diff --git a/polylogue/rendering/semantic_cards.py b/polylogue/rendering/semantic_cards.py +index 5f4cf28e4a13d866b0bb82f3867692ad9af907d4..5ee4eb937a712389c63d91d80951f88e695ff32b 100644 +--- a/polylogue/rendering/semantic_cards.py ++++ b/polylogue/rendering/semantic_cards.py +@@ -7,7 +7,9 @@ import json + from collections import defaultdict, deque + from collections.abc import Iterable, Mapping, Sequence + from dataclasses import dataclass, replace ++from datetime import datetime + from enum import Enum ++from typing import Literal + + from polylogue.archive.message.models import Message + from polylogue.core.enums import BlockType +@@ -27,13 +29,32 @@ from polylogue.rendering.semantic_card_models import ( + SemanticCardSource, + SemanticTranscript, + SemanticTranscriptEntry, ++ TranscriptAbsence, ++ TranscriptAbsenceReference, ++ TranscriptContentKind, + TranscriptProse, + ) +-from polylogue.rendering.semantic_card_registry import card_kind_for_tool, normalize_provider_family ++from polylogue.rendering.semantic_card_registry import ( ++ card_kind_for_tool, ++ normalize_provider_family, ++ parse_mcp_tool_name, ++) + + DEFAULT_PREVIEW_HEAD_LINES = 48 + DEFAULT_PREVIEW_TAIL_LINES = 16 + DEFAULT_PREVIEW_MAX_CHARS = 16_000 ++_THINKING_BLOCK_TYPES = {BlockType.THINKING.value, BlockType.REASONING.value} ++ ++ ++@dataclass(frozen=True, slots=True) ++class RenderableAttachment: ++ id: str ++ name: str | None = None ++ mime_type: str | None = None ++ size_bytes: int | None = None ++ path: str | None = None ++ source_url: str | None = None ++ caption: str | None = None + + + @dataclass(frozen=True, slots=True) +@@ -46,6 +67,11 @@ class RenderableMessage: + text: str | None + provider_family: str + blocks: tuple[RenderableBlock, ...] ++ attachments: tuple[RenderableAttachment, ...] = () ++ source_session_id: str | None = None ++ material_origin: str = "unknown" ++ timestamp: str | None = None ++ duration_ms: int | None = None + parent_id: str | None = None + branch_index: int = 0 + +@@ -65,23 +91,29 @@ def coerce_renderable_message(value: Message | Mapping[str, object] | object) -> + raw_role = value.get("role", "unknown") + raw_message_type = value.get("message_type", "message") + raw_text = value.get("text") +- raw_provider = value.get("provider", value.get("provider_family", "unknown")) ++ raw_provider = value.get("provider", value.get("provider_family", value.get("origin", "unknown"))) + raw_blocks = value.get("blocks", value.get("content_blocks", ())) +- raw_parent_id = value.get("parent_id") +- raw_branch_index = value.get("branch_index", 0) ++ raw_attachments = value.get("attachments", ()) ++ raw_source_session_id = value.get("source_session_id", value.get("session_id")) ++ raw_material_origin = value.get("material_origin", "unknown") ++ raw_timestamp = value.get("timestamp", value.get("occurred_at")) ++ raw_duration_ms = value.get("duration_ms") ++ raw_parent_id = value.get("parent_id", value.get("parent_message_id")) ++ raw_branch_index = value.get("branch_index", value.get("variant_index", 0)) + else: + raw_id = getattr(value, "id", getattr(value, "message_id", "")) + raw_role = getattr(value, "role", "unknown") + raw_message_type = getattr(value, "message_type", "message") + raw_text = getattr(value, "text", None) +- raw_provider = getattr(value, "provider", "unknown") ++ raw_provider = getattr(value, "provider", getattr(value, "origin", "unknown")) + raw_blocks = getattr(value, "blocks", getattr(value, "content_blocks", ())) +- raw_parent_id = getattr(value, "parent_id", None) +- raw_branch_index = getattr(value, "branch_index", 0) +- +- raw_role = _enum_like_value(raw_role) +- raw_message_type = _enum_like_value(raw_message_type) +- raw_provider = _enum_like_value(raw_provider) ++ raw_attachments = getattr(value, "attachments", ()) ++ raw_source_session_id = getattr(value, "source_session_id", getattr(value, "session_id", None)) ++ raw_material_origin = getattr(value, "material_origin", "unknown") ++ raw_timestamp = getattr(value, "timestamp", getattr(value, "occurred_at", None)) ++ raw_duration_ms = getattr(value, "duration_ms", None) ++ raw_parent_id = getattr(value, "parent_id", getattr(value, "parent_message_id", None)) ++ raw_branch_index = getattr(value, "branch_index", getattr(value, "variant_index", 0)) + + blocks_input: Sequence[object] | None + if isinstance(raw_blocks, Sequence) and not isinstance(raw_blocks, (str, bytes, bytearray)): +@@ -89,32 +121,58 @@ def coerce_renderable_message(value: Message | Mapping[str, object] | object) -> + else: + blocks_input = None + +- if isinstance(raw_branch_index, bool): +- branch_index = 0 +- elif isinstance(raw_branch_index, (int, str)): +- try: +- branch_index = int(raw_branch_index or 0) +- except ValueError: +- branch_index = 0 +- else: +- branch_index = 0 +- +- if isinstance(raw_text, str): +- text = raw_text +- elif isinstance(raw_text, (bytes, bytearray)): +- text = bytes(raw_text).decode("utf-8", errors="replace") ++ attachments_input: Sequence[object] ++ if isinstance(raw_attachments, Sequence) and not isinstance(raw_attachments, (str, bytes, bytearray)): ++ attachments_input = raw_attachments + else: +- text = None ++ attachments_input = () + + return RenderableMessage( + id=str(raw_id), +- role=str(raw_role), +- message_type=str(raw_message_type), +- text=text, +- provider_family=normalize_provider_family(raw_provider), ++ role=str(_enum_like_value(raw_role)), ++ message_type=str(_enum_like_value(raw_message_type)), ++ text=_optional_text(raw_text), ++ provider_family=normalize_provider_family(_enum_like_value(raw_provider)), + blocks=coerce_renderable_blocks(blocks_input), ++ attachments=tuple( ++ attachment for raw in attachments_input if (attachment := _coerce_attachment(raw)) is not None ++ ), ++ source_session_id=str(raw_source_session_id) if raw_source_session_id is not None else None, ++ material_origin=str(_enum_like_value(raw_material_origin)), ++ timestamp=_timestamp_text(raw_timestamp), ++ duration_ms=_optional_nonnegative_int(raw_duration_ms), + parent_id=str(raw_parent_id) if raw_parent_id is not None else None, +- branch_index=branch_index, ++ branch_index=_coerce_int(raw_branch_index), ++ ) ++ ++ ++def _coerce_attachment(value: object) -> RenderableAttachment | None: ++ if isinstance(value, Mapping): ++ raw_id = value.get("id", value.get("attachment_id", "")) ++ if not raw_id: ++ return None ++ return RenderableAttachment( ++ id=str(raw_id), ++ name=_optional_string(value.get("name", value.get("display_name"))), ++ mime_type=_optional_string(value.get("mime_type", value.get("media_type"))), ++ size_bytes=_optional_nonnegative_int(value.get("size_bytes", value.get("byte_count"))), ++ path=_optional_string(value.get("path")), ++ source_url=_optional_string(value.get("source_url", value.get("url"))), ++ caption=_optional_string(value.get("caption")), ++ ) ++ raw_id = getattr(value, "id", getattr(value, "attachment_id", None)) ++ if raw_id is None: ++ return None ++ return RenderableAttachment( ++ id=str(raw_id), ++ name=_optional_string(getattr(value, "name", getattr(value, "display_name", None))), ++ mime_type=_optional_string(getattr(value, "mime_type", getattr(value, "media_type", None))), ++ size_bytes=_optional_nonnegative_int( ++ getattr(value, "size_bytes", getattr(value, "byte_count", None)) ++ ), ++ path=_optional_string(getattr(value, "path", None)), ++ source_url=_optional_string(getattr(value, "source_url", getattr(value, "url", None))), ++ caption=_optional_string(getattr(value, "caption", None)), + ) + + +@@ -125,6 +183,49 @@ def _enum_like_value(value: object) -> object: + return candidate if isinstance(candidate, str) else value + + ++def _optional_text(value: object) -> str | None: ++ if isinstance(value, str): ++ return value ++ if isinstance(value, (bytes, bytearray)): ++ return bytes(value).decode("utf-8", errors="replace") ++ return None ++ ++ ++def _optional_string(value: object) -> str | None: ++ return value if isinstance(value, str) else None ++ ++ ++def _optional_nonnegative_int(value: object) -> int | None: ++ if value is None or isinstance(value, bool): ++ return None ++ if not isinstance(value, (int, float, str, bytes, bytearray)): ++ return None ++ try: ++ parsed = int(value) ++ except (TypeError, ValueError): ++ return None ++ return max(parsed, 0) ++ ++ ++def _coerce_int(value: object) -> int: ++ if isinstance(value, bool): ++ return 0 ++ if not isinstance(value, (int, float, str, bytes, bytearray)): ++ return 0 ++ try: ++ return int(value) ++ except (TypeError, ValueError): ++ return 0 ++ ++ ++def _timestamp_text(value: object) -> str | None: ++ if isinstance(value, datetime): ++ return value.isoformat() ++ if isinstance(value, str): ++ return value ++ return None ++ ++ + def lineage_descriptor_from_topology(topology: SessionTopology, *, session_id: str) -> LineageDescriptor | None: + """Project an already-read topology into pure lineage-card input.""" + +@@ -137,13 +238,12 @@ def lineage_descriptor_from_topology(topology: SessionTopology, *, session_id: s + cycle_detected=True, + ) + return None +- relation = target_edge.kind.value + return LineageDescriptor( + session_id=session_id, + root_session_id=str(topology.root_id), + parent_session_id=str(target_edge.parent_id) if target_edge.parent_id is not None else None, + parent_native_id=target_edge.parent_native_id, +- relation=relation, ++ relation=target_edge.kind.value, + resolved=target_edge.resolved, + cycle_detected=topology.cycle_detected, + ) +@@ -155,12 +255,13 @@ def build_semantic_transcript( + session_id: str, + lineage: LineageDescriptor | None = None, + provider_family: str | None = None, ++ page_bounded: bool = False, + ) -> SemanticTranscript: +- """Build ordered prose/card entries without archive access. ++ """Build one ordered semantic stream without archive access. + +- Result pairing is exact by ``tool_id`` and FIFO within duplicate IDs. Text +- never influences classification or outcome. A caller may supply the session +- origin as ``provider_family`` when legacy message rows carry ``unknown``. ++ Result pairing is exact by ``tool_id`` and FIFO within duplicate IDs. Empty ++ thinking is detected from typed block/message state only and compacted once ++ per contiguous run. Text never influences tool classification or outcome. + """ + + normalized: list[RenderableMessage] = [] +@@ -169,6 +270,8 @@ def build_semantic_transcript( + message = coerce_renderable_message(raw) + if session_provider and message.provider_family in {"", "unknown"}: + message = replace(message, provider_family=session_provider) ++ if message.source_session_id is None: ++ message = replace(message, source_session_id=session_id) + normalized.append(message) + + result_index: dict[str, deque[_ResultMatch]] = defaultdict(deque) +@@ -179,38 +282,68 @@ def build_semantic_transcript( + + consumed_results: set[tuple[str, int]] = set() + entries: list[SemanticTranscriptEntry] = [] ++ absence_run: list[TranscriptAbsenceReference] = [] ++ ++ def flush_absence() -> None: ++ if not absence_run: ++ return ++ entries.append( ++ SemanticTranscriptEntry(absence=TranscriptAbsence(references=tuple(absence_run))) ++ ) ++ absence_run.clear() ++ ++ def emit(entry: SemanticTranscriptEntry) -> None: ++ flush_absence() ++ entries.append(entry) ++ + if lineage is not None: +- entries.append(SemanticTranscriptEntry(card=_build_lineage_card(lineage))) ++ emit(SemanticTranscriptEntry(card=_build_lineage_card(lineage))) + + for message in normalized: ++ emitted_content_block = False + if not message.blocks: +- if message.text: +- entries.append(SemanticTranscriptEntry(prose=_prose_for_message(message, message.text))) +- continue +- +- emitted_text_block = False +- for block_index, block in enumerate(message.blocks): +- if block.type == BlockType.TOOL_USE.value: +- result = _take_result(result_index, block.tool_id) +- if result is not None: +- consumed_results.add((result.message.id, result.block_index)) +- entries.append( +- SemanticTranscriptEntry( +- card=_build_tool_card( +- session_id=session_id, +- message=message, +- block_index=block_index, +- block=block, +- result=result, ++ if message.message_type == "thinking": ++ if message.text and message.text.strip(): ++ emit(SemanticTranscriptEntry(prose=_content_for_message(message, message.text, thinking=True))) ++ else: ++ absence_run.append(_absence_reference(message, block_id=None)) ++ elif message.text: ++ emit(SemanticTranscriptEntry(prose=_content_for_message(message, message.text))) ++ else: ++ for block_index, block in enumerate(message.blocks): ++ if block.type == BlockType.TOOL_USE.value: ++ result = _take_result(result_index, block.tool_id) ++ if result is not None: ++ consumed_results.add((result.message.id, result.block_index)) ++ emit( ++ SemanticTranscriptEntry( ++ card=_build_tool_card( ++ session_id=session_id, ++ message=message, ++ block_index=block_index, ++ block=block, ++ result=result, ++ ) + ) + ) +- ) +- continue +- if block.type == BlockType.TOOL_RESULT.value: +- if (message.id, block_index) not in consumed_results: +- entries.append( ++ continue ++ if block.type == BlockType.TOOL_RESULT.value: ++ if (message.id, block_index) not in consumed_results: ++ emit( ++ SemanticTranscriptEntry( ++ card=_build_orphan_result_card( ++ session_id=session_id, ++ message=message, ++ block_index=block_index, ++ block=block, ++ ) ++ ) ++ ) ++ continue ++ if block.type in {BlockType.IMAGE.value, BlockType.DOCUMENT.value, "file"}: ++ emit( + SemanticTranscriptEntry( +- card=_build_orphan_result_card( ++ card=_build_block_attachment_card( + session_id=session_id, + message=message, + block_index=block_index, +@@ -218,40 +351,125 @@ def build_semantic_transcript( + ) + ) + ) +- continue +- if block.type in {BlockType.IMAGE.value, BlockType.DOCUMENT.value, "file"}: +- entries.append( +- SemanticTranscriptEntry( +- card=_build_attachment_card( +- session_id=session_id, +- message=message, +- block_index=block_index, +- block=block, ++ continue ++ if block.type in _THINKING_BLOCK_TYPES: ++ emitted_content_block = True ++ if block.text and block.text.strip(): ++ emit( ++ SemanticTranscriptEntry( ++ prose=_content_for_block( ++ message, ++ block, ++ content_kind=TranscriptContentKind.THINKING, ++ foldable=True, ++ ) ++ ) + ) +- ) +- ) +- continue +- if block.text: +- entries.append( +- SemanticTranscriptEntry( +- prose=TranscriptProse( +- message_id=message.id, +- role=message.role, +- message_type=message.message_type, +- text=block.text, +- block_id=block.block_id, +- block_type=block.type, ++ else: ++ absence_run.append(_absence_reference(message, block_id=block.block_id)) ++ continue ++ if block.type == BlockType.CODE.value: ++ emitted_content_block = True ++ if block.text is not None: ++ emit( ++ SemanticTranscriptEntry( ++ prose=_content_for_block( ++ message, ++ block, ++ content_kind=TranscriptContentKind.CODE, ++ language=block.language, ++ foldable=True, ++ ) ++ ) + ) ++ continue ++ if block.text: ++ emitted_content_block = True ++ emit(SemanticTranscriptEntry(prose=_content_for_block(message, block))) ++ ++ # Preserve message-level context/protocol/prose only when typed ++ # blocks did not already carry it. Aggregate tool text is not ++ # emitted a second time. ++ if ( ++ message.text ++ and not emitted_content_block ++ and message.message_type in {"message", "summary", "context", "protocol"} ++ and not any(block.type in {BlockType.TOOL_USE.value, BlockType.TOOL_RESULT.value} for block in message.blocks) ++ ): ++ emit(SemanticTranscriptEntry(prose=_content_for_message(message, message.text))) ++ ++ for attachment in message.attachments: ++ emit( ++ SemanticTranscriptEntry( ++ card=_build_message_attachment_card( ++ session_id=session_id, ++ message=message, ++ attachment=attachment, + ) + ) +- emitted_text_block = True ++ ) ++ ++ flush_absence() ++ scope: Literal["page", "complete_session"] = "page" if page_bounded else "complete_session" ++ return SemanticTranscript( ++ session_id=session_id, ++ entries=tuple(entries), ++ pairing_scope=scope, ++ absence_compaction_scope=scope, ++ lineage_complete=lineage.complete if lineage is not None else None, ++ lineage_truncation_reason=lineage.truncation_reason if lineage is not None else None, ++ ) + +- # Preserve legacy ordinary prose only when typed blocks did not already +- # carry it. Aggregate tool-use/result text is not emitted a second time. +- if message.text and not emitted_text_block and message.message_type == "message": +- entries.append(SemanticTranscriptEntry(prose=_prose_for_message(message, message.text))) + +- return SemanticTranscript(session_id=session_id, entries=tuple(entries)) ++def _absence_reference(message: RenderableMessage, *, block_id: str | None) -> TranscriptAbsenceReference: ++ return TranscriptAbsenceReference( ++ message_id=message.id, ++ block_id=block_id, ++ source_session_id=message.source_session_id, ++ role=message.role, ++ message_type=message.message_type, ++ material_origin=message.material_origin, ++ ) ++ ++ ++def _content_for_message(message: RenderableMessage, text: str, *, thinking: bool = False) -> TranscriptProse: ++ return TranscriptProse( ++ message_id=message.id, ++ role=message.role, ++ message_type=message.message_type, ++ text=text, ++ content_kind=TranscriptContentKind.THINKING if thinking else TranscriptContentKind.TEXT, ++ foldable=thinking, ++ source_session_id=message.source_session_id, ++ material_origin=message.material_origin, ++ timestamp=message.timestamp, ++ duration_ms=message.duration_ms, ++ ) ++ ++ ++def _content_for_block( ++ message: RenderableMessage, ++ block: RenderableBlock, ++ *, ++ content_kind: TranscriptContentKind = TranscriptContentKind.TEXT, ++ language: str | None = None, ++ foldable: bool = False, ++) -> TranscriptProse: ++ return TranscriptProse( ++ message_id=message.id, ++ role=message.role, ++ message_type=message.message_type, ++ text=block.text or "", ++ block_id=block.block_id, ++ block_type=block.type, ++ content_kind=content_kind, ++ language=language, ++ foldable=foldable, ++ source_session_id=message.source_session_id, ++ material_origin=message.material_origin, ++ timestamp=message.timestamp, ++ duration_ms=message.duration_ms, ++ ) + + + def _take_result(index: Mapping[str, deque[_ResultMatch]], tool_id: str | None) -> _ResultMatch | None: +@@ -263,12 +481,39 @@ def _take_result(index: Mapping[str, deque[_ResultMatch]], tool_id: str | None) + return queue.popleft() + + +-def _prose_for_message(message: RenderableMessage, text: str) -> TranscriptProse: +- return TranscriptProse( ++def _source_for_tool( ++ *, ++ session_id: str, ++ message: RenderableMessage, ++ block_index: int, ++ block: RenderableBlock, ++ result: _ResultMatch | None, ++) -> SemanticCardSource: ++ result_message = result.message if result is not None else None ++ result_block = result.block if result is not None else None ++ return SemanticCardSource( ++ session_id=session_id, ++ provider_family=message.provider_family, ++ source_session_id=message.source_session_id, + message_id=message.id, ++ block_id=block.block_id, ++ block_index=block_index, + role=message.role, + message_type=message.message_type, +- text=text, ++ material_origin=message.material_origin, ++ timestamp=message.timestamp, ++ duration_ms=message.duration_ms, ++ tool_name=block.tool_name, ++ tool_id=block.tool_id, ++ result_source_session_id=result_message.source_session_id if result_message is not None else None, ++ result_message_id=result_message.id if result_message is not None else None, ++ result_block_id=result_block.block_id if result_block is not None else None, ++ result_block_index=result.block_index if result is not None else None, ++ result_role=result_message.role if result_message is not None else None, ++ result_message_type=result_message.message_type if result_message is not None else None, ++ result_material_origin=result_message.material_origin if result_message is not None else None, ++ result_timestamp=result_message.timestamp if result_message is not None else None, ++ result_duration_ms=result_message.duration_ms if result_message is not None else None, + ) + + +@@ -286,17 +531,12 @@ def _build_tool_card( + tool_name=block.tool_name, + semantic_type=block.semantic_type, + ) +- source = SemanticCardSource( ++ source = _source_for_tool( + session_id=session_id, +- provider_family=message.provider_family, +- message_id=message.id, +- block_id=block.block_id, ++ message=message, + block_index=block_index, +- tool_name=block.tool_name, +- tool_id=block.tool_id, +- result_message_id=result.message.id if result is not None else None, +- result_block_id=result_block.block_id if result_block is not None else None, +- result_block_index=result.block_index if result is not None else None, ++ block=block, ++ result=result, + ) + outcome, outcome_caveats = _outcome_from_result(result_block) + common_caveats: list[str] = list(outcome_caveats) +@@ -305,13 +545,21 @@ def _build_tool_card( + if result_block is None: + common_caveats.append("no structurally paired tool result is present") + +- if kind is SemanticCardKind.SHELL: +- return _build_shell_card(block, result_block, source, outcome, tuple(common_caveats)) +- if kind is SemanticCardKind.FILE_EDIT: +- return _build_file_edit_card(block, result_block, source, outcome, tuple(common_caveats)) +- if kind is SemanticCardKind.TASK: +- return _build_task_card(block, result_block, source, outcome, tuple(common_caveats)) +- return _build_fallback_card(block, result_block, source, outcome, tuple(common_caveats)) ++ builders = { ++ SemanticCardKind.SHELL: _build_shell_card, ++ SemanticCardKind.FILE_READ: _build_file_read_card, ++ SemanticCardKind.FILE_EDIT: _build_file_edit_card, ++ SemanticCardKind.SEARCH: _build_search_card, ++ SemanticCardKind.WEB: _build_web_card, ++ SemanticCardKind.MCP: _build_mcp_card, ++ SemanticCardKind.TASK: _build_task_card, ++ } ++ builder = builders.get(kind, _build_fallback_card) ++ return builder(block, result_block, source, outcome, tuple(common_caveats)) ++ ++ ++def _base_tool_fields(block: RenderableBlock) -> list[SemanticCardField]: ++ return [SemanticCardField("tool", block.tool_name or "unknown")] + + + def _build_shell_card( +@@ -322,20 +570,49 @@ def _build_shell_card( + caveats: tuple[str, ...], + ) -> SemanticCard: + command = _first_scalar(block.tool_input, ("command", "cmd", "script")) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ fields = _base_tool_fields(block) + if command: + fields.append(SemanticCardField("command", command)) + previews = _result_previews(result, kind="output") +- return SemanticCard( ++ return _card( + kind=SemanticCardKind.SHELL, + title="Shell command", + summary=command, + source=source, + outcome=outcome, +- fields=tuple(fields), ++ fields=fields, + previews=previews, +- raw_evidence=_raw_evidence(block, result), +- caveats=_deduplicate(caveats + _preview_caveats(previews)), ++ block=block, ++ result=result, ++ caveats=caveats, ++ ) ++ ++ ++def _build_file_read_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++) -> SemanticCard: ++ path = _first_scalar(block.tool_input, ("file_path", "path", "file", "filename")) ++ fields = _base_tool_fields(block) ++ if path: ++ fields.append(SemanticCardField("path", path)) ++ _append_scalar_field(fields, block.tool_input, "offset", ("offset", "line_start", "start")) ++ _append_scalar_field(fields, block.tool_input, "limit", ("limit", "line_count", "count")) ++ previews = _result_previews(result, kind="content") ++ return _card( ++ kind=SemanticCardKind.FILE_READ, ++ title="File read", ++ summary=path, ++ source=source, ++ outcome=outcome, ++ fields=fields, ++ previews=previews, ++ block=block, ++ result=result, ++ caveats=caveats, + ) + + +@@ -355,26 +632,108 @@ def _build_file_edit_card( + else: + local_caveats.append("no exact diff could be constructed from the available tool input") + if result is not None and result.text: +- previews.append( +- _bounded_preview( +- result.text, +- kind="result", +- encoding_replacements=result.text_encoding_replacements, +- ) +- ) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ previews.append(_bounded_preview(result.text, kind="result", encoding_replacements=result.text_encoding_replacements)) ++ fields = _base_tool_fields(block) + if path: + fields.append(SemanticCardField("path", path)) +- return SemanticCard( ++ return _card( + kind=SemanticCardKind.FILE_EDIT, + title="File edit", + summary=path, + source=source, + outcome=outcome, +- fields=tuple(fields), ++ fields=fields, + previews=tuple(previews), +- raw_evidence=_raw_evidence(block, result), +- caveats=_deduplicate(tuple(local_caveats) + _preview_caveats(tuple(previews))), ++ block=block, ++ result=result, ++ caveats=tuple(local_caveats), ++ ) ++ ++ ++def _build_search_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++) -> SemanticCard: ++ query = _first_scalar(block.tool_input, ("query", "pattern", "search", "regex", "glob")) ++ path = _first_scalar(block.tool_input, ("path", "file_path", "directory", "cwd")) ++ fields = _base_tool_fields(block) ++ if query: ++ fields.append(SemanticCardField("query", query)) ++ if path: ++ fields.append(SemanticCardField("scope", path)) ++ previews = _result_previews(result, kind="matches") ++ return _card( ++ kind=SemanticCardKind.SEARCH, ++ title="Search", ++ summary=query or path, ++ source=source, ++ outcome=outcome, ++ fields=fields, ++ previews=previews, ++ block=block, ++ result=result, ++ caveats=caveats, ++ ) ++ ++ ++def _build_web_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++) -> SemanticCard: ++ url = _first_scalar(block.tool_input, ("url", "uri", "link")) ++ query = _first_scalar(block.tool_input, ("query", "search_query", "q")) ++ fields = _base_tool_fields(block) ++ if url: ++ fields.append(SemanticCardField("url", url)) ++ if query: ++ fields.append(SemanticCardField("query", query)) ++ previews = _result_previews(result, kind="response") ++ return _card( ++ kind=SemanticCardKind.WEB, ++ title="Web request", ++ summary=url or query, ++ source=source, ++ outcome=outcome, ++ fields=fields, ++ previews=previews, ++ block=block, ++ result=result, ++ caveats=caveats, ++ ) ++ ++ ++def _build_mcp_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++) -> SemanticCard: ++ parsed = parse_mcp_tool_name(block.tool_name) ++ if parsed is None: ++ return _build_fallback_card(block, result, source, outcome, caveats) ++ server, method = parsed ++ fields = _base_tool_fields(block) ++ fields.extend((SemanticCardField("server", server), SemanticCardField("method", method))) ++ input_preview = _input_preview(block) ++ previews = (() if input_preview is None else (input_preview,)) + _result_previews(result, kind="result") ++ return _card( ++ kind=SemanticCardKind.MCP, ++ title="MCP call", ++ summary=f"{server}.{method}", ++ source=source, ++ outcome=outcome, ++ fields=fields, ++ previews=previews, ++ block=block, ++ result=result, ++ caveats=caveats, + ) + + +@@ -385,24 +744,25 @@ def _build_task_card( + outcome: SemanticCardOutcome, + caveats: tuple[str, ...], + ) -> SemanticCard: +- prompt = _first_scalar(block.tool_input, ("prompt", "task", "instructions", "description")) ++ prompt = _first_scalar(block.tool_input, ("prompt", "task", "instructions", "description", "question")) + agent_type = _first_scalar(block.tool_input, ("subagent_type", "agent_type", "agent")) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ fields = _base_tool_fields(block) + if agent_type: + fields.append(SemanticCardField("agent", agent_type)) + if prompt: + fields.append(SemanticCardField("request", prompt)) + previews = _result_previews(result, kind="result") +- return SemanticCard( ++ return _card( + kind=SemanticCardKind.TASK, +- title="Task / subagent", ++ title="Task / delegation", + summary=prompt or agent_type, + source=source, + outcome=outcome, +- fields=tuple(fields), ++ fields=fields, + previews=previews, +- raw_evidence=_raw_evidence(block, result), +- caveats=_deduplicate(caveats + _preview_caveats(previews)), ++ block=block, ++ result=result, ++ caveats=caveats, + ) + + +@@ -415,19 +775,43 @@ def _build_fallback_card( + ) -> SemanticCard: + input_preview = _input_preview(block) + previews = (() if input_preview is None else (input_preview,)) + _result_previews(result, kind="result") +- return SemanticCard( ++ return _card( + kind=SemanticCardKind.FALLBACK, + title=f"Tool evidence · {block.tool_name or 'unknown'}", ++ summary=None, ++ source=source, ++ outcome=outcome, ++ fields=_base_tool_fields(block), ++ previews=previews, ++ block=block, ++ result=result, ++ caveats=("tool classification is unknown; raw evidence is shown without a guessed semantic card type",) + caveats, ++ ) ++ ++ ++def _card( ++ *, ++ kind: SemanticCardKind, ++ title: str, ++ summary: str | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ fields: Sequence[SemanticCardField], ++ previews: tuple[SemanticCardPreview, ...], ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ caveats: tuple[str, ...], ++) -> SemanticCard: ++ return SemanticCard( ++ kind=kind, ++ title=title, ++ summary=summary, + source=source, + outcome=outcome, +- fields=(SemanticCardField("tool", block.tool_name or "unknown"),), ++ fields=tuple(fields), + previews=previews, + raw_evidence=_raw_evidence(block, result), +- caveats=_deduplicate( +- ("tool classification is unknown; raw evidence is shown without a guessed semantic card type",) +- + caveats +- + _preview_caveats(previews) +- ), ++ caveats=_deduplicate(caveats + _preview_caveats(previews)), + ) + + +@@ -446,9 +830,15 @@ def _build_orphan_result_card( + source=SemanticCardSource( + session_id=session_id, + provider_family=message.provider_family, ++ result_source_session_id=message.source_session_id, + result_message_id=message.id, + result_block_id=block.block_id, + result_block_index=block_index, ++ result_role=message.role, ++ result_message_type=message.message_type, ++ result_material_origin=message.material_origin, ++ result_timestamp=message.timestamp, ++ result_duration_ms=message.duration_ms, + tool_id=block.tool_id, + ), + outcome=outcome, +@@ -462,7 +852,7 @@ def _build_orphan_result_card( + ) + + +-def _build_attachment_card( ++def _build_block_attachment_card( + *, + session_id: str, + message: RenderableMessage, +@@ -480,18 +870,62 @@ def _build_attachment_card( + kind=SemanticCardKind.ATTACHMENT, + title="Attachment", + summary=block.name or block.mime_type or block.type, +- source=SemanticCardSource( +- session_id=session_id, +- provider_family=message.provider_family, +- message_id=message.id, +- block_id=block.block_id, +- block_index=block_index, +- ), ++ source=_message_source(session_id, message, block_id=block.block_id, block_index=block_index), ++ fields=tuple(fields), ++ caveats=("attachment bytes are not embedded in the transcript card",), ++ ) ++ ++ ++def _build_message_attachment_card( ++ *, ++ session_id: str, ++ message: RenderableMessage, ++ attachment: RenderableAttachment, ++) -> SemanticCard: ++ fields = [SemanticCardField("attachment id", attachment.id)] ++ for label, value in ( ++ ("name", attachment.name), ++ ("media type", attachment.mime_type), ++ ("path", attachment.path), ++ ("url", attachment.source_url), ++ ("caption", attachment.caption), ++ ): ++ if value: ++ fields.append(SemanticCardField(label, value)) ++ if attachment.size_bytes is not None: ++ fields.append(SemanticCardField("bytes", str(attachment.size_bytes))) ++ return SemanticCard( ++ kind=SemanticCardKind.ATTACHMENT, ++ title="Attachment", ++ summary=attachment.name or attachment.mime_type or attachment.id, ++ source=_message_source(session_id, message), + fields=tuple(fields), + caveats=("attachment bytes are not embedded in the transcript card",), + ) + + ++def _message_source( ++ session_id: str, ++ message: RenderableMessage, ++ *, ++ block_id: str | None = None, ++ block_index: int | None = None, ++) -> SemanticCardSource: ++ return SemanticCardSource( ++ session_id=session_id, ++ provider_family=message.provider_family, ++ source_session_id=message.source_session_id, ++ message_id=message.id, ++ block_id=block_id, ++ block_index=block_index, ++ role=message.role, ++ message_type=message.message_type, ++ material_origin=message.material_origin, ++ timestamp=message.timestamp, ++ duration_ms=message.duration_ms, ++ ) ++ ++ + def _build_lineage_card(lineage: LineageDescriptor) -> SemanticCard: + fields = [ + SemanticCardField("session", f"session:{lineage.session_id}"), +@@ -502,6 +936,10 @@ def _build_lineage_card(lineage: LineageDescriptor) -> SemanticCard: + fields.append(SemanticCardField("parent", f"session:{lineage.parent_session_id}")) + elif lineage.parent_native_id: + fields.append(SemanticCardField("native parent", lineage.parent_native_id)) ++ if lineage.complete is not None: ++ fields.append(SemanticCardField("composed transcript", "complete" if lineage.complete else "truncated")) ++ if lineage.truncation_reason: ++ fields.append(SemanticCardField("truncation reason", lineage.truncation_reason)) + caveats: list[str] = [] + if not lineage.resolved: + caveats.append("the provider-native parent has not resolved to a stored session") +@@ -509,6 +947,10 @@ def _build_lineage_card(lineage: LineageDescriptor) -> SemanticCard: + caveats.append("the parent edge is structural but its narrower lineage relation is unknown") + if lineage.cycle_detected: + caveats.append("a cycle was detected in the session topology") ++ if lineage.complete is None: ++ caveats.append("lineage composition completeness was not supplied by this read surface") ++ elif not lineage.complete: ++ caveats.append("the composed transcript is incomplete") + return SemanticCard( + kind=SemanticCardKind.LINEAGE, + title=f"Lineage boundary · {lineage.relation}", +@@ -539,6 +981,17 @@ def _outcome_from_result(result: RenderableBlock | None) -> tuple[SemanticCardOu + ) + + ++def _append_scalar_field( ++ fields: list[SemanticCardField], ++ document: JSONDocument | None, ++ label: str, ++ keys: Iterable[str], ++) -> None: ++ value = _first_scalar(document, keys) ++ if value is not None: ++ fields.append(SemanticCardField(label, value)) ++ ++ + def _first_scalar(document: JSONDocument | None, keys: Iterable[str]) -> str | None: + if document is None: + return None +@@ -674,5 +1127,6 @@ __all__ = [ + "build_semantic_transcript", + "coerce_renderable_message", + "lineage_descriptor_from_topology", ++ "RenderableAttachment", + "RenderableMessage", + ] +diff --git a/polylogue/rendering/semantic_markdown.py b/polylogue/rendering/semantic_markdown.py +index 73303e928c85a9693802022b73bf7605a1980989..bfc83da5f22d9dde7511e71cf0dc602816eac35e 100644 +--- a/polylogue/rendering/semantic_markdown.py ++++ b/polylogue/rendering/semantic_markdown.py +@@ -1,4 +1,4 @@ +-"""Markdown rendering for pure semantic transcript cards.""" ++"""Markdown leaf renderer for the shared semantic transcript.""" + + from __future__ import annotations + +@@ -10,6 +10,8 @@ from polylogue.rendering.semantic_card_models import ( + SemanticCard, + SemanticCardPreview, + SemanticTranscript, ++ TranscriptAbsence, ++ TranscriptContentKind, + TranscriptProse, + ) + +@@ -18,11 +20,21 @@ def render_semantic_transcript_markdown(transcript: SemanticTranscript) -> str: + """Render an ordered semantic transcript without archive access.""" + + rendered: list[str] = [] ++ if transcript.pairing_scope == "page": ++ rendered.append( ++ "> Scope: tool pairing and empty-thinking compaction are bounded to this page; " ++ "matching evidence outside the page is not inferred." ++ ) ++ if transcript.lineage_complete is False: ++ reason = transcript.lineage_truncation_reason or "unknown" ++ rendered.append(f"> Lineage warning: composed transcript is incomplete ({reason}).") + for entry in transcript.entries: + if entry.card is not None: + rendered.append(render_semantic_card_markdown(entry.card)) ++ elif entry.absence is not None: ++ rendered.append(_render_absence(entry.absence)) + elif entry.prose is not None: +- rendered.append(_render_prose(entry.prose)) ++ rendered.append(_render_content(entry.prose)) + if not rendered: + return "" + return "\n\n---\n\n".join(rendered).rstrip() + "\n" +@@ -40,18 +52,26 @@ def render_semantic_card_markdown(card: SemanticCard) -> str: + parts.append(_render_inline_or_fenced(field.label, field.value)) + + source_bits = [f"session:{card.source.session_id}"] ++ if card.source.source_session_id: ++ source_bits.append(f"source-session:{card.source.source_session_id}") + if card.source.message_id: + source_bits.append(f"message:{card.source.message_id}") + if card.source.block_id: + source_bits.append(f"block:{card.source.block_id}") + elif card.source.block_index is not None: + source_bits.append(f"block-index:{card.source.block_index}") ++ if card.source.material_origin: ++ source_bits.append(f"authoredness:{card.source.material_origin}") ++ if card.source.result_source_session_id: ++ source_bits.append(f"result-source-session:{card.source.result_source_session_id}") + if card.source.result_message_id: + source_bits.append(f"result-message:{card.source.result_message_id}") + if card.source.result_block_id: + source_bits.append(f"result-block:{card.source.result_block_id}") + elif card.source.result_block_index is not None: + source_bits.append(f"result-block-index:{card.source.result_block_index}") ++ if card.source.result_material_origin: ++ source_bits.append(f"result-authoredness:{card.source.result_material_origin}") + parts.append("- evidence: " + ", ".join(_inline_code(value) for value in source_bits)) + + if card.outcome is not None: +@@ -68,20 +88,49 @@ def render_semantic_card_markdown(card: SemanticCard) -> str: + + + def render_semantic_cards_markdown(cards: Iterable[SemanticCard]) -> str: +- """Render cards without prose for server-side fallback surfaces.""" ++ """Render cards without content for server-side fallback surfaces.""" + + return "\n\n---\n\n".join(render_semantic_card_markdown(card) for card in cards) + + +-def _render_prose(prose: TranscriptProse) -> str: +- label = f"**{prose.role} · {prose.message_type}**" +- refs = [f"message:{prose.message_id}"] +- if prose.block_id: +- refs.append(f"block:{prose.block_id}") +- if prose.block_type: +- label += f" · {_inline_code(prose.block_type)}" ++def _render_content(content: TranscriptProse) -> str: ++ label = f"**{content.role} · {content.message_type}**" ++ refs = [f"message:{content.message_id}"] ++ if content.source_session_id: ++ refs.append(f"source-session:{content.source_session_id}") ++ if content.block_id: ++ refs.append(f"block:{content.block_id}") ++ if content.material_origin: ++ refs.append(f"authoredness:{content.material_origin}") ++ if content.block_type: ++ label += f" · {_inline_code(content.block_type)}" + rendered_refs = ", ".join(_inline_code(ref) for ref in refs) +- return f"{label} · {rendered_refs}\n\n{prose.text}" ++ header = f"{label} · {rendered_refs}" ++ if content.content_kind is TranscriptContentKind.CODE: ++ return f"{header}\n\n{_fenced(content.text, language=content.language or 'text')}" ++ if content.content_kind is TranscriptContentKind.THINKING: ++ body = content.text ++ return ( ++ f"{header}\n\n
\nThinking\n\n" ++ f"{body}\n\n
" ++ ) ++ return f"{header}\n\n{content.text}" ++ ++ ++def _render_absence(absence: TranscriptAbsence) -> str: ++ refs = [] ++ for reference in absence.references: ++ coordinate = f"message:{reference.message_id}" ++ if reference.block_id: ++ coordinate += f"/block:{reference.block_id}" ++ if reference.source_session_id: ++ coordinate += f"@source-session:{reference.source_session_id}" ++ refs.append(_inline_code(coordinate)) ++ noun = "entry" if absence.count == 1 else "entries" ++ return ( ++ f"> **Thinking unavailable** · {absence.count} contiguous typed {noun}; " ++ f"source carried no thinking content. Evidence: {', '.join(refs)}" ++ ) + + + def _status_label(card: SemanticCard) -> str | None: +@@ -107,6 +156,9 @@ def _render_preview(preview: SemanticCardPreview) -> str: + "input": "json", + "raw_input": "text", + "output": "text", ++ "content": "text", ++ "matches": "text", ++ "response": "text", + "result": "text", + "raw_result": "text", + }.get(preview.kind, "text") +@@ -131,11 +183,7 @@ def _longest_backtick_run(text: str) -> int: + + + def _inline_code(value: str) -> str: +- """Render a Markdown code span without corrupting values containing ticks.""" +- + fence = "`" * max(1, _longest_backtick_run(value) + 1) +- # CommonMark requires padding when content starts or ends with a backtick, +- # and padding is harmless for other embedded-tick values. + padding = " " if "`" in value or value.startswith(" ") or value.endswith(" ") else "" + return f"{fence}{padding}{value}{padding}{fence}" + +diff --git a/polylogue/storage/hydrators.py b/polylogue/storage/hydrators.py +index 64fceeaab449a1a6fb218d723f81e1ff97d6e670..fb93feb0667cba936418e075b341c929cfc63264 100644 +--- a/polylogue/storage/hydrators.py ++++ b/polylogue/storage/hydrators.py +@@ -106,6 +106,7 @@ def message_from_record( + "tool_id": b.tool_id, + "tool_input": _parse_json_blob(b.tool_input), + "media_type": media_type, ++ "language": block_metadata.get("language") if isinstance(block_metadata, dict) else None, + "metadata": block_metadata, + "semantic_type": str(b.semantic_type) if b.semantic_type is not None else None, + "tool_result_is_error": b.tool_result_is_error, +@@ -119,6 +120,7 @@ def message_from_record( + + return Message( + id=record.message_id, ++ source_session_id=str(record.session_id), + role=Role.normalize((record.role or "").strip() or "unknown"), + text=record.text, + timestamp=ts, +@@ -137,6 +139,7 @@ def message_from_record( + output_tokens=record.output_tokens, + cache_read_tokens=record.cache_read_tokens, + cache_write_tokens=record.cache_write_tokens, ++ duration_ms=record.duration_ms or 0, + model_name=record.model_name, + ) + +diff --git a/polylogue/storage/repository/archive/sessions.py b/polylogue/storage/repository/archive/sessions.py +index 4c702e3620645bd500dc6b610a544396e07aa786..01c545e7b9f146b76346c7e6b5acd51519082e33 100644 +--- a/polylogue/storage/repository/archive/sessions.py ++++ b/polylogue/storage/repository/archive/sessions.py +@@ -74,11 +74,19 @@ class RepositoryArchiveSessionMixin: + return None + resolved_session_id = str(conv_record.session_id) + +- msg_records, att_records, session_event_records = await asyncio.gather( ++ msg_records, session_event_records = await asyncio.gather( + self.queries.get_messages(resolved_session_id), +- self.queries.get_attachments(resolved_session_id), + self.queries.get_session_events(resolved_session_id), + ) ++ physical_session_ids = list( ++ dict.fromkeys([resolved_session_id, *(str(record.session_id) for record in msg_records)]) ++ ) ++ attachments_by_session = await self.queries.get_attachments_batch(physical_session_ids) ++ att_records = [ ++ attachment ++ for physical_session_id in physical_session_ids ++ for attachment in attachments_by_session.get(physical_session_id, []) ++ ] + tags_by_id = await self._fetch_tags_by_session([resolved_session_id]) + return session_from_records( + conv_record, +@@ -93,10 +101,14 @@ class RepositoryArchiveSessionMixin: + if not conv_record: + return None + +- msg_records, att_records = await asyncio.gather( +- self.queries.get_messages(session_id), +- self.queries.get_attachments(session_id), +- ) ++ msg_records = await self.queries.get_messages(session_id) ++ physical_session_ids = list(dict.fromkeys([session_id, *(str(record.session_id) for record in msg_records)])) ++ attachments_by_session = await self.queries.get_attachments_batch(physical_session_ids) ++ att_records = [ ++ attachment ++ for physical_session_id in physical_session_ids ++ for attachment in attachments_by_session.get(physical_session_id, []) ++ ] + return SessionRenderProjection( + session=conv_record, + messages=msg_records, +@@ -131,7 +143,21 @@ class RepositoryArchiveSessionMixin: + limit=limit, + offset=offset, + ) +- messages = [message_from_record(r, attachments=[], origin=origin) for r in records] ++ physical_session_ids = list(dict.fromkeys(str(record.session_id) for record in records)) ++ attachments_by_session = await self.queries.get_attachments_batch(physical_session_ids) ++ attachments_by_message: dict[str, list[AttachmentRecord]] = {} ++ for physical_session_id in physical_session_ids: ++ for attachment in attachments_by_session.get(physical_session_id, []): ++ if attachment.message_id is not None: ++ attachments_by_message.setdefault(str(attachment.message_id), []).append(attachment) ++ messages = [ ++ message_from_record( ++ record, ++ attachments=attachments_by_message.get(str(record.message_id), []), ++ origin=origin, ++ ) ++ for record in records ++ ] + return messages, total + + async def get_sessions_batch(self, ids: list[str]) -> list[SessionRecord]: +@@ -174,17 +200,40 @@ class RepositoryArchiveSessionMixin: + return [] + + async with self._backend.read_pool(size=2): +- msgs_by_id, atts_by_id, session_events_by_id = await asyncio.gather( ++ msgs_by_id, session_events_by_id = await asyncio.gather( + self.queries.get_messages_batch(present_ids), +- self.queries.get_attachments_batch(present_ids), + self.queries.get_session_events_batch(present_ids), + ) ++ physical_session_ids = list( ++ dict.fromkeys( ++ [ ++ *present_ids, ++ *( ++ str(record.session_id) ++ for records in msgs_by_id.values() ++ for record in records ++ ), ++ ] ++ ) ++ ) ++ atts_by_id = await self.queries.get_attachments_batch(physical_session_ids) + tags_by_id = await self._fetch_tags_by_session(present_ids) ++ ++ def attachments_for(session_id: str) -> list[AttachmentRecord]: ++ owner_ids = dict.fromkeys( ++ [session_id, *(str(record.session_id) for record in msgs_by_id.get(session_id, []))] ++ ) ++ return [ ++ attachment ++ for owner_id in owner_ids ++ for attachment in atts_by_id.get(owner_id, []) ++ ] ++ + return [ + session_from_records( + by_id[session_id], + msgs_by_id.get(session_id, []), +- atts_by_id.get(session_id, []), ++ attachments_for(session_id), + session_events_by_id.get(session_id, []), + tags=tags_by_id.get(session_id, ()), + ) +diff --git a/polylogue/storage/sqlite/archive_tiers/write.py b/polylogue/storage/sqlite/archive_tiers/write.py +index 46ca47b86ba3da0946e4b4c09e92c4e4c9365d9c..424af3a10147bd64bc5bea73ec6fab04b02f8425 100644 +--- a/polylogue/storage/sqlite/archive_tiers/write.py ++++ b/polylogue/storage/sqlite/archive_tiers/write.py +@@ -109,6 +109,7 @@ class ArchiveMessageRow: + is_active_path: bool + is_active_leaf: bool + blocks: tuple[ArchiveBlockRow, ...] ++ source_session_id: str | None = None + message_type: str = "message" + material_origin: str = "unknown" + word_count: int = 0 +@@ -894,6 +895,7 @@ def read_archive_session_envelope( + messages.append( + ArchiveMessageRow( + message_id=message["message_id"], ++ source_session_id=str(session["session_id"]), + native_id=message["native_id"], + role=message["role"], + position=message["position"], +diff --git a/polylogue/storage/sqlite/queries/mappers_archive.py b/polylogue/storage/sqlite/queries/mappers_archive.py +index 93256571521e6ce28279db93142533bb09763458..b94947cae970e513e113f827aaf840facce1a12f 100644 +--- a/polylogue/storage/sqlite/queries/mappers_archive.py ++++ b/polylogue/storage/sqlite/queries/mappers_archive.py +@@ -89,6 +89,7 @@ def _row_to_message(row: sqlite3.Row) -> MessageRecord: + output_tokens=_row_int(row, "output_tokens", 0) or 0, + cache_read_tokens=_row_int(row, "cache_read_tokens", 0) or 0, + cache_write_tokens=_row_int(row, "cache_write_tokens", 0) or 0, ++ duration_ms=_row_int(row, "duration_ms"), + model_name=_row_text(row, "model_name"), + message_type=MessageType.normalize(_row_text(row, "message_type") or "message"), + material_origin=MaterialOrigin.normalize(_row_text(row, "material_origin")), +diff --git a/polylogue/storage/sqlite/queries/message_query_reads.py b/polylogue/storage/sqlite/queries/message_query_reads.py +index 62655e0cf22249496a19e1e32289f6ddd114af8d..b719dd2dd63c982b0302fd07a2f02f2996d6d83c 100644 +--- a/polylogue/storage/sqlite/queries/message_query_reads.py ++++ b/polylogue/storage/sqlite/queries/message_query_reads.py +@@ -53,7 +53,8 @@ _MESSAGE_RECORD_SELECT = """ + m.input_tokens, + m.output_tokens, + m.cache_read_tokens, +- m.cache_write_tokens ++ m.cache_write_tokens, ++ m.duration_ms + """ + + +diff --git a/tests/data/semantic_cards/cases/attachment-card.json b/tests/data/semantic_cards/cases/attachment-card.json +index 54a668bbba70ec3f068cf6e69e55b5947002a983..bdfe0f3fde0f1dd4686182e18305e34f752343b8 100644 +--- a/tests/data/semantic_cards/cases/attachment-card.json ++++ b/tests/data/semantic_cards/cases/attachment-card.json +@@ -38,9 +38,13 @@ + "source": { + "session_id": "fixture:attachment-card", + "provider_family": "chatgpt", ++ "source_session_id": "fixture:attachment-card", + "message_id": "m-attachment", + "block_id": "b-image", +- "block_index": 0 ++ "block_index": 0, ++ "role": "user", ++ "message_type": "message", ++ "material_origin": "unknown" + }, + "fields": [ + { +@@ -62,5 +66,45 @@ + ], + "summary": "diagram.png" + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:attachment-card", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:attachment-card", ++ "message_id": "m-attachment", ++ "block_id": "b-image", ++ "block_index": 0, ++ "role": "user", ++ "message_type": "message", ++ "material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "name", ++ "value": "diagram.png" ++ }, ++ { ++ "label": "media type", ++ "value": "image/png" ++ }, ++ { ++ "label": "url", ++ "value": "blob:sha256:abc" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript card" ++ ], ++ "summary": "diagram.png" ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json b/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json +index 8f45488e639a01f30ca529cbaed3c498e075b0e5..87728a5a14c07f9d1c054e768978c64da1255418 100644 +--- a/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json ++++ b/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json +@@ -57,14 +57,22 @@ + "source": { + "session_id": "fixture:chatgpt-canmore-edit", + "provider_family": "chatgpt", ++ "source_session_id": "fixture:chatgpt-canmore-edit", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "canmore.update_textdoc", + "tool_id": "c1", ++ "result_source_session_id": "fixture:chatgpt-canmore-edit", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -122,5 +130,90 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_edit", ++ "title": "File edit", ++ "source": { ++ "session_id": "fixture:chatgpt-canmore-edit", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:chatgpt-canmore-edit", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "canmore.update_textdoc", ++ "tool_id": "c1", ++ "result_source_session_id": "fixture:chatgpt-canmore-edit", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "canmore.update_textdoc" ++ }, ++ { ++ "label": "path", ++ "value": "canvas.md" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "diff", ++ "text": "--- a/canvas.md\n+++ b/canvas.md\n@@ -1 +1 @@\n-draft\n\n+final\n", ++ "line_count": 6, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "updated canvas", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "canvas.md", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "path": "canvas.md", ++ "old_text": "draft\n", ++ "new_text": "final\n" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "updated canvas", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/chatgpt-web-fallback.json b/tests/data/semantic_cards/cases/chatgpt-web-fallback.json +deleted file mode 100644 +index 0e998139892364e858fe0d8d6d0803176cd43dd2..0000000000000000000000000000000000000000 +--- a/tests/data/semantic_cards/cases/chatgpt-web-fallback.json ++++ /dev/null +@@ -1,127 +0,0 @@ +-{ +- "case_id": "chatgpt-web-fallback", +- "claim": "Known but unsupported web tools remain raw fallback cards; the renderer does not invent a web card.", +- "oracle": "Hand-authored compact contract plus exact frozen card JSON.", +- "provider_family": "chatgpt", +- "session_id": "fixture:chatgpt-web-fallback", +- "messages": [ +- { +- "id": "m-use", +- "role": "assistant", +- "message_type": "tool_use", +- "text": null, +- "blocks": [ +- { +- "id": "b-use", +- "type": "tool_use", +- "tool_name": "web", +- "tool_id": "c1", +- "tool_input": { +- "search_query": [ +- { +- "q": "semantic transcript renderer" +- } +- ] +- } +- } +- ] +- }, +- { +- "id": "m-result", +- "role": "tool", +- "message_type": "tool_result", +- "text": null, +- "blocks": [ +- { +- "id": "b-result", +- "type": "tool_result", +- "tool_id": "c1", +- "text": "results returned", +- "tool_result_is_error": false +- } +- ] +- } +- ], +- "expected_contract": { +- "card_count": 1, +- "kinds": [ +- "fallback" +- ], +- "outcomes": [ +- "succeeded" +- ] +- }, +- "expected_cards": [ +- { +- "schema_version": "semantic-card.v1", +- "kind": "fallback", +- "title": "Tool evidence · web", +- "source": { +- "session_id": "fixture:chatgpt-web-fallback", +- "provider_family": "chatgpt", +- "message_id": "m-use", +- "block_id": "b-use", +- "block_index": 0, +- "tool_name": "web", +- "tool_id": "c1", +- "result_message_id": "m-result", +- "result_block_id": "b-result", +- "result_block_index": 0 +- }, +- "fields": [ +- { +- "label": "tool", +- "value": "web" +- } +- ], +- "previews": [ +- { +- "kind": "input", +- "text": "{\n \"search_query\": [\n {\n \"q\": \"semantic transcript renderer\"\n }\n ]\n}", +- "line_count": 7, +- "omitted_lines": 0, +- "omitted_characters": 0, +- "truncated": false, +- "strategy": "full", +- "encoding_replacements": 0 +- }, +- { +- "kind": "result", +- "text": "results returned", +- "line_count": 1, +- "omitted_lines": 0, +- "omitted_characters": 0, +- "truncated": false, +- "strategy": "full", +- "encoding_replacements": 0 +- } +- ], +- "caveats": [ +- "tool classification is unknown; raw evidence is shown without a guessed semantic card type" +- ], +- "outcome": { +- "state": "succeeded", +- "is_error": false +- }, +- "raw_evidence": { +- "tool_input": { +- "search_query": [ +- { +- "q": "semantic transcript renderer" +- } +- ] +- }, +- "result_preview": { +- "kind": "raw_result", +- "text": "results returned", +- "line_count": 1, +- "omitted_lines": 0, +- "omitted_characters": 0, +- "truncated": false, +- "strategy": "full", +- "encoding_replacements": 0 +- } +- } +- } +- ] +-} +diff --git a/tests/data/semantic_cards/cases/chatgpt-web.json b/tests/data/semantic_cards/cases/chatgpt-web.json +new file mode 100644 +index 0000000000000000000000000000000000000000..9e47dc756a88edb7b96cf95f0f2b8f4ee9646400 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/chatgpt-web.json +@@ -0,0 +1,195 @@ ++{ ++ "case_id": "chatgpt-web-fallback", ++ "claim": "Known but unsupported web tools remain raw fallback cards; the renderer does not invent a web card.", ++ "oracle": "Hand-authored compact contract plus exact frozen card JSON.", ++ "provider_family": "chatgpt", ++ "session_id": "fixture:chatgpt-web", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "text": null, ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "web", ++ "tool_id": "c1", ++ "tool_input": { ++ "search_query": [ ++ { ++ "q": "semantic transcript renderer" ++ } ++ ] ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "text": null, ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "c1", ++ "text": "results returned", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "web" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "web", ++ "title": "Web request", ++ "source": { ++ "session_id": "fixture:chatgpt-web", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:chatgpt-web", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "web", ++ "tool_id": "c1", ++ "result_source_session_id": "fixture:chatgpt-web", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "web" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "response", ++ "text": "results returned", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "search_query": [ ++ { ++ "q": "semantic transcript renderer" ++ } ++ ] ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "results returned", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "web", ++ "title": "Web request", ++ "source": { ++ "session_id": "fixture:chatgpt-web", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:chatgpt-web", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "web", ++ "tool_id": "c1", ++ "result_source_session_id": "fixture:chatgpt-web", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "web" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "response", ++ "text": "results returned", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "search_query": [ ++ { ++ "q": "semantic transcript renderer" ++ } ++ ] ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "results returned", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/claude-bash-explicit-success.json b/tests/data/semantic_cards/cases/claude-bash-explicit-success.json +index b76b9a1229b39ee0c5538d399eadff8a30450be6..7e64b43426fb294c0dd7f6e7677dc6a2d084c021 100644 +--- a/tests/data/semantic_cards/cases/claude-bash-explicit-success.json ++++ b/tests/data/semantic_cards/cases/claude-bash-explicit-success.json +@@ -56,14 +56,22 @@ + "source": { + "session_id": "fixture:claude-bash-explicit-success", + "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-bash-explicit-success", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "Bash", + "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-bash-explicit-success", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -110,5 +118,79 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:claude-bash-explicit-success", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-bash-explicit-success", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Bash", ++ "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-bash-explicit-success", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Bash" ++ }, ++ { ++ "label": "command", ++ "value": "pytest -q" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": "ERROR is a fixture filename; 2 passed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "pytest -q", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false, ++ "exit_code": 0 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "pytest -q" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "ERROR is a fixture filename; 2 passed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/claude-edit-diff.json b/tests/data/semantic_cards/cases/claude-edit-diff.json +index ea8d45b4e1fb3a6e4f30bca5f2c22478386826fe..2ae6ac905ef61902f6d2297f4f7bea86d7128f97 100644 +--- a/tests/data/semantic_cards/cases/claude-edit-diff.json ++++ b/tests/data/semantic_cards/cases/claude-edit-diff.json +@@ -57,14 +57,22 @@ + "source": { + "session_id": "fixture:claude-edit-diff", + "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-edit-diff", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "Edit", + "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-edit-diff", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -122,5 +130,90 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_edit", ++ "title": "File edit", ++ "source": { ++ "session_id": "fixture:claude-edit-diff", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-edit-diff", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Edit", ++ "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-edit-diff", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Edit" ++ }, ++ { ++ "label": "path", ++ "value": "src/app.py" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "diff", ++ "text": "--- a/src/app.py\n+++ b/src/app.py\n@@ -1 +1 @@\n-return 1\n\n+return 2\n", ++ "line_count": 6, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "updated src/app.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "src/app.py", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "file_path": "src/app.py", ++ "old_string": "return 1\n", ++ "new_string": "return 2\n" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "updated src/app.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/claude-file-read.json b/tests/data/semantic_cards/cases/claude-file-read.json +new file mode 100644 +index 0000000000000000000000000000000000000000..4bac72bc13acc99ded8ec1fe9b53ff71ce19b8c9 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/claude-file-read.json +@@ -0,0 +1,214 @@ ++{ ++ "provider_family": "claude-code", ++ "session_id": "fixture:claude-file-read", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "source_session_id": "fixture:claude-file-read", ++ "material_origin": "authored", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "Read", ++ "tool_id": "r1", ++ "tool_input": { ++ "file_path": "src/app.py", ++ "offset": 10, ++ "limit": 20 ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "source_session_id": "fixture:claude-file-read", ++ "material_origin": "tool_generated", ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "r1", ++ "text": "10: def main():\n11: return 0", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "file_read" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_read", ++ "title": "File read", ++ "source": { ++ "session_id": "fixture:claude-file-read", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-file-read", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "authored", ++ "tool_name": "Read", ++ "tool_id": "r1", ++ "result_source_session_id": "fixture:claude-file-read", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "tool_generated" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Read" ++ }, ++ { ++ "label": "path", ++ "value": "src/app.py" ++ }, ++ { ++ "label": "offset", ++ "value": "10" ++ }, ++ { ++ "label": "limit", ++ "value": "20" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "content", ++ "text": "10: def main():\n11: return 0", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "src/app.py", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "file_path": "src/app.py", ++ "offset": 10, ++ "limit": 20 ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "10: def main():\n11: return 0", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_read", ++ "title": "File read", ++ "source": { ++ "session_id": "fixture:claude-file-read", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-file-read", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "authored", ++ "tool_name": "Read", ++ "tool_id": "r1", ++ "result_source_session_id": "fixture:claude-file-read", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "tool_generated" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Read" ++ }, ++ { ++ "label": "path", ++ "value": "src/app.py" ++ }, ++ { ++ "label": "offset", ++ "value": "10" ++ }, ++ { ++ "label": "limit", ++ "value": "20" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "content", ++ "text": "10: def main():\n11: return 0", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "src/app.py", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "file_path": "src/app.py", ++ "offset": 10, ++ "limit": 20 ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "10: def main():\n11: return 0", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/claude-interleaved-subagents.json b/tests/data/semantic_cards/cases/claude-interleaved-subagents.json +index e5b8a6a57913d153fb81247c17566effce023b80..d6242b4a163a140aadfab8af732f31c2469cf4dd 100644 +--- a/tests/data/semantic_cards/cases/claude-interleaved-subagents.json ++++ b/tests/data/semantic_cards/cases/claude-interleaved-subagents.json +@@ -79,18 +79,26 @@ + { + "schema_version": "semantic-card.v1", + "kind": "task", +- "title": "Task / subagent", ++ "title": "Task / delegation", + "source": { + "session_id": "fixture:claude-interleaved-subagents", + "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-interleaved-subagents", + "message_id": "m-use", + "block_id": "b-use-1", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "Task", + "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-interleaved-subagents", + "result_message_id": "m-result-1", + "result_block_id": "b-result-1", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -144,18 +152,26 @@ + { + "schema_version": "semantic-card.v1", + "kind": "task", +- "title": "Task / subagent", ++ "title": "Task / delegation", + "source": { + "session_id": "fixture:claude-interleaved-subagents", + "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-interleaved-subagents", + "message_id": "m-use", + "block_id": "b-use-2", + "block_index": 1, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "Task", + "tool_id": "t2", ++ "result_source_session_id": "fixture:claude-interleaved-subagents", + "result_message_id": "m-result-2", + "result_block_id": "b-result-2", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -206,5 +222,159 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "task", ++ "title": "Task / delegation", ++ "source": { ++ "session_id": "fixture:claude-interleaved-subagents", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-interleaved-subagents", ++ "message_id": "m-use", ++ "block_id": "b-use-1", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Task", ++ "tool_id": "t1", ++ "result_source_session_id": "fixture:claude-interleaved-subagents", ++ "result_message_id": "m-result-1", ++ "result_block_id": "b-result-1", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Task" ++ }, ++ { ++ "label": "agent", ++ "value": "Explore" ++ }, ++ { ++ "label": "request", ++ "value": "Map parser seams." ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "result", ++ "text": "parser map complete", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "Map parser seams.", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "subagent_type": "Explore", ++ "prompt": "Map parser seams." ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "parser map complete", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ }, ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "task", ++ "title": "Task / delegation", ++ "source": { ++ "session_id": "fixture:claude-interleaved-subagents", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-interleaved-subagents", ++ "message_id": "m-use", ++ "block_id": "b-use-2", ++ "block_index": 1, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Task", ++ "tool_id": "t2", ++ "result_source_session_id": "fixture:claude-interleaved-subagents", ++ "result_message_id": "m-result-2", ++ "result_block_id": "b-result-2", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Task" ++ }, ++ { ++ "label": "agent", ++ "value": "Test" ++ }, ++ { ++ "label": "request", ++ "value": "Design hostile fixtures." ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "result", ++ "text": "fixture design complete", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "Design hostile fixtures.", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "subagent_type": "Test", ++ "prompt": "Design hostile fixtures." ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "fixture design complete", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/claude-search.json b/tests/data/semantic_cards/cases/claude-search.json +new file mode 100644 +index 0000000000000000000000000000000000000000..39a23e82415cfb96e3e9c7549911bc11d7fb32c0 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/claude-search.json +@@ -0,0 +1,199 @@ ++{ ++ "provider_family": "claude-code", ++ "session_id": "fixture:claude-search", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "Grep", ++ "tool_id": "s1", ++ "tool_input": { ++ "pattern": "build_semantic_transcript", ++ "path": "polylogue/" ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "s1", ++ "text": "polylogue/rendering/semantic_cards.py:247", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "search" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "search", ++ "title": "Search", ++ "source": { ++ "session_id": "fixture:claude-search", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-search", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Grep", ++ "tool_id": "s1", ++ "result_source_session_id": "fixture:claude-search", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Grep" ++ }, ++ { ++ "label": "query", ++ "value": "build_semantic_transcript" ++ }, ++ { ++ "label": "scope", ++ "value": "polylogue/" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "matches", ++ "text": "polylogue/rendering/semantic_cards.py:247", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "build_semantic_transcript", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "pattern": "build_semantic_transcript", ++ "path": "polylogue/" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "polylogue/rendering/semantic_cards.py:247", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "search", ++ "title": "Search", ++ "source": { ++ "session_id": "fixture:claude-search", ++ "provider_family": "claude-code", ++ "source_session_id": "fixture:claude-search", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "Grep", ++ "tool_id": "s1", ++ "result_source_session_id": "fixture:claude-search", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Grep" ++ }, ++ { ++ "label": "query", ++ "value": "build_semantic_transcript" ++ }, ++ { ++ "label": "scope", ++ "value": "polylogue/" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "matches", ++ "text": "polylogue/rendering/semantic_cards.py:247", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "build_semantic_transcript", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "pattern": "build_semantic_transcript", ++ "path": "polylogue/" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "polylogue/rendering/semantic_cards.py:247", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json b/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json +index cba6e0b385b81438245cbff89f6673cf00b98ef8..4c7b51a2cd29243aba17969fe7f0d00ae8201ff3 100644 +--- a/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json ++++ b/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json +@@ -41,9 +41,13 @@ + "source": { + "session_id": "fixture:codex-apply-patch-missing-result", + "provider_family": "codex", ++ "source_session_id": "fixture:codex-apply-patch-missing-result", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "apply_patch", + "tool_id": "call-1" + }, +@@ -83,5 +87,63 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_edit", ++ "title": "File edit", ++ "source": { ++ "session_id": "fixture:codex-apply-patch-missing-result", ++ "provider_family": "codex", ++ "source_session_id": "fixture:codex-apply-patch-missing-result", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "apply_patch", ++ "tool_id": "call-1" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "apply_patch" ++ }, ++ { ++ "label": "path", ++ "value": "src/core.py" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "diff", ++ "text": "--- a/src/core.py\n+++ b/src/core.py\n@@\n-old\n+new", ++ "line_count": 5, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "no structurally paired tool result is present" ++ ], ++ "summary": "src/core.py", ++ "outcome": { ++ "state": "unknown" ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "path": "src/core.py", ++ "patch": "--- a/src/core.py\n+++ b/src/core.py\n@@\n-old\n+new" ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/codex-conflicting-outcome.json b/tests/data/semantic_cards/cases/codex-conflicting-outcome.json +index d53334708b99e6a32dde79373bfe69ed10b6eb19..f4033b060be0d809e75657c25a867b7649a8af84 100644 +--- a/tests/data/semantic_cards/cases/codex-conflicting-outcome.json ++++ b/tests/data/semantic_cards/cases/codex-conflicting-outcome.json +@@ -56,14 +56,22 @@ + "source": { + "session_id": "fixture:codex-conflicting-outcome", + "provider_family": "codex", ++ "source_session_id": "fixture:codex-conflicting-outcome", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "exec_command", + "tool_id": "call-1", ++ "result_source_session_id": "fixture:codex-conflicting-outcome", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -112,5 +120,81 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:codex-conflicting-outcome", ++ "provider_family": "codex", ++ "source_session_id": "fixture:codex-conflicting-outcome", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "exec_command", ++ "tool_id": "call-1", ++ "result_source_session_id": "fixture:codex-conflicting-outcome", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "exec_command" ++ }, ++ { ++ "label": "command", ++ "value": "python check.py" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": "completed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "source outcome fields disagree; non-zero exit code is treated as failure" ++ ], ++ "summary": "python check.py", ++ "outcome": { ++ "state": "failed", ++ "is_error": false, ++ "exit_code": 2 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "python check.py" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "completed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json b/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json +index 5c1a485f8f5e5e16883bd0e78c6d86e531bc6b80..887e767bf63aa68aae1d71728b604bd1c27de635 100644 +--- a/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json ++++ b/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json +@@ -58,14 +58,22 @@ + "source": { + "session_id": "fixture:codex-exec-failure-ten-thousand-lines", + "provider_family": "codex", ++ "source_session_id": "fixture:codex-exec-failure-ten-thousand-lines", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "exec_command", + "tool_id": "call-1", ++ "result_source_session_id": "fixture:codex-exec-failure-ten-thousand-lines", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -114,5 +122,81 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:codex-exec-failure-ten-thousand-lines", ++ "provider_family": "codex", ++ "source_session_id": "fixture:codex-exec-failure-ten-thousand-lines", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "exec_command", ++ "tool_id": "call-1", ++ "result_source_session_id": "fixture:codex-exec-failure-ten-thousand-lines", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "exec_command" ++ }, ++ { ++ "label": "command", ++ "value": "pytest tests/huge.py -q" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": "line 00001: deterministic shell output\nline 00002: deterministic shell output\nline 00003: deterministic shell output\nline 00004: deterministic shell output\nline 00005: deterministic shell output\nline 00006: deterministic shell output\nline 00007: deterministic shell output\nline 00008: deterministic shell output\nline 00009: deterministic shell output\nline 00010: deterministic shell output\nline 00011: deterministic shell output\nline 00012: deterministic shell output\nline 00013: deterministic shell output\nline 00014: deterministic shell output\nline 00015: deterministic shell output\nline 00016: deterministic shell output\nline 00017: deterministic shell output\nline 00018: deterministic shell output\nline 00019: deterministic shell output\nline 00020: deterministic shell output\nline 00021: deterministic shell output\nline 00022: deterministic shell output\nline 00023: deterministic shell output\nline 00024: deterministic shell output\nline 00025: deterministic shell output\nline 00026: deterministic shell output\nline 00027: deterministic shell output\nline 00028: deterministic shell output\nline 00029: deterministic shell output\nline 00030: deterministic shell output\nline 00031: deterministic shell output\nline 00032: deterministic shell output\nline 00033: deterministic shell output\nline 00034: deterministic shell output\nline 00035: deterministic shell output\nline 00036: deterministic shell output\nline 00037: deterministic shell output\nline 00038: deterministic shell output\nline 00039: deterministic shell output\nline 00040: deterministic shell output\nline 00041: deterministic shell output\nline 00042: deterministic shell output\nline 00043: deterministic shell output\nline 00044: deterministic shell output\nline 00045: deterministic shell output\nline 00046: deterministic shell output\nline 00047: deterministic shell output\nline 00048: deterministic shell output\nline 09985: deterministic shell output\nline 09986: deterministic shell output\nline 09987: deterministic shell output\nline 09988: deterministic shell output\nline 09989: deterministic shell output\nline 09990: deterministic shell output\nline 09991: deterministic shell output\nline 09992: deterministic shell output\nline 09993: deterministic shell output\nline 09994: deterministic shell output\nline 09995: deterministic shell output\nline 09996: deterministic shell output\nline 09997: deterministic shell output\nline 09998: deterministic shell output\nline 09999: deterministic shell output\nline 10000: deterministic shell output", ++ "line_count": 10000, ++ "omitted_lines": 9936, ++ "omitted_characters": 0, ++ "truncated": true, ++ "strategy": "head_tail", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "9936 output lines are omitted from the bounded preview" ++ ], ++ "summary": "pytest tests/huge.py -q", ++ "outcome": { ++ "state": "failed", ++ "is_error": true, ++ "exit_code": 4 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "cmd": "pytest tests/huge.py -q" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "line 00001: deterministic shell output\nline 00002: deterministic shell output\nline 00003: deterministic shell output\nline 00004: deterministic shell output\nline 00005: deterministic shell output\nline 00006: deterministic shell output\nline 00007: deterministic shell output\nline 00008: deterministic shell output\nline 00009: deterministic shell output\nline 00010: deterministic shell output\nline 00011: deterministic shell output\nline 00012: deterministic shell output\nline 00013: deterministic shell output\nline 00014: deterministic shell output\nline 00015: deterministic shell output\nline 00016: deterministic shell output\nline 00017: deterministic shell output\nline 00018: deterministic shell output\nline 00019: deterministic shell output\nline 00020: deterministic shell output\nline 00021: deterministic shell output\nline 00022: deterministic shell output\nline 00023: deterministic shell output\nline 00024: deterministic shell output\nline 00025: deterministic shell output\nline 00026: deterministic shell output\nline 00027: deterministic shell output\nline 00028: deterministic shell output\nline 00029: deterministic shell output\nline 00030: deterministic shell output\nline 00031: deterministic shell output\nline 00032: deterministic shell output\nline 00033: deterministic shell output\nline 00034: deterministic shell output\nline 00035: deterministic shell output\nline 00036: deterministic shell output\nline 00037: deterministic shell output\nline 00038: deterministic shell output\nline 00039: deterministic shell output\nline 00040: deterministic shell output\nline 00041: deterministic shell output\nline 00042: deterministic shell output\nline 00043: deterministic shell output\nline 00044: deterministic shell output\nline 00045: deterministic shell output\nline 00046: deterministic shell output\nline 00047: deterministic shell output\nline 00048: deterministic shell output\nline 09985: deterministic shell output\nline 09986: deterministic shell output\nline 09987: deterministic shell output\nline 09988: deterministic shell output\nline 09989: deterministic shell output\nline 09990: deterministic shell output\nline 09991: deterministic shell output\nline 09992: deterministic shell output\nline 09993: deterministic shell output\nline 09994: deterministic shell output\nline 09995: deterministic shell output\nline 09996: deterministic shell output\nline 09997: deterministic shell output\nline 09998: deterministic shell output\nline 09999: deterministic shell output\nline 10000: deterministic shell output", ++ "line_count": 10000, ++ "omitted_lines": 9936, ++ "omitted_characters": 0, ++ "truncated": true, ++ "strategy": "head_tail", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/codex-mcp.json b/tests/data/semantic_cards/cases/codex-mcp.json +new file mode 100644 +index 0000000000000000000000000000000000000000..1b222c8d1093ddc393b2f7ac890c1dc924f04940 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/codex-mcp.json +@@ -0,0 +1,216 @@ ++{ ++ "provider_family": "codex", ++ "session_id": "fixture:codex-mcp", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "mcp__filesystem__read_file", ++ "tool_id": "mcp1", ++ "tool_input": { ++ "path": "/workspace/README.md" ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "mcp1", ++ "text": "# Project", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "mcp" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "mcp", ++ "title": "MCP call", ++ "source": { ++ "session_id": "fixture:codex-mcp", ++ "provider_family": "codex", ++ "source_session_id": "fixture:codex-mcp", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "mcp__filesystem__read_file", ++ "tool_id": "mcp1", ++ "result_source_session_id": "fixture:codex-mcp", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "mcp__filesystem__read_file" ++ }, ++ { ++ "label": "server", ++ "value": "filesystem" ++ }, ++ { ++ "label": "method", ++ "value": "read_file" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "input", ++ "text": "{\n \"path\": \"/workspace/README.md\"\n}", ++ "line_count": 3, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "# Project", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "filesystem.read_file", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "path": "/workspace/README.md" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "# Project", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "mcp", ++ "title": "MCP call", ++ "source": { ++ "session_id": "fixture:codex-mcp", ++ "provider_family": "codex", ++ "source_session_id": "fixture:codex-mcp", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "mcp__filesystem__read_file", ++ "tool_id": "mcp1", ++ "result_source_session_id": "fixture:codex-mcp", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "mcp__filesystem__read_file" ++ }, ++ { ++ "label": "server", ++ "value": "filesystem" ++ }, ++ { ++ "label": "method", ++ "value": "read_file" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "input", ++ "text": "{\n \"path\": \"/workspace/README.md\"\n}", ++ "line_count": 3, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "# Project", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "filesystem.read_file", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "path": "/workspace/README.md" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "# Project", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json b/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json +index fda30ebabfd17a8f5131d697c21b3d55c71fbc29..005b17f238c40f299886cabee5c989f215bf3992 100644 +--- a/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json ++++ b/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json +@@ -55,14 +55,22 @@ + "source": { + "session_id": "fixture:gemini-shell-null-outcome", + "provider_family": "gemini-cli", ++ "source_session_id": "fixture:gemini-shell-null-outcome", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "run_shell_command", + "tool_id": "g1", ++ "result_source_session_id": "fixture:gemini-shell-null-outcome", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -109,5 +117,79 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:gemini-shell-null-outcome", ++ "provider_family": "gemini-cli", ++ "source_session_id": "fixture:gemini-shell-null-outcome", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "run_shell_command", ++ "tool_id": "g1", ++ "result_source_session_id": "fixture:gemini-shell-null-outcome", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "run_shell_command" ++ }, ++ { ++ "label": "command", ++ "value": "make verify" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": "SUCCESS: command completed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "tool result exists but carries no structural success/failure outcome" ++ ], ++ "summary": "make verify", ++ "outcome": { ++ "state": "unknown" ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "make verify" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "SUCCESS: command completed", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/gemini-write-file.json b/tests/data/semantic_cards/cases/gemini-write-file.json +index 131ce87c2e84b88839f55e69af33ffbd054dfdb0..d9c312867394946bff7407dabf0bdd7aa9b39315 100644 +--- a/tests/data/semantic_cards/cases/gemini-write-file.json ++++ b/tests/data/semantic_cards/cases/gemini-write-file.json +@@ -57,14 +57,22 @@ + "source": { + "session_id": "fixture:gemini-write-file", + "provider_family": "gemini-cli", ++ "source_session_id": "fixture:gemini-write-file", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "write_file", + "tool_id": "g1", ++ "result_source_session_id": "fixture:gemini-write-file", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -121,5 +129,89 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_edit", ++ "title": "File edit", ++ "source": { ++ "session_id": "fixture:gemini-write-file", ++ "provider_family": "gemini-cli", ++ "source_session_id": "fixture:gemini-write-file", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "write_file", ++ "tool_id": "g1", ++ "result_source_session_id": "fixture:gemini-write-file", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "write_file" ++ }, ++ { ++ "label": "path", ++ "value": "notes.txt" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "diff", ++ "text": "--- a/notes.txt\n+++ b/notes.txt\n@@ -0,0 +1 @@\n+new contents\n", ++ "line_count": 4, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "written", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "notes.txt", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "file_path": "notes.txt", ++ "content": "new contents\n" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "written", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json b/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json +index 9554605347a459f107ece3e10272b24dedf1ab68..4bc06d8166cb6f0a4e616c526e9c00265b1a41a5 100644 +--- a/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json ++++ b/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json +@@ -58,14 +58,22 @@ + "source": { + "session_id": "fixture:hermes-shell-non-utf8", + "provider_family": "hermes", ++ "source_session_id": "fixture:hermes-shell-non-utf8", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "shell", + "tool_id": "h1", ++ "result_source_session_id": "fixture:hermes-shell-non-utf8", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [ + { +@@ -114,5 +122,81 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:hermes-shell-non-utf8", ++ "provider_family": "hermes", ++ "source_session_id": "fixture:hermes-shell-non-utf8", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "shell", ++ "tool_id": "h1", ++ "result_source_session_id": "fixture:hermes-shell-non-utf8", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "shell" ++ }, ++ { ++ "label": "command", ++ "value": "cat binary.log" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": "ok�done\n", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 1 ++ } ++ ], ++ "caveats": [ ++ "1 invalid UTF-8 sequence(s) were rendered with replacement characters" ++ ], ++ "summary": "cat binary.log", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false, ++ "exit_code": 0 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "cat binary.log" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "ok�done\n", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 1 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json b/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json +index 19f0ab6bcd430c2a3ae64e54ffba6a9a349dc426..80c6ab2ca22b1fba97896a69eb3473dccb3d8396 100644 +--- a/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json ++++ b/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json +@@ -38,9 +38,13 @@ + "source": { + "session_id": "fixture:hermes-truncated-unknown-input", + "provider_family": "hermes", ++ "source_session_id": "fixture:hermes-truncated-unknown-input", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", + "tool_name": "mystery_tool", + "tool_id": "h1" + }, +@@ -74,5 +78,57 @@ + "tool_input_raw": "{\"path\":\"/tmp/x\"," + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "fallback", ++ "title": "Tool evidence · mystery_tool", ++ "source": { ++ "session_id": "fixture:hermes-truncated-unknown-input", ++ "provider_family": "hermes", ++ "source_session_id": "fixture:hermes-truncated-unknown-input", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "role": "assistant", ++ "message_type": "tool_use", ++ "material_origin": "unknown", ++ "tool_name": "mystery_tool", ++ "tool_id": "h1" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "mystery_tool" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "raw_input", ++ "text": "{\"path\":\"/tmp/x\",", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "tool classification is unknown; raw evidence is shown without a guessed semantic card type", ++ "tool input was retained as raw text because it was not valid JSON", ++ "no structurally paired tool result is present" ++ ], ++ "outcome": { ++ "state": "unknown" ++ }, ++ "raw_evidence": { ++ "tool_input_raw": "{\"path\":\"/tmp/x\"," ++ } ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/lineage-unknown-boundary.json b/tests/data/semantic_cards/cases/lineage-unknown-boundary.json +index 8ac1d8da85a19a1b11ffeded551a1b2f60132639..ea637532f23f7117a1f303d110ba9808b9023aee 100644 +--- a/tests/data/semantic_cards/cases/lineage-unknown-boundary.json ++++ b/tests/data/semantic_cards/cases/lineage-unknown-boundary.json +@@ -65,9 +65,63 @@ + ], + "previews": [], + "caveats": [ +- "the parent edge is structural but its narrower lineage relation is unknown" ++ "the parent edge is structural but its narrower lineage relation is unknown", ++ "lineage composition completeness was not supplied by this read surface" + ], + "summary": "session:fixture:lineage-unknown-boundary" + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "lineage", ++ "title": "Lineage boundary · unknown", ++ "source": { ++ "session_id": "fixture:lineage-unknown-boundary", ++ "provider_family": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "session", ++ "value": "session:fixture:lineage-unknown-boundary" ++ }, ++ { ++ "label": "root", ++ "value": "session:fixture:root" ++ }, ++ { ++ "label": "relation", ++ "value": "unknown" ++ }, ++ { ++ "label": "parent", ++ "value": "session:fixture:parent" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "the parent edge is structural but its narrower lineage relation is unknown", ++ "lineage composition completeness was not supplied by this read surface" ++ ], ++ "summary": "session:fixture:lineage-unknown-boundary" ++ } ++ }, ++ { ++ "entry_type": "content", ++ "content": { ++ "message_id": "m-prose", ++ "role": "user", ++ "message_type": "message", ++ "text": "Continue from the inherited context.", ++ "block_id": "b-prose", ++ "block_type": "text", ++ "content_kind": "text", ++ "foldable": false, ++ "source_session_id": "fixture:lineage-unknown-boundary", ++ "material_origin": "unknown" ++ } ++ } + ] + } +diff --git a/tests/data/semantic_cards/cases/message-attachment.json b/tests/data/semantic_cards/cases/message-attachment.json +new file mode 100644 +index 0000000000000000000000000000000000000000..30e3ac536b7ed6276e877b4d599937414db8b19f +--- /dev/null ++++ b/tests/data/semantic_cards/cases/message-attachment.json +@@ -0,0 +1,139 @@ ++{ ++ "provider_family": "chatgpt", ++ "session_id": "fixture:message-attachment", ++ "messages": [ ++ { ++ "id": "m-attachment", ++ "role": "user", ++ "message_type": "message", ++ "source_session_id": "fixture:message-attachment", ++ "material_origin": "authored", ++ "attachments": [ ++ { ++ "id": "att-1", ++ "name": "evidence.csv", ++ "mime_type": "text/csv", ++ "size_bytes": 1234, ++ "path": "uploads/evidence.csv", ++ "source_url": "blob:sha256:def", ++ "caption": "Benchmark evidence" ++ } ++ ], ++ "blocks": [] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "attachment" ++ ], ++ "outcomes": [ ++ null ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:message-attachment", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:message-attachment", ++ "message_id": "m-attachment", ++ "role": "user", ++ "message_type": "message", ++ "material_origin": "authored" ++ }, ++ "fields": [ ++ { ++ "label": "attachment id", ++ "value": "att-1" ++ }, ++ { ++ "label": "name", ++ "value": "evidence.csv" ++ }, ++ { ++ "label": "media type", ++ "value": "text/csv" ++ }, ++ { ++ "label": "path", ++ "value": "uploads/evidence.csv" ++ }, ++ { ++ "label": "url", ++ "value": "blob:sha256:def" ++ }, ++ { ++ "label": "caption", ++ "value": "Benchmark evidence" ++ }, ++ { ++ "label": "bytes", ++ "value": "1234" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript card" ++ ], ++ "summary": "evidence.csv" ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:message-attachment", ++ "provider_family": "chatgpt", ++ "source_session_id": "fixture:message-attachment", ++ "message_id": "m-attachment", ++ "role": "user", ++ "message_type": "message", ++ "material_origin": "authored" ++ }, ++ "fields": [ ++ { ++ "label": "attachment id", ++ "value": "att-1" ++ }, ++ { ++ "label": "name", ++ "value": "evidence.csv" ++ }, ++ { ++ "label": "media type", ++ "value": "text/csv" ++ }, ++ { ++ "label": "path", ++ "value": "uploads/evidence.csv" ++ }, ++ { ++ "label": "url", ++ "value": "blob:sha256:def" ++ }, ++ { ++ "label": "caption", ++ "value": "Benchmark evidence" ++ }, ++ { ++ "label": "bytes", ++ "value": "1234" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript card" ++ ], ++ "summary": "evidence.csv" ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/mixed-content-thinking-code.json b/tests/data/semantic_cards/cases/mixed-content-thinking-code.json +new file mode 100644 +index 0000000000000000000000000000000000000000..0e282b9fc283c5f27642531f04e41ae3e756b368 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/mixed-content-thinking-code.json +@@ -0,0 +1,279 @@ ++{ ++ "provider_family": "codex", ++ "session_id": "fixture:child", ++ "messages": [ ++ { ++ "id": "m-context", ++ "role": "system", ++ "message_type": "context", ++ "source_session_id": "fixture:root", ++ "material_origin": "synthetic", ++ "timestamp": "2026-07-17T04:00:00+00:00", ++ "duration_ms": 5, ++ "blocks": [ ++ { ++ "id": "b-context", ++ "type": "text", ++ "text": "Inherited execution context." ++ } ++ ] ++ }, ++ { ++ "id": "m-thinking-empty", ++ "role": "assistant", ++ "message_type": "thinking", ++ "source_session_id": "fixture:root", ++ "material_origin": "authored", ++ "text": "", ++ "blocks": [] ++ }, ++ { ++ "id": "m-mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "source_session_id": "fixture:child", ++ "material_origin": "authored", ++ "blocks": [ ++ { ++ "id": "b-thinking-empty", ++ "type": "thinking", ++ "text": "" ++ }, ++ { ++ "id": "b-reasoning-empty", ++ "type": "reasoning", ++ "text": " " ++ }, ++ { ++ "id": "b-thinking", ++ "type": "thinking", ++ "text": "Use the typed evidence stream." ++ }, ++ { ++ "id": "b-code", ++ "type": "code", ++ "text": "print(\"semantic\")\n", ++ "language": "python" ++ } ++ ], ++ "attachments": [ ++ { ++ "id": "att-mixed", ++ "name": "trace.json", ++ "mime_type": "application/json", ++ "size_bytes": 42 ++ } ++ ] ++ }, ++ { ++ "id": "m-protocol", ++ "role": "system", ++ "message_type": "protocol", ++ "source_session_id": "fixture:child", ++ "material_origin": "synthetic", ++ "text": "Protocol boundary retained.", ++ "blocks": [] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "attachment" ++ ], ++ "outcomes": [ ++ null ++ ], ++ "entry_types": [ ++ "content", ++ "absence", ++ "content", ++ "content", ++ "card", ++ "content" ++ ], ++ "content_kinds": [ ++ "text", ++ "thinking", ++ "code", ++ "text" ++ ], ++ "absence_counts": [ ++ 3 ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:child", ++ "provider_family": "codex", ++ "source_session_id": "fixture:child", ++ "message_id": "m-mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "material_origin": "authored" ++ }, ++ "fields": [ ++ { ++ "label": "attachment id", ++ "value": "att-mixed" ++ }, ++ { ++ "label": "name", ++ "value": "trace.json" ++ }, ++ { ++ "label": "media type", ++ "value": "application/json" ++ }, ++ { ++ "label": "bytes", ++ "value": "42" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript card" ++ ], ++ "summary": "trace.json" ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "content", ++ "content": { ++ "message_id": "m-context", ++ "role": "system", ++ "message_type": "context", ++ "text": "Inherited execution context.", ++ "block_id": "b-context", ++ "block_type": "text", ++ "content_kind": "text", ++ "foldable": false, ++ "source_session_id": "fixture:root", ++ "material_origin": "synthetic", ++ "timestamp": "2026-07-17T04:00:00+00:00", ++ "duration_ms": 5 ++ } ++ }, ++ { ++ "entry_type": "absence", ++ "absence": { ++ "absence_kind": "thinking_unavailable", ++ "presentation": "compact", ++ "count": 3, ++ "references": [ ++ { ++ "message_id": "m-thinking-empty", ++ "source_session_id": "fixture:root", ++ "role": "assistant", ++ "message_type": "thinking", ++ "material_origin": "authored" ++ }, ++ { ++ "message_id": "m-mixed", ++ "block_id": "b-thinking-empty", ++ "source_session_id": "fixture:child", ++ "role": "assistant", ++ "message_type": "message", ++ "material_origin": "authored" ++ }, ++ { ++ "message_id": "m-mixed", ++ "block_id": "b-reasoning-empty", ++ "source_session_id": "fixture:child", ++ "role": "assistant", ++ "message_type": "message", ++ "material_origin": "authored" ++ } ++ ] ++ } ++ }, ++ { ++ "entry_type": "content", ++ "content": { ++ "message_id": "m-mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "text": "Use the typed evidence stream.", ++ "block_id": "b-thinking", ++ "block_type": "thinking", ++ "content_kind": "thinking", ++ "foldable": true, ++ "source_session_id": "fixture:child", ++ "material_origin": "authored" ++ } ++ }, ++ { ++ "entry_type": "content", ++ "content": { ++ "message_id": "m-mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "text": "print(\"semantic\")\n", ++ "block_id": "b-code", ++ "block_type": "code", ++ "content_kind": "code", ++ "language": "python", ++ "foldable": true, ++ "source_session_id": "fixture:child", ++ "material_origin": "authored" ++ } ++ }, ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:child", ++ "provider_family": "codex", ++ "source_session_id": "fixture:child", ++ "message_id": "m-mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "material_origin": "authored" ++ }, ++ "fields": [ ++ { ++ "label": "attachment id", ++ "value": "att-mixed" ++ }, ++ { ++ "label": "name", ++ "value": "trace.json" ++ }, ++ { ++ "label": "media type", ++ "value": "application/json" ++ }, ++ { ++ "label": "bytes", ++ "value": "42" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript card" ++ ], ++ "summary": "trace.json" ++ } ++ }, ++ { ++ "entry_type": "content", ++ "content": { ++ "message_id": "m-protocol", ++ "role": "system", ++ "message_type": "protocol", ++ "text": "Protocol boundary retained.", ++ "content_kind": "text", ++ "foldable": false, ++ "source_session_id": "fixture:child", ++ "material_origin": "synthetic" ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/orphan-result.json b/tests/data/semantic_cards/cases/orphan-result.json +index a993e2ee24a2bb69f3e1cb7def4a7d66dcdce042..21bdd63a424aae02071bf83681292a085fe55fe7 100644 +--- a/tests/data/semantic_cards/cases/orphan-result.json ++++ b/tests/data/semantic_cards/cases/orphan-result.json +@@ -40,9 +40,13 @@ + "session_id": "fixture:orphan-result", + "provider_family": "gemini-cli", + "tool_id": "missing-call", ++ "result_source_session_id": "fixture:orphan-result", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" + }, + "fields": [], + "previews": [ +@@ -78,5 +82,60 @@ + } + } + } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "fallback", ++ "title": "Unpaired tool result", ++ "source": { ++ "session_id": "fixture:orphan-result", ++ "provider_family": "gemini-cli", ++ "tool_id": "missing-call", ++ "result_source_session_id": "fixture:orphan-result", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_role": "tool", ++ "result_message_type": "tool_result", ++ "result_material_origin": "unknown" ++ }, ++ "fields": [], ++ "previews": [ ++ { ++ "kind": "result", ++ "text": "orphan failure", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [ ++ "no matching tool-use block is present; result semantics were not guessed" ++ ], ++ "outcome": { ++ "state": "failed", ++ "is_error": true, ++ "exit_code": 9 ++ }, ++ "raw_evidence": { ++ "result_preview": { ++ "kind": "result", ++ "text": "orphan failure", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ } + ] + } +diff --git a/tests/unit/api/test_facade_contracts.py b/tests/unit/api/test_facade_contracts.py +index 828802db9ad6246a9bf78395d078b6a16e88abb1..1f0b02846d2009b4afcba936a56db94b4b0e27c6 100644 +--- a/tests/unit/api/test_facade_contracts.py ++++ b/tests/unit/api/test_facade_contracts.py +@@ -45,7 +45,7 @@ from polylogue.core.enums import AssertionKind, AssertionStatus, BlockType, Bran + from polylogue.core.errors import DatabaseError, PolylogueError + from polylogue.core.json import JSONDocument + from polylogue.core.refs import delegation_edge_object_id +-from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession ++from polylogue.sources.parsers.base import ParsedAttachment, ParsedContentBlock, ParsedMessage, ParsedSession + from polylogue.storage.runtime.store_constants import SESSION_INSIGHT_MATERIALIZER_VERSION + from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore + from polylogue.storage.sqlite.archive_tiers.bootstrap import initialize_archive_tier +@@ -1854,6 +1854,98 @@ async def test_message_hydration_preserves_origin_and_structural_tool_outcome(tm + assert result["tool_result_exit_code"] == 0 + + ++async def test_composed_message_reads_retain_ancestor_owned_attachments(tmp_path: Path) -> None: ++ """Composed reads fetch attachments from every physical owner session. ++ ++ Production dependency: ``ArchiveRepository`` composes the canonical lineage ++ rows and batches attachment hydration by ``Message.source_session_id`` for ++ full, paginated, and bulk facade reads. ++ ++ Anti-vacuity mutation: reverting attachment lookup to the requested child ++ session ID drops ``context.txt`` from the inherited parent message and makes ++ all three assertions below fail. ++ """ ++ ++ archive = _archive(tmp_path) ++ with ArchiveStore(tmp_path) as archive_db: ++ parent_id = archive_db.write_parsed( ++ ParsedSession( ++ source_name=Provider.CODEX, ++ provider_session_id="attachment-parent", ++ title="Attachment parent", ++ messages=[ ++ ParsedMessage( ++ provider_message_id="p0", ++ role=Role.USER, ++ text="shared prompt", ++ ), ++ ParsedMessage( ++ provider_message_id="p1", ++ role=Role.ASSISTANT, ++ text="shared reply", ++ ), ++ ], ++ attachments=[ ++ ParsedAttachment( ++ provider_attachment_id="att-parent", ++ message_provider_id="p1", ++ name="context.txt", ++ mime_type="text/plain", ++ size_bytes=7, ++ path="context.txt", ++ ) ++ ], ++ ) ++ ) ++ child_id = archive_db.write_parsed( ++ ParsedSession( ++ source_name=Provider.CODEX, ++ provider_session_id="attachment-child", ++ title="Attachment child", ++ parent_session_provider_id="attachment-parent", ++ branch_type=BranchType.FORK, ++ messages=[ ++ ParsedMessage( ++ provider_message_id="c0", ++ role=Role.USER, ++ text="shared prompt", ++ ), ++ ParsedMessage( ++ provider_message_id="c1", ++ role=Role.ASSISTANT, ++ text="shared reply", ++ ), ++ ParsedMessage( ++ provider_message_id="c2", ++ role=Role.USER, ++ text="child tail", ++ ), ++ ], ++ ) ++ ) ++ ++ def inherited_attachment_names(messages: Iterable[Any]) -> list[str]: ++ return [ ++ attachment.name or "" ++ for message in messages ++ if message.source_session_id == parent_id ++ for attachment in message.attachments ++ ] ++ ++ try: ++ session = await archive.get_session(child_id) ++ assert session is not None ++ paged, total = await archive.get_messages_paginated(child_id, limit=10) ++ bulk = await archive.bulk_get_messages((child_id,)) ++ finally: ++ await archive.close() ++ ++ assert total == 3 ++ assert inherited_attachment_names(session.messages) == ["context.txt"] ++ assert inherited_attachment_names(paged) == ["context.txt"] ++ assert inherited_attachment_names(bulk[child_id]) == ["context.txt"] ++ ++ + async def test_get_messages_paginated_filters_material_origin(tmp_path: Path) -> None: + """Message reads can filter authoredness separately from provider role.""" + +diff --git a/tests/unit/core/test_models.py b/tests/unit/core/test_models.py +index 2fdd46a71e404fd89469d35a9e19ebf41c993dc8..dc8d69ac8e110eb6c6948e4ed41d76a1b1351d42 100644 +--- a/tests/unit/core/test_models.py ++++ b/tests/unit/core/test_models.py +@@ -364,11 +364,43 @@ class TestMessageFromRecord: + "tool_result_is_error": None, + "tool_result_exit_code": None, + "media_type": None, ++ "language": None, + "metadata": {"path": "/workspace/polylogue/README.md"}, + "semantic_type": "file_read", + } + ] + ++ def test_from_record_preserves_physical_owner_duration_and_code_language(self) -> None: ++ """Production dependency: canonical SQLite record hydrator. ++ ++ Anti-vacuity mutation: dropping session_id, duration_ms, or metadata ++ language during hydration breaks the three semantic-source assertions. ++ """ ++ ++ record = make_message( ++ message_id="m-code", ++ session_id="root-session", ++ role="assistant", ++ text="print('ok')", ++ duration_ms=87, ++ blocks=[ ++ make_content_block( ++ message_id="m-code", ++ session_id="root-session", ++ block_index=0, ++ block_type="code", ++ text="print('ok')", ++ metadata='{"language": "python"}', ++ ) ++ ], ++ ) ++ ++ message = message_from_record(record, []) ++ ++ assert message.source_session_id == "root-session" ++ assert message.duration_ms == 87 ++ assert message.blocks[0]["language"] == "python" ++ + + class TestSessionSummaryFromRecord: + def test_session_record_canonicalizes_epoch_timestamp_text(self) -> None: +diff --git a/tests/unit/daemon/test_web_reader.py b/tests/unit/daemon/test_web_reader.py +index fb0f1cf6da88f966b450badb0e158a7118c63b72..9d29754a0909280bbb37520d4d613802977fad6e 100644 +--- a/tests/unit/daemon/test_web_reader.py ++++ b/tests/unit/daemon/test_web_reader.py +@@ -1266,6 +1266,13 @@ class TestReaderSessionState: + workspace_env: dict[str, Path], + monkeypatch: pytest.MonkeyPatch, + ) -> None: ++ """Archive-backed detail emits the shared typed semantic transcript. ++ ++ Production dependency: archive-envelope conversion plus daemon semantic ++ placement. Anti-vacuity mutation: dropping archive source ownership or ++ bypassing the shared renderer removes the asserted typed fields. ++ """ ++ + session_id = _seed_archive_test_archive(workspace_env) + with _running_server_without_seed() as (_, base_url): + detail = _get_json(base_url, f"/api/sessions/{session_id}") +@@ -1276,6 +1283,12 @@ class TestReaderSessionState: + assert detail["origin"] == "codex-session" + assert "provider" not in detail + assert detail["messages"][0]["text"] == "Hello archive reader" ++ assert detail["semantic_transcript"]["schema_version"] == "semantic-transcript.v1" ++ assert detail["messages"][0]["source_session_id"] == session_id ++ assert {entry["entry_type"] for entry in detail["messages"][0]["semantic_entries"]} >= { ++ "card", ++ "content", ++ } + assert isinstance(messages, dict) + assert messages["total"] == 1 + assert messages["messages"][0]["target_ref"]["identity_key"].startswith(f"message:{session_id}:") +diff --git a/tests/unit/rendering/test_semantic_cards.py b/tests/unit/rendering/test_semantic_cards.py +index 7801bed04c63d923bd92f01dd89101e9b579f70f..dd02895142c036cbddcefb2b01c65d5ee2e80743 100644 +--- a/tests/unit/rendering/test_semantic_cards.py ++++ b/tests/unit/rendering/test_semantic_cards.py +@@ -9,8 +9,9 @@ from typing import cast + import jsonschema + import pytest + +-from devtools.render_semantic_card_fixtures import _load_case, render_case ++from devtools.render_semantic_card_fixtures import _load_case, render_case, render_entries + from devtools.render_semantic_card_fixtures import main as fixture_main ++from polylogue.core.enums import Origin + from polylogue.core.json import JSONDocument, require_json_document + from polylogue.rendering.semantic_card_models import ( + SemanticCard, +@@ -20,6 +21,8 @@ from polylogue.rendering.semantic_card_models import ( + ) + from polylogue.rendering.semantic_card_registry import ( + card_kind_for_tool, ++ origin_policy_documents, ++ parse_mcp_tool_name, + provider_namespace_documents, + semantic_type_policy_documents, + tool_mapping_rows, +@@ -43,6 +46,13 @@ def _expected(case: dict[str, object]) -> list[JSONDocument]: + return [require_json_document(item, context="expected semantic card") for item in value] + + ++def _expected_entries(case: dict[str, object]) -> list[JSONDocument]: ++ value = case.get("expected_entries") ++ assert isinstance(value, list) ++ assert all(isinstance(item, dict) for item in value) ++ return [require_json_document(item, context="expected semantic transcript entry") for item in value] ++ ++ + @pytest.mark.parametrize("case_path", CASE_PATHS, ids=lambda path: path.stem) + def test_semantic_card_golden_cases(case_path: Path) -> None: + case = _case(case_path) +@@ -50,6 +60,18 @@ def test_semantic_card_golden_cases(case_path: Path) -> None: + assert actual == _expected(case) + + ++@pytest.mark.parametrize("case_path", CASE_PATHS, ids=lambda path: path.stem) ++def test_semantic_transcript_golden_entries(case_path: Path) -> None: ++ """Production dependency: the ordered pure transcript renderer. ++ ++ Anti-vacuity mutation: dropping content, absence, or attachment entries ++ changes the frozen expected stream and fails this comparison. ++ """ ++ ++ case = _case(case_path) ++ assert render_entries(case, case_path=case_path) == _expected_entries(case) ++ ++ + @pytest.mark.parametrize("case_path", CASE_PATHS, ids=lambda path: path.stem) + def test_semantic_card_golden_cases_satisfy_public_schema(case_path: Path) -> None: + schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) +@@ -66,7 +88,18 @@ def test_golden_corpus_covers_every_card_kind_and_provider_family() -> None: + kinds.update(card.get("kind") for card in _expected(case)) + + assert providers == {"claude-code", "codex", "gemini-cli", "chatgpt", "hermes"} +- assert kinds == {"shell", "file_edit", "task", "lineage", "attachment", "fallback"} ++ assert kinds == { ++ "shell", ++ "file_read", ++ "file_edit", ++ "search", ++ "web", ++ "mcp", ++ "task", ++ "lineage", ++ "attachment", ++ "fallback", ++ } + + + def test_registry_is_explicit_unique_and_evidence_backed() -> None: +@@ -80,7 +113,7 @@ def test_registry_is_explicit_unique_and_evidence_backed() -> None: + "chatgpt", + "hermes", + } +- assert {row.rendering_status for row in rows} == {"launch", "model_only", "fallback"} ++ assert {row.rendering_status for row in rows} == {"launch", "fallback"} + assert {row.evidence_kind for row in rows} == { + "fixture_observed", + "parser_record_type", +@@ -110,21 +143,67 @@ def test_semantic_type_policy_is_exhaustive_over_persisted_vocabulary() -> None: + by_type = {str(row["semantic_type"]): row for row in rows} + assert by_type["shell"]["card_kind"] == "shell" + assert by_type["file_edit"]["card_kind"] == "file_edit" +- assert by_type["subagent"]["rendering_status"] == "model_only" +- assert by_type["search"]["card_kind"] == "fallback" ++ assert by_type["subagent"]["rendering_status"] == "launch" ++ assert by_type["search"]["card_kind"] == "search" ++ assert by_type["file_read"]["card_kind"] == "file_read" ++ assert by_type["web"]["card_kind"] == "web" + + + def test_provider_namespace_policy_discloses_open_world_fallback() -> None: + policies = provider_namespace_documents() + assert {item["provider_family"] for item in policies} == { +- "claude-code", +- "codex", +- "gemini-cli", +- "chatgpt", +- "hermes", ++ str(item["provider_family"]) for item in origin_policy_documents() + } + assert all(item["namespace"] == "open" for item in policies) +- assert all(item["unlisted_behavior"] == "persisted_semantic_type_then_fallback_raw_evidence" for item in policies) ++ assert all( ++ item["unlisted_behavior"] == "persisted_semantic_type_then_exact_mcp_then_fallback_raw_evidence" ++ for item in policies ++ ) ++ ++ ++def test_origin_policy_is_exhaustive_over_persisted_origin_enum() -> None: ++ """Production dependency: Origin-to-provider normalization policy. ++ ++ Anti-vacuity mutation: removing any Origin mapping makes enum coverage fail. ++ """ ++ ++ policies = origin_policy_documents() ++ assert {item["origin"] for item in policies} == {origin.value for origin in Origin} ++ assert len(policies) == len(Origin) ++ ++ ++def test_exact_mcp_grammar_specializes_and_lookalikes_fall_back() -> None: ++ """Production dependency: exact MCP tool-name structural classifier. ++ ++ Anti-vacuity mutation: prefix/substring matching would accept the malformed ++ lookalikes below and fail this test. ++ """ ++ ++ assert parse_mcp_tool_name("mcp__filesystem__read_file") == ("filesystem", "read_file") ++ assert ( ++ card_kind_for_tool( ++ provider_family="unknown", ++ tool_name="mcp__filesystem__read_file", ++ semantic_type=None, ++ ).value ++ == "mcp" ++ ) ++ for malformed in ( ++ "mcp_filesystem_read_file", ++ "mcp__filesystem", ++ "mcp____read_file", ++ "prefix_mcp__filesystem__read_file", ++ "mcp__filesystem__read_file__extra", ++ ): ++ assert parse_mcp_tool_name(malformed) is None ++ assert ( ++ card_kind_for_tool( ++ provider_family="unknown", ++ tool_name=malformed, ++ semantic_type=None, ++ ).value ++ == "fallback" ++ ) + + + def test_unknown_tool_remains_fallback_even_when_input_looks_like_shell() -> None: +diff --git a/tests/unit/rendering/test_semantic_transcript.py b/tests/unit/rendering/test_semantic_transcript.py +new file mode 100644 +index 0000000000000000000000000000000000000000..dfc427b32d23373a5f847e11493f13c6e9a7608e +--- /dev/null ++++ b/tests/unit/rendering/test_semantic_transcript.py +@@ -0,0 +1,243 @@ ++from __future__ import annotations ++ ++from polylogue.rendering.semantic_card_placement import semantic_card_placement_for_messages ++from polylogue.rendering.semantic_cards import build_semantic_transcript ++from polylogue.rendering.semantic_markdown import render_semantic_transcript_markdown ++ ++ ++def test_typed_empty_thinking_compacts_only_contiguous_runs() -> None: ++ """Production dependency: typed absence compaction in build_semantic_transcript. ++ ++ Anti-vacuity mutation: compacting every empty record independently, or ++ inferring absence from ordinary prose, changes the entry sequence/counts. ++ """ ++ ++ messages: list[dict[str, object]] = [ ++ { ++ "id": "thinking-message", ++ "role": "assistant", ++ "message_type": "thinking", ++ "text": "", ++ "blocks": [], ++ }, ++ { ++ "id": "thinking-block", ++ "role": "assistant", ++ "message_type": "message", ++ "blocks": [{"id": "tb", "type": "thinking", "text": " "}], ++ }, ++ { ++ "id": "ordinary-prose", ++ "role": "assistant", ++ "message_type": "message", ++ "text": "Thinking unavailable is merely quoted prose.", ++ "blocks": [], ++ }, ++ { ++ "id": "reasoning-block", ++ "role": "assistant", ++ "message_type": "message", ++ "blocks": [{"id": "rb", "type": "reasoning", "text": None}], ++ }, ++ ] ++ ++ transcript = build_semantic_transcript(messages, session_id="session") ++ documents = [entry.to_document() for entry in transcript.entries] ++ ++ assert [document["entry_type"] for document in documents] == [ ++ "absence", ++ "content", ++ "absence", ++ ] ++ assert documents[0]["absence"]["count"] == 2 # type: ignore[index] ++ assert documents[1]["content"]["text"] == "Thinking unavailable is merely quoted prose." # type: ignore[index] ++ assert documents[2]["absence"]["count"] == 1 # type: ignore[index] ++ ++ ++def test_nonempty_thinking_and_code_language_remain_typed_and_foldable() -> None: ++ """Production dependency: ordered typed content construction. ++ ++ Anti-vacuity mutation: flattening blocks to prose or dropping block language ++ makes these content documents and Markdown assertions fail. ++ """ ++ ++ transcript = build_semantic_transcript( ++ [ ++ { ++ "id": "mixed", ++ "role": "assistant", ++ "message_type": "message", ++ "source_session_id": "root", ++ "material_origin": "authored", ++ "blocks": [ ++ {"id": "think", "type": "thinking", "text": "Reason structurally."}, ++ {"id": "code", "type": "code", "text": "print('ok')\n", "language": "python"}, ++ ], ++ } ++ ], ++ session_id="child", ++ ) ++ ++ documents = [entry.to_document() for entry in transcript.entries] ++ assert [entry["content"]["content_kind"] for entry in documents] == ["thinking", "code"] # type: ignore[index] ++ assert documents[0]["content"]["foldable"] is True # type: ignore[index] ++ assert documents[1]["content"]["language"] == "python" # type: ignore[index] ++ assert documents[1]["content"]["source_session_id"] == "root" # type: ignore[index] ++ ++ markdown = render_semantic_transcript_markdown(transcript) ++ assert "Thinking" in markdown ++ assert "```python" in markdown ++ assert "source-session:root" in markdown ++ ++ ++def test_fifo_pairing_preserves_invocation_and_result_ownership() -> None: ++ """Production dependency: exact FIFO tool-id pairing and source projection. ++ ++ Anti-vacuity mutation: adjacency pairing, last-result wins, or collapsing ++ result metadata into invocation metadata breaks the four source assertions. ++ """ ++ ++ messages: list[dict[str, object]] = [ ++ { ++ "id": "use-root", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "source_session_id": "root", ++ "material_origin": "inherited", ++ "blocks": [ ++ { ++ "id": "use-root-block", ++ "type": "tool_use", ++ "tool_name": "exec_command", ++ "tool_id": "duplicate", ++ "tool_input": {"command": "first"}, ++ } ++ ], ++ }, ++ { ++ "id": "use-child", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "source_session_id": "child", ++ "material_origin": "authored", ++ "blocks": [ ++ { ++ "id": "use-child-block", ++ "type": "tool_use", ++ "tool_name": "exec_command", ++ "tool_id": "duplicate", ++ "tool_input": {"command": "second"}, ++ } ++ ], ++ }, ++ { ++ "id": "result-root", ++ "role": "tool", ++ "message_type": "tool_result", ++ "source_session_id": "root", ++ "material_origin": "tool_generated", ++ "blocks": [ ++ { ++ "id": "result-root-block", ++ "type": "tool_result", ++ "tool_id": "duplicate", ++ "text": "first result", ++ "tool_result_is_error": False, ++ } ++ ], ++ }, ++ { ++ "id": "result-child", ++ "role": "tool", ++ "message_type": "tool_result", ++ "source_session_id": "child", ++ "material_origin": "tool_generated", ++ "blocks": [ ++ { ++ "id": "result-child-block", ++ "type": "tool_result", ++ "tool_id": "duplicate", ++ "text": "second result", ++ "tool_result_is_error": False, ++ } ++ ], ++ }, ++ ] ++ ++ cards = build_semantic_transcript(messages, session_id="child", provider_family="codex").cards ++ assert [card.source.source_session_id for card in cards] == ["root", "child"] ++ assert [card.source.result_source_session_id for card in cards] == ["root", "child"] ++ assert [card.source.result_message_id for card in cards] == ["result-root", "result-child"] ++ assert [card.previews[0].text for card in cards] == ["first result", "second result"] ++ ++ ++def test_placement_keeps_mixed_result_row_that_owns_visible_content() -> None: ++ """Production dependency: message-at-a-time semantic placement adapter. ++ ++ Anti-vacuity mutation: suppressing every paired result message would hide ++ the context block that shares the physical result row. ++ """ ++ ++ messages: list[dict[str, object]] = [ ++ { ++ "id": "use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "blocks": [ ++ { ++ "id": "use-block", ++ "type": "tool_use", ++ "tool_name": "exec_command", ++ "tool_id": "tool", ++ "tool_input": {"command": "verify"}, ++ } ++ ], ++ }, ++ { ++ "id": "mixed-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "blocks": [ ++ { ++ "id": "result-block", ++ "type": "tool_result", ++ "tool_id": "tool", ++ "text": "done", ++ "tool_result_is_error": False, ++ }, ++ { ++ "id": "context-block", ++ "type": "text", ++ "text": "Provider context carried beside the result.", ++ }, ++ ], ++ }, ++ ] ++ ++ placement = semantic_card_placement_for_messages( ++ messages, ++ session_id="session", ++ provider_family="codex", ++ ) ++ ++ assert placement.is_suppressed("mixed-result") is False ++ entries = placement.entries_for("mixed-result") ++ assert len(entries) == 1 ++ assert entries[0]["entry_type"] == "content" ++ assert entries[0]["content"]["text"] == "Provider context carried beside the result." # type: ignore[index] ++ assert placement.cards_for("use")[0]["source"]["result_message_id"] == "mixed-result" # type: ignore[index] ++ ++ ++def test_page_bounded_scope_is_machine_readable_and_visible_in_markdown() -> None: ++ """Production dependency: page-scope disclosure in model and Markdown leaf. ++ ++ Anti-vacuity mutation: treating a partial page as a complete session removes ++ both scope fields and the human-readable warning. ++ """ ++ ++ transcript = build_semantic_transcript([], session_id="session", page_bounded=True) ++ document = transcript.to_document() ++ ++ assert document["pairing_scope"] == "page" ++ assert document["absence_compaction_scope"] == "page" ++ assert "bounded to this page" in render_semantic_transcript_markdown(transcript) +diff --git a/tests/unit/storage/test_lineage_normalization.py b/tests/unit/storage/test_lineage_normalization.py +index 04c6009a1007c7bb1b6922c7020e86137a9bfabf..cc15a70506c50cb144c779ce7ec4e6a13524e778 100644 +--- a/tests/unit/storage/test_lineage_normalization.py ++++ b/tests/unit/storage/test_lineage_normalization.py +@@ -145,6 +145,12 @@ def test_prefix_sharing_child_stores_only_tail_and_composes(tmp_path: Path) -> N + "child diverges here", + "child reply", + ] ++ assert [message.source_session_id for message in envelope.messages] == [ ++ parent_id, ++ parent_id, ++ child_id, ++ child_id, ++ ] + + conn.close() + +diff --git a/tests/visual/test_reader_semantic_cards.py b/tests/visual/test_reader_semantic_cards.py +index 6f7d6cb26d749d243a6500d685e5add062bbb2bb..586697c67292dfcfc851f17dc37750f2e1a7e44e 100644 +--- a/tests/visual/test_reader_semantic_cards.py ++++ b/tests/visual/test_reader_semantic_cards.py +@@ -61,6 +61,10 @@ def _preview(card: dict[str, object], kind: str) -> dict[str, object] | None: + return None + + ++def _card_by_kind(cards: list[dict[str, object]], kind: str) -> dict[str, object]: ++ return next(card for card in cards if card["kind"] == kind) ++ ++ + def _source(card: dict[str, object]) -> dict[str, object]: + return cast("dict[str, object]", card["source"]) + +@@ -79,8 +83,21 @@ def _semantic_card_session(reader_workspace: ReaderWorkspace) -> tuple[str, dict + + + def test_semantic_card_web_json_contract(_semantic_card_session: tuple[str, dict[str, object]]) -> None: ++ """Production dependency: daemon detail route plus shared placement/model serialization. ++ ++ Anti-vacuity mutation: a web-only classifier, omitted transcript envelope, ++ or loss of paired result suppression changes the schema and source fields. ++ """ ++ + _, payload = _semantic_card_session + messages = cast("list[dict[str, object]]", payload["messages"]) ++ transcript = cast("dict[str, object]", payload["semantic_transcript"]) ++ assert transcript["schema_version"] == "semantic-transcript.v1" ++ assert transcript["session_id"] == READER_SEM1 ++ assert any( ++ cast("dict[str, object]", entry).get("entry_type") == "card" ++ for entry in cast("list[dict[str, object]]", transcript["entries"]) ++ ) + + schema = json.loads(SEMANTIC_CARD_SCHEMA_PATH.read_text(encoding="utf-8")) + all_cards: list[dict[str, object]] = [] +@@ -98,8 +115,10 @@ def test_semantic_card_web_json_contract(_semantic_card_session: tuple[str, dict + # --- Bash: paired, succeeded, exit 0 -------------------------------- + shell_use = _message(messages, READER_SEM1_SHELL_USE) + shell_cards = cast("list[dict[str, object]]", shell_use["semantic_cards"]) +- assert len(shell_cards) == 1 +- shell_card = shell_cards[0] ++ assert {card["kind"] for card in shell_cards} == {"lineage", "shell"} ++ shell_card = _card_by_kind(shell_cards, "shell") ++ shell_entries = cast("list[dict[str, object]]", shell_use["semantic_entries"]) ++ assert [entry["entry_type"] for entry in shell_entries] == ["card", "card"] + assert shell_card["kind"] == "shell" + assert _source(shell_card)["tool_name"] == "Bash" + assert _outcome(shell_card)["state"] == "succeeded" +@@ -143,6 +162,12 @@ def test_semantic_card_web_json_contract(_semantic_card_session: tuple[str, dict + + + def test_semantic_card_web_dom_shape_contract(reader_workspace: ReaderWorkspace, tmp_path: Path) -> None: ++ """Production dependency: typed semantic-entry dispatch in the served reader shell. ++ ++ Anti-vacuity mutation: removing the typed dispatcher or reverting to only ++ raw-block heuristics removes one of the asserted JavaScript/DOM contracts. ++ """ ++ + with running_reader_server(reader_workspace) as (_, base_url): + status, content_type, body = get_text(base_url, "/") + +@@ -155,7 +180,8 @@ def test_semantic_card_web_dom_shape_contract(reader_workspace: ReaderWorkspace, + # message fully absorbed into another message's card, rather than + # re-deriving tool semantics from raw ``has_tool_use`` flags alone. + for phrase in ( +- "_polySemanticCardsForMessage", ++ "_polySemanticEntriesForMessage", ++ "_polySemanticEntriesHtml", + "semantic_card_suppressed", + "_polySemanticCardsHtml", + 'class="sem-card sem-card-', +@@ -177,7 +203,8 @@ def test_semantic_card_web_dom_shape_contract(reader_workspace: ReaderWorkspace, + "status": status, + "content_type": content_type, + "semantic_card_renderer_wired": True, +- "card_kinds_covered": ["shell", "file_edit", "task"], ++ "ordered_semantic_entries_wired": True, ++ "card_kinds_covered": ["lineage", "shell", "file_edit", "task"], + "suppression_wired": True, + "private_path_safe": True, + }, diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/TESTS.md new file mode 100644 index 000000000..538db1002 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/extracted/TESTS.md @@ -0,0 +1,138 @@ +# TESTS — beads-05 semantic transcript rendering + +## Test strategy + +The tests combine three independent forms of evidence: + +1. Frozen truth: 20 committed fixtures contain both expected `semantic-card.v1` cards and the complete ordered `semantic-transcript.v1` entry sequence. +2. Real production routes: canonical archive writer/query/hydrator/facade reads, archive-envelope reads, daemon HTTP detail, CLI Markdown, and the served web shell are exercised directly. +3. Anti-vacuity mutations: each new behavioral test names a representative production mutation/removal that would make it fail. + +The fixture generator does not derive expected output during verification. It renders current production output and compares it to frozen `expected_cards` and `expected_entries` already stored in each case. + +## Test-to-production dependency matrix + +| Test or corpus | Production dependency | Representative mutation/removal that fails it | +|---|---|---| +| `test_semantic_transcript_golden_entries` over 20 cases | `build_semantic_transcript`, card registry, model serialization | Drop content/absence/attachment entries, change ordering, pairing, source fields, or family classification. | +| `test_typed_empty_thinking_compacts_only_contiguous_runs` | Typed absence compaction | Emit one marker per record, merge across intervening content, or infer absence from prose. | +| `test_nonempty_thinking_and_code_language_remain_typed_and_foldable` | Typed block projection and Markdown leaf | Flatten blocks to text, drop code language, source ownership, or thinking fold. | +| `test_fifo_pairing_preserves_invocation_and_result_ownership` | FIFO `tool_id` pairing and separate source projection | Pair by adjacency/last-result, or collapse result metadata into invocation metadata. | +| `test_placement_keeps_mixed_result_row_that_owns_visible_content` | Message placement/suppression adapter | Suppress every paired result row, including rows with independent content. | +| `test_page_bounded_scope_is_machine_readable_and_visible_in_markdown` | Transcript scope contract and Markdown warning | Treat a partial page as a complete session or remove scope disclosure. | +| Origin policy completeness | `Origin` → provider policy | Remove any current Origin mapping. | +| Exact MCP grammar and malformed canaries | Structural MCP classifier | Replace exact parsing with prefix/substring matching. | +| Hydrator source/duration/language test | SQLite row mapper and hydrator | Drop physical owner, duration, or block metadata language. | +| Prefix-sharing lineage test | Archive-envelope composition | Re-identify inherited parent messages as child-owned. | +| Composed attachment facade test | Full/paginated/bulk repository reads | Query attachments only by requested child session. | +| Archive daemon detail test | Archive-to-domain conversion and shared placement | Bypass semantic placement or drop archive source ownership. | +| Web JSON contract | Normal daemon detail and `SemanticCard.to_document()` | Add a web-only classifier, omit transcript envelope, or stop result suppression. | +| Web DOM/source contract | Typed `semantic_entries` dispatcher | Remove typed dispatch or revert to raw-block heuristics only. | +| ChatGPT recipient JSON/non-JSON canaries | ChatGPT parser recipient handling | Parse JSON tool payload as text, or overclassify ordinary recipient prose. | + +## Commands and results + +### Environment + +The first broad collection attempt under the default environment stopped because the test extras were not installed (`hypothesis` was missing). No production assertion from that attempt was counted. The repository’s declared dev/test dependency group was then installed with `uv sync --extra dev`, and all reported results below use that environment. + +### Generated truth + +```bash +.venv/bin/python devtools/render_semantic_card_fixtures.py --check +.venv/bin/python devtools/render_semantic_card_registry.py --check +``` + +Result: + +```text +semantic-card fixtures: verified 20 case(s) +semantic-card registry: verified 2 surface(s) +``` + +The same commands also pass from the clean apply-check worktree after applying `PATCH.diff`. + +### Consolidated production regression set + +```bash +.venv/bin/pytest -q tests/unit/rendering/test_semantic_cards.py tests/unit/rendering/test_semantic_transcript.py tests/visual/test_reader_semantic_cards.py tests/unit/core/test_models.py tests/unit/storage/test_message_query_reads.py tests/unit/storage/test_lineage_normalization.py tests/unit/storage/test_archive_tiers_write.py tests/unit/api/test_facade_contracts.py::test_composed_message_reads_retain_ancestor_owned_attachments tests/unit/daemon/test_web_reader.py::TestReaderSessionState tests/unit/daemon/test_web_reader.py::TestReaderWorkspaceRoutes tests/unit/cli/test_messages.py tests/unit/cli/test_streaming_markdown_read_view.py tests/unit/sources/test_parsers_chatgpt.py::test_chatgpt_recipient_addressed_json_payload_parses_as_tool_use tests/unit/sources/test_parsers_chatgpt.py::test_chatgpt_recipient_addressed_non_json_text_stays_text +``` + +Result: `317 passed in 27.66s`. + +This count includes overlapping files later rerun after small typing/docstring changes. It must not be added to the rerun counts as a unique-test total. + +### Renderer rerun after final behavior/type changes + +```bash +.venv/bin/pytest -q tests/unit/rendering/test_semantic_cards.py tests/unit/rendering/test_semantic_transcript.py +``` + +Result: `84 passed in 1.11s` in the implementation worktree. + +The same 84 tests pass in `1.15s` from a detached clean worktree after applying the final `PATCH.diff`. + +### Final real-route/docstring check + +```bash +.venv/bin/pytest -q tests/visual/test_reader_semantic_cards.py tests/unit/daemon/test_web_reader.py::TestReaderSessionState::test_archive_file_set_session_detail_and_messages_from_archive_tiers +``` + +Result: `3 passed in 2.89s`. + +### Targeted checks run during implementation + +- Inherited attachment through full, paginated, and bulk facade reads: `1 passed`. +- Archive-backed daemon semantic transcript route: `1 passed` after correcting the assertion to account for the intentionally first lineage card. +- ChatGPT JSON tool-use and non-JSON counterexample canaries: `2 passed`. +- CLI messages and streaming Markdown: `14 passed`. +- Canonical message hydration/query route: `7 passed`. +- Archive-tier writer and lineage normalization set: `89 passed`. +- Daemon session-state/workspace focused set: `20 passed` in an earlier focused invocation. + +These are diagnostic reruns and overlap the 317-test command. + +### Static and generated quality gates + +```bash +.venv/bin/ruff check +``` + +Result: `All checks passed!` + +```bash +.venv/bin/mypy devtools/render_semantic_card_fixtures.py devtools/render_semantic_card_registry.py polylogue/api/archive.py polylogue/archive/message/models.py polylogue/cli/messages.py polylogue/daemon/http.py polylogue/daemon/web_shell_reader.py polylogue/daemon/web_shell_semantic_cards.py polylogue/rendering/semantic_card_models.py polylogue/rendering/semantic_card_placement.py polylogue/rendering/semantic_card_registry.py polylogue/rendering/semantic_cards.py polylogue/rendering/semantic_markdown.py polylogue/storage/hydrators.py polylogue/storage/repository/archive/sessions.py polylogue/storage/sqlite/archive_tiers/write.py polylogue/storage/sqlite/queries/mappers_archive.py polylogue/storage/sqlite/queries/message_query_reads.py +``` + +Result: `Success: no issues found in 18 source files`. + +The first Mypy invocation identified unsafe generic `int(object)` coercions and an untyped scope literal in `semantic_cards.py`. Those were repaired with explicit supported scalar types and a `Literal` annotation; the passing result above is after repair. + +```bash +node --check /tmp/beads05-reader.js +``` + +`/tmp/beads05-reader.js` is the concatenation of `SEMANTIC_CARD_JS` and `READER_JS`. Result: exit status 0. + +```bash +git diff --check +``` + +Result: exit status 0 in both implementation and apply-check worktrees. + +### Patch application certification + +```bash +git worktree add --detach /mnt/data/beads05_applycheck f654480cadb7cc4c194704e24dfd483199547b35 +git -C /mnt/data/beads05_applycheck apply --check PATCH.diff +git -C /mnt/data/beads05_applycheck apply PATCH.diff +git -C /mnt/data/beads05_applycheck diff --check +``` + +Result: all commands passed; the applied worktree reports the same 49 changed paths. + +## Failed or incomplete verification + +A full invocation of `tests/unit/daemon/test_web_reader.py` was attempted with a 300-second execution limit. It progressed through roughly 60 tests without reporting a failure, then did not complete within that limit in a later query-completion area outside the semantic transcript routes. The relevant `TestReaderSessionState` and `TestReaderWorkspaceRoutes` classes pass in the consolidated command. + +The complete repository suite was not run. Playwright/browser visual checks, a sanitized real ChatGPT export, the operator’s live archive/daemon, and deployment checks remain unverified. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/raw.zip b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/raw.zip new file mode 100644 index 000000000..7d6b44616 Binary files /dev/null and b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r01/raw.zip differ diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/EVIDENCE.md new file mode 100644 index 000000000..332fac34d --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/EVIDENCE.md @@ -0,0 +1,151 @@ +# Evidence and decision record + +## Authority order + +The implementation used this authority order: + +1. the mission and exact Result ZIP contract; +2. the attached snapshot’s Git bundle at `f654480cadb7cc4c194704e24dfd483199547b35`; +3. repository instructions and current source at that commit; +4. complete current Beads records for `polylogue-ap7.1` and `polylogue-395j`; +5. relevant landed history and tests; +6. older plans or naming only where current source still confirmed them. + +No API, test helper, deployment state, or live operator evidence was invented. + +## Snapshot evidence + +`polylogue-manifest.json` and `polylogue-overview.json` both report: + +```text +branch: master +commit: f654480cadb7cc4c194704e24dfd483199547b35 +dirty: true +generated_at: 2026-07-17T043202Z +``` + +The same archive contains: + +```text +polylogue-branch-delta.patch 0 bytes +polylogue-branch-delta-files.txt 0 bytes +polylogue-branch-delta-log.txt 0 bytes +``` + +This is a material contradiction: the manifest calls the source dirty, but every captured tracked-delta surface is empty. The working-tree tar is also selective; treating it as a deletion-authoritative mirror would falsely remove tracked repository material. The safe reconstruction is therefore the named clean Git commit plus a non-destructive overlay. The delivered patch does not claim to preserve an unavailable pre-existing tracked dirty patch. + +## Beads evidence + +### `polylogue-ap7.1` + +The record says the landed shell/edit/task/attachment core must be completed with provider-neutral family coverage and bounded lineage parity. Its design requires a coverage matrix over normalized tool families and executable Origins, explicit fallback, source-structural outcomes, target/path extraction, provenance, bounded previews, and one `SemanticCard.to_document` contract consumed by CLI Markdown and web. It specifically rejects full-family topology hydration and duplicate work-graph semantics. + +Implementation consequences: + +- enumerate policy over `SemanticBlockType` and `Origin`, not an aspirational list of provider tools; +- keep provider namespaces open and unknown tools inspectable as fallback; +- classify from persisted semantic type, structural MCP identity, or exact aliases only; +- keep delegation as its existing bounded query/read model; +- represent DB lineage as partial when the session row lacks root/completeness authority; +- represent archive lineage from the exact composed envelope. + +### `polylogue-395j` + +The record describes a real ChatGPT export with many typed but zero-content thinking blocks. Its later design note explicitly says the archive evidence is legitimate and must not be fabricated or erased; operator presentation should emit one compact absence marker per contiguous run with count and provenance. The rule must key on typed block kind and value state, never provider name or prose regex. + +Implementation consequences: + +- both `thinking` and storage-superset `reasoning` are typed absence candidates; +- whitespace-only content is absent; +- each notice contains every exact source coordinate; +- non-thinking typed rows terminate runs; +- non-empty thinking remains normal typed prose; +- raw/forensic evidence remains available through source coordinates and original storage, while the reader avoids full message-card weight. + +## Current-source findings + +### Existing card core was real and should be extended + +PR #2700 already established a pure card model/registry, structural outcome trivalence, bounded preview logic, raw fallback, CLI Markdown, a public card schema, and hostile cross-provider fixtures. Replacing it with another renderer would violate the mission and architecture. The patch extends those types and builders into an ordered transcript rather than creating a parallel framework. + +### Web wiring was card-only and not on the browser’s primary message route + +PR #2736 attached card placement to full session-detail routes and gave the browser a card HTML hook. Current browser code loads a summary route and the paginated `/api/sessions/{id}/messages` route. Before this patch, the primary paginated route did not attach semantic cards/entries, and the presentation leaf still classified raw tool/thinking roles as a fallback. That made the landed web integration incomplete for the actual read path. + +The patch enriches both paginated backends and makes `semantic_entries` authoritative. Legacy fallback remains only when the field is absent, preserving compatibility without allowing current payloads to drift. + +### Card-only placement could not represent prose or typed absence + +The prior placement model indexed cards and suppression by message. It could not carry exact authored prose, a compact run-level absence notice, or session-level ordered entries. The patch changes placement to distribute the shared transcript’s entry documents while retaining card-only compatibility views. + +### One-pass result pairing is order-sensitive + +The original renderer paired a result only when the use had already been observed. Provider exports can serialize a tool result before its use. That produced an orphan result when scanned, then a paired card later, duplicating the same evidence. The patch indexes both coordinate sets first and pairs FIFO by exact `tool_id`, independent of serialization order. + +### Suppression must account for mixed rows + +A message can contain a paired protocol result and independently meaningful runtime context/prose. Suppressing solely because its result is paired deletes real content. Placement now suppresses only `paired_result_ids - independent_message_ids`; the mixed-row fixture proves the context survives. + +### Role and provider are insufficient for empty-thinking behavior + +`role=thinking`, `has_thinking`, or ChatGPT-specific names are not enough to establish a compact absence. The typed block vocabulary in `BlockType` distinguishes `thinking` and `reasoning`, and the actual value establishes absence. The shared renderer therefore owns compaction, not the web leaf. + +### Persisted semantic type and exact alias have different authority + +`SemanticBlockType` is closed and established upstream; provider tool names are open. Treating the alias table as complete would be false. The registry therefore gives persisted semantic type precedence over aliases, except for structurally encoded MCP identity, and documents every unlisted origin namespace as fallback-capable. + +### MCP identity parsing was duplicated + +`polylogue/insights/tool_usage.py` already parsed `mcp__server__tool`; rendering needed the same protocol identity. Duplicating the string split would allow insight/render drift. The parser is now a core structural helper reused by both. + +### Archive composition already has stronger authority than DB session rows + +The archive envelope already carries lineage completeness and truncation reason from PR #2603. It did not retain which source session contributed each composed message or expose the exact inheritance/branch point on the envelope. Position-based inference would be unsafe. The patch adds those bounded facts at the composition seam. + +Ordinary DB session rows carry parent/relation but not exact root/completeness/branch-point authority. The renderer labels those unavailable instead of hydrating a full topology family or inventing values. + +### Storage decoupling initially lost metadata-backed display fields + +Removing the renderer’s runtime import was architecturally correct, but the generic attribute projection initially failed to recover `language`, URL, name, and media type from persisted metadata JSON. The final audit found this concrete regression. `_coerce_mapping_block` now falls back to parsed metadata for those fields, and a focused test fails if the fallback is removed. + +### ChatGPT web fixture name was stale + +The old case name `chatgpt-web-fallback.json` encoded the prior gap. Current typed/alias policy can classify the structurally known ChatGPT web recipient as a web card. The case is renamed to `chatgpt-web.json` and asserts the specialized contract instead of preserving a stale fallback expectation. + +### Generated text is marked binary by repository attributes + +Normal Git stat output labels several generated JSON/Markdown/YAML files as binary. A default patch would therefore omit reviewable text hunks. `PATCH.diff` was exported from a temporary index with `--text --full-index --binary`; it contains ordinary unified text hunks and no binary patch marker. A real clean apply reproduced all changed bytes. + +## Historical evidence + +### PR #2700 — `0f5059068` + +The commit message states that it added a pure semantic-card contract and registry for shell, edit, lineage, task, attachment, and unknown evidence; wired CLI Markdown; and deliberately left web parity and larger cross-provider coverage open. This confirms the correct extension point and the residual scope. + +### PR #2736 — `0c251b600` + +The commit message states that it wired the existing registry into the daemon reader, introduced per-message card placement, and covered only shell, file edit, and task end-to-end. It explicitly describes older-payload fallback. This confirms both the shared-registry intent and why complete ordered entries still needed work. + +### PR #2629 — `7688d103d` + +The commit corrected recipient-addressed ChatGPT tool calls to typed `TOOL_USE` instead of raw text. The patch preserves that authority: a typed tool-use envelope suppresses serialized invocation JSON in `message.text` rather than rendering it as prose. + +### PR #2603 — `c06ca601c` + +The commit added `lineage_complete` and `lineage_truncation_reason` to archive composition. The patch consumes those exact fields and does not recreate completeness logic in rendering. + +### PR #2759 — `9ebc09ef0` + +The commit established a bounded delegation evidence-card projection with its own query/read semantics. The semantic transcript uses task cards for tool dispatch evidence but does not copy that broader delegation model into the renderer. + +## Test and route evidence + +The frozen corpus now contains 23 cases. It covers every card kind plus ordered prose/notices, result-before-use, mixed result/context rows, message-envelope attachments, MCP structure, typed empty thinking, exact authoredness/variant/inherited metadata, hostile outcomes, non-UTF-8 replacement disclosure, missing results, unknown tools, and lineage uncertainty. + +`tests/visual/test_reader_semantic_cards.py` was changed from the full-detail compatibility route to the actual paginated message route used by the browser. It reconstructs the ordered transcript envelope and validates it against `semantic-transcript.v1`, while nested cards are validated against `semantic-card.v1`. The test also pins the authoritative entry hooks in served HTML. + +`tests/unit/storage/test_lineage_normalization.py` now asserts that a prefix-sharing child’s composed parent rows retain the parent source session, child-tail rows retain the child source session, and the envelope exposes exact inheritance and branch point. + +## Unverified evidence + +No claim is made about live operator data, current daemon state, private archives, secrets, browser-capture behavior, Nix deployment, or a real unsanitized provider export. The environment could not execute normal repository-wide imports/gates because locked dependencies are incomplete. The exact blocked checks are listed in `TESTS.md` and `HANDOFF.md`. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/HANDOFF.md new file mode 100644 index 000000000..7d17188e5 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/HANDOFF.md @@ -0,0 +1,220 @@ +# Semantic transcript rendering — implementation handoff + +## Mission and delivered slice + +This package implements the `beads-05` semantic transcript rendering mission for Beads `polylogue-ap7.1` and `polylogue-395j`. It extends the semantic-card core already landed on the snapshot into one ordered, provider-neutral transcript document shared by CLI Markdown and the daemon/web reader. The document represents typed prose, compact notices, tools/results, attachments, lineage, authoredness, variants, inherited prefixes, duration, and exact source coordinates without deriving semantics from prose. + +The patch is one cohesive change against snapshot commit `f654480cadb7cc4c194704e24dfd483199547b35` on branch `master`. Apply `PATCH.diff` as a single unit. No complete replacement files are included because the unified diff is self-contained and was byte-compared after a clean apply. + +## Snapshot identity + +The attached project-state archive reported: + +| Field | Value | +|---|---| +| Branch | `master` | +| Commit | `f654480cadb7cc4c194704e24dfd483199547b35` | +| Manifest generated | `2026-07-17T043202Z` | +| Manifest dirty flag | `true` | +| Captured branch-delta patch | empty, 0 bytes | +| Captured branch-delta file list | empty, 0 bytes | +| Captured branch-only log | empty, 0 bytes | + +The working-tree payload was selective rather than deletion-authoritative. Reconstruction therefore used the attached Git bundle at the named commit and overlaid the payload non-destructively. No pre-existing tracked dirty change can be reconstructed from the archive because every captured branch-delta surface is empty. This patch contains only the implementation described here. + +## Evidence inspected + +Repository instructions and architecture were read from `AGENTS.md`/`CLAUDE.md`, including the substrate-first rule, typed `Origin` and `SemanticBlockType` vocabularies, lineage composition model, focused-test policy, and the requirement to regenerate topology inventories after adding a `polylogue/` module. + +The complete Beads records inspected were: + +- `polylogue-ap7.1`, “Complete semantic-card family coverage and bounded lineage parity.” Its acceptance criteria require exhaustive normalized-family/origin policy, schema-valid cards for all named families, shared CLI/web structure, bounded DB/archive lineage parity, and preservation of recipient-addressed ChatGPT tool structure. +- `polylogue-395j`, “Web/CLI transcript renderer gives zero-content ChatGPT thinking blocks full message-card weight.” Its later design note requires one compact absence marker per contiguous typed run, retained provenance, non-empty thinking rendered normally, and no provider/prose heuristic. + +Relevant source and tests inspected include `polylogue/core/enums.py`, `polylogue/rendering/{block_models,semantic_card_models,semantic_card_registry,semantic_cards,semantic_card_placement,semantic_markdown}.py`, `polylogue/cli/messages.py`, the actual daemon session-detail and paginated-message routes in `polylogue/daemon/http.py`, the web shell reader fragments, archive composition in `polylogue/storage/sqlite/archive_tiers/write.py`, ChatGPT parser regressions, existing semantic fixtures, CLI tests, lineage normalization tests, and visual reader tests. + +Relevant history inspected: + +| Commit | Finding | +|---|---| +| `0f5059068` / PR #2700 | Landed the pure semantic-card contract, CLI Markdown backend, shell/edit/task/attachment core, fixtures, and card schema. Explicitly left broader coverage and web parity open. | +| `0c251b600` / PR #2736 | Wired card-only placement into the web reader for the full-detail route and retained presentation-leaf fallbacks. Explicitly covered only shell/edit/task. | +| `7688d103d` / PR #2629 | Corrected ChatGPT recipient-addressed calls to typed `TOOL_USE` rather than raw JSON prose. | +| `c06ca601c` / PR #2603 | Added exact lineage-completeness and truncation signals to composed archive reads. | +| `9ebc09ef0` / PR #2759 | Established bounded delegation evidence as a separate query/read projection; this patch does not duplicate that work-graph model. | + +## Mechanism + +### One ordered transcript contract + +`SemanticTranscript` now contains ordered `SemanticTranscriptEntry` values, exactly one of: + +- typed `TranscriptProse`, retaining role, message type, material origin, provider family/origin, block identity/type/language, timestamp, duration, parent identity, variant, active-path/leaf state, and inherited-prefix state; +- a `SemanticCard`, retaining machine coordinates, structural outcome, bounded previews, raw input/result evidence, and explicit caveats; +- a typed `TranscriptNotice`, currently `empty_thinking`, retaining every represented block coordinate. + +The public wire contract is `semantic-transcript.v1`, defined by the new self-contained `docs/schemas/semantic-transcript-v1.schema.json`. `semantic-card.v1` remains the nested card contract and was expanded for all card kinds and exact source metadata. + +### Structural classification and complete policy + +The shared registry now covers every persisted `SemanticBlockType` and every executable `Origin`. Classification precedence is explicit: + +1. structurally valid `mcp____` identity; +2. persisted `semantic_type` from normalization; +3. evidence-backed exact provider/tool alias; +4. generic fallback with raw evidence and a reason. + +This prevents tool prose, output text, or open provider namespaces from inventing semantics. Card kinds now cover `shell`, `file_read`, `file_edit`, `search`, `web`, `task`, `mcp`, `lineage`, `attachment`, and `fallback`. The generated registry documents disclose open namespaces and fallback behavior. + +MCP identity parsing was factored into `polylogue/core/tool_identity.py` and reused by rendering and tool-usage insights, removing duplicate structural parsing. + +### Tool/result pairing and mixed rows + +Tool uses and results are indexed by exact `tool_id`, then paired FIFO by structural coordinates. Pairing is independent of serialization order, so a result serialized before its use is absorbed once rather than emitted as both an orphan and a paired preview. A result message is suppressed only when it has no independently meaningful prose, context, attachment, or notice entry; mixed protocol/context rows retain their independent content. + +Structural outcome remains trivalent. `tool_result_is_error` and `tool_result_exit_code` establish success/failure; absent evidence remains unknown. Conflicting fields remain failure with an explicit caveat. Output prose never controls outcome. + +### Empty thinking + +Empty or whitespace-only typed `thinking` and `reasoning` blocks form a contiguous absence run. The renderer emits one compact `empty_thinking` notice for the run and retains every message/block coordinate plus authoredness and lineage metadata. Any typed non-thinking row terminates the run, even when that row is absorbed into a paired card. Non-empty thinking remains a typed prose entry and is foldable in the browser. + +### Attachments and exact metadata + +Both typed attachment blocks and message-envelope attachments become attachment cards. Duplicate envelope/block representations are structurally de-duplicated. Attachment bytes are deliberately not embedded. + +Generic block coercion no longer imports storage runtime types. It projects block-shaped objects structurally and parses persisted metadata JSON. During the final audit, a regression in this decoupling was found and repaired: `language`, URL, name, and media type are again retained from metadata-backed runtime records. A focused test fails if that fallback is removed. + +### Bounded lineage + +DB-backed reads project only facts already present on the session row and explicitly label root/completeness as unavailable where the row has no authority. They no longer hydrate a topology family solely to render a lineage card. + +Archive-backed composed reads now retain `source_session_id` on every composed message and expose exact inheritance mode and branch-point identity on `ArchiveSessionEnvelope`. This lets the renderer identify inherited-prefix evidence without guessing by position. Archive lineage includes exact completeness and truncation reason from the existing composition authority; `resolved` is asserted only where parent, prefix inheritance, and complete composition are all established. + +### Surface wiring + +CLI Markdown renders the complete ordered transcript through `render_semantic_transcript_markdown`. + +Both daemon full-detail routes and the actual paginated `/api/sessions/{id}/messages` routes attach authoritative `semantic_entries`. Compatibility `semantic_cards` and suppression fields remain for older consumers. The browser treats the presence of `semantic_entries`, including an empty array, as authoritative; raw role/text classification is only a legacy payload fallback. Session-level lineage entries survive initial load and pagination. Attachments are not rendered a second time when semantic entries are present. + +## Key decisions + +| Decision | Reason | +|---|---| +| Preserve `semantic-card.v1` and add `semantic-transcript.v1` | Existing card consumers remain compatible while prose/notices gain one ordered contract. | +| Keep compatibility card fields on daemon payloads | Avoid a breaking web/API cutover; the browser prefers the new authoritative field. | +| Use typed absence notices rather than omitting empty thinking | Operator views become compact without losing evidence that the provider exported a typed but unavailable block. | +| Pair structurally before rendering | Supports result-before-use serialization and prevents presentation-order bugs. | +| Retain raw evidence on fallback cards | Unknown open-namespace tools remain inspectable without guessed semantics. | +| Use exact archive composition provenance | Inherited prefixes are facts established by composition, not inferred from message position. | +| Use session-row lineage for ordinary DB reads | Satisfies boundedness and avoids whole-family topology hydration. | +| Build paginated DB placement from the complete session | Preserves cross-page tool/result identity with the current API; this is the principal performance risk and the best candidate for a storage-level follow-up. | +| Keep delegation as an existing separate bounded projection | Avoids duplicating the work-graph semantics landed by PR #2759. | +| Force generated text files to text in `PATCH.diff` | Repository attributes present some generated text as binary; an apply-ready reviewable unified diff is required here. | + +## Changed files + +The patch has 49 diff records spanning 50 paths, including one detected rename, with 4,119 insertions and 536 deletions. + +Production and contracts: + +- `polylogue/core/tool_identity.py` +- `polylogue/insights/tool_usage.py` +- `polylogue/rendering/block_models.py` +- `polylogue/rendering/semantic_card_models.py` +- `polylogue/rendering/semantic_card_registry.py` +- `polylogue/rendering/semantic_cards.py` +- `polylogue/rendering/semantic_card_placement.py` +- `polylogue/rendering/semantic_markdown.py` +- `polylogue/cli/messages.py` +- `polylogue/daemon/http.py` +- `polylogue/daemon/web_shell.py` +- `polylogue/daemon/web_shell_reader.py` +- `polylogue/daemon/web_shell_semantic_cards.py` +- `polylogue/storage/sqlite/archive_tiers/write.py` +- `docs/schemas/semantic-card-v1.schema.json` +- `docs/schemas/semantic-transcript-v1.schema.json` + +Generated surfaces: + +- `docs/generated/semantic-card-tool-map.json` +- `docs/generated/semantic-card-tool-map.md` +- `docs/plans/topology-target.yaml` +- `docs/topology-status.md` + +Fixture and generator changes: + +- `devtools/render_semantic_card_fixtures.py` +- `devtools/render_semantic_card_registry.py` +- all prior semantic-card cases updated for exact source metadata; +- `chatgpt-web-fallback.json` renamed to `chatgpt-web.json` because web is now structurally classified; +- new cases: `codex-search.json`, `empty-thinking-run.json`, `gemini-file-read.json`, `mcp-tool.json`, `message-attachment-envelope.json`, `mixed-tool-result-context.json`, and `typed-prose-metadata.json`. + +Tests: + +- `tests/unit/core/test_tool_identity.py` +- `tests/unit/rendering/test_semantic_cards.py` +- `tests/unit/cli/test_messages.py` +- `tests/unit/storage/test_lineage_normalization.py` +- `tests/visual/test_reader_semantic_cards.py` + +## Acceptance matrix + +| Requirement | Status | Evidence | +|---|---|---| +| Every persisted semantic family has an explicit card/fallback policy | Satisfied | Generated semantic-type policy; exhaustive enum assertion; registry check. | +| Every executable `Origin` has an explicit namespace/fallback policy | Satisfied | Generated origin policy; test compares policy set with `set(Origin)`. | +| Shell, read, edit/write, search, web, task, MCP, attachment, lineage, unknown | Satisfied | All ten card kinds occur in the 23-case corpus and validate against `semantic-card.v1`. | +| Structural outcome, target, refs, previews, unknown/missing states | Satisfied | Golden fixtures and 96 focused tests; outcome tests use only typed fields. | +| CLI and web consume one document | Satisfied in implementation; runtime route test not executed here | CLI and daemon import the same renderer; browser consumes `semantic_entries`; visual test targets the actual paginated route. | +| DB/archive bounded lineage parity without family hydration | Satisfied for lineage mechanism; storage route execution partially unverified | DB uses session-row authority; archive uses exact envelope authority; renderer tests cover complete/partial states; storage provenance test was added but could not run in this dependency-incomplete runtime. | +| Recipient-addressed ChatGPT tool calls remain typed and raw JSON does not leak | Preserved from current source; focused reparse unverified | PR #2629 parser path remains authoritative; tool-envelope message text is suppressed when a typed `TOOL_USE` block exists. | +| Empty thinking is compact in CLI and web, non-empty thinking remains normal | Satisfied in implementation and fixtures; live private-session check unverified | `empty-thinking-run.json`, ordered transcript assertions, Markdown and browser notice renderers. | +| Mixed protocol/context rows retain context | Satisfied | `mixed-tool-result-context.json`; suppression requires absence of independent entries. | +| Variants, active state, inherited prefixes, duration, authoredness | Satisfied | Exact metadata is present in prose/notices/card sources and covered by fixtures/tests. | +| Real attachments | Satisfied at normalized block/envelope level | Block and message-envelope attachment fixtures; bytes remain outside transcript by design. | +| Superseded renderer duplication removed | Satisfied for semantic classification | Browser leaf no longer classifies authoritative payloads; shared MCP parser removes duplicate protocol parsing. Legacy fallback remains intentionally for older payloads. | + +## Apply order + +1. Start from clean commit `f654480cadb7cc4c194704e24dfd483199547b35`. +2. Run `git apply --check PATCH.diff`. +3. Apply once with `git apply PATCH.diff`. +4. Enter the repository’s locked development environment. +5. Run the focused commands in `TESTS.md`, then the normal `devtools verify` baseline. +6. Manually exercise a real ChatGPT export containing unavailable reasoning blocks and the daemon/browser reader before merge. + +The package generation process performed both `git apply --check` and a real apply into a fresh clone. Every changed path was then compared byte-for-byte with the implementation workspace. + +## Verification performed + +- Focused renderer suite: **96 passed** in 2.29 seconds. Two warnings report unavailable pytest timeout options in this reduced environment; no tests failed. +- Frozen semantic fixtures: **23 verified**. +- Generated semantic registry: **2 surfaces verified**. +- Topology projection/status: regenerated and checked; projection contains **1,008 rows**, with 9 existing TBD ownership rows. +- Public schemas: both Draft 2020-12 schemas are structurally valid. +- Fixture schema validation: **23 transcript documents** and **22 nested card entries** validated. +- Shared MCP identity tests: **2 executed** directly. +- Changed Python files: **21 compiled**. +- Browser JavaScript: reader fragment, semantic renderer fragment, and assembled shell script all passed `node --check`. +- `git diff --check`: clean. +- `PATCH.diff`: 275,889 bytes, 7,265 lines, no binary-patch marker, no copied input archive path, and no implementation placeholder marker. +- Clean-base `git apply --check`: passed. +- Clean-base actual apply and byte comparison: passed for all **50 changed paths**. + +## Important limitations and risks + +The complete repository environment was not available offline. `devtools render all --check` stops during import because `ijson` is missing. Normal CLI, storage, and visual-test collection also encounters missing runtime/test dependencies such as `dateparser`, `aiosqlite`, `tenacity`, and `hypothesis`. Ruff and mypy executables are absent. Therefore full generated-surface verification, strict typing, repository lint/format gates, the added storage test, the actual daemon route test, and the CLI test remain unverified in this runtime. The patch compiles and the independently runnable semantic suite is green, but the normal locked environment must run those gates. + +No operator daemon, browser session, Nix deployment, secrets, private archive, or unsanitized provider export was available. The required manual check against a real ChatGPT reasoning export is unverified. Exact `reasoning` subtype behavior is covered by typed renderer logic and fixtures, not a live export in this environment. + +The paginated DB route currently hydrates the complete session to preserve tool/result pairing across page boundaries. This is semantically correct but may be expensive for very large sessions. The substantial follow-up is a bounded storage/API pairing index or action-coordinate projection that lets a page retain cross-boundary identity without full-session hydration. + +DB session rows do not carry exact root identity, composition completeness, branch point, or active leaf, so those fields are explicitly unknown rather than guessed. Archive-backed reads carry stronger authority. This asymmetry is deliberate and visible. + +Legacy attachment byte envelopes remain separate from normalized attachment-card rendering. Attachment identity/metadata is represented; bytes are not copied into transcript documents. + +## Value of another iteration + +A small repair pass has high confidence and limited scope: run the locked `devtools verify`/Ruff/mypy/testmon gates, execute the added CLI/storage/visual tests, inspect any formatting/type findings, and perform the real ChatGPT/browser manual check. That pass should not require architectural changes unless the unavailable gates expose one. + +A substantial second pass would add a bounded cross-page tool/result pairing projection in storage/API, converge legacy attachment envelopes with normalized attachment identity, and expose stronger DB lineage authority. That work could improve performance and parity materially, but it is larger than the remaining certification work and should be tracked independently rather than folded into this patch. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/PATCH.diff new file mode 100644 index 000000000..ed56e2906 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/PATCH.diff @@ -0,0 +1,7265 @@ +diff --git a/devtools/render_semantic_card_fixtures.py b/devtools/render_semantic_card_fixtures.py +index 68391372759e0d51d7a3fc1f6f391a4de7feaa3e..35d1f06096b75b46732127f085d92802125b5cca 100644 +--- a/devtools/render_semantic_card_fixtures.py ++++ b/devtools/render_semantic_card_fixtures.py +@@ -1,8 +1,10 @@ + """Regenerate or verify semantic-card golden fixture expectations. + + The input side of each case is hand-authored. ``expected_contract`` is the +-compact independent oracle reviewers can audit; ``expected_cards`` freezes the +-complete provider-neutral JSON contract emitted by the pure renderer. ++compact independent oracle reviewers can audit; ``expected_cards`` freezes card ++contracts. Cases that exercise prose/notices also carry ``expected_entries`` so ++the complete ordered transcript behavior is reviewable without duplicating it ++for every card-only fixture. + """ + + from __future__ import annotations +@@ -76,7 +78,21 @@ def render_case(case: Mapping[str, object], *, case_path: Path) -> list[JSONDocu + return card_json_documents(build_case_transcript(case, case_path=case_path).cards) + + +-def _assert_compact_contract(case: Mapping[str, object], actual: list[JSONDocument], *, case_path: Path) -> list[str]: ++def render_case_entries(case: Mapping[str, object], *, case_path: Path) -> list[JSONDocument]: ++ document = build_case_transcript(case, case_path=case_path).to_document() ++ entries = document.get("entries") ++ if not isinstance(entries, list): ++ raise ValueError(f"{case_path}: renderer did not emit transcript entries") ++ return [cast(JSONDocument, entry) for entry in entries] ++ ++ ++def _assert_compact_contract( ++ case: Mapping[str, object], ++ actual: list[JSONDocument], ++ actual_entries: list[JSONDocument], ++ *, ++ case_path: Path, ++) -> list[str]: + contract = case.get("expected_contract") + if not isinstance(contract, Mapping): + return [f"{case_path}: expected_contract must be an object"] +@@ -95,6 +111,22 @@ def _assert_compact_contract(case: Mapping[str, object], actual: list[JSONDocume + outcomes.append(outcome.get("state") if isinstance(outcome, dict) else None) + if expected_outcomes != outcomes: + errors.append(f"{case_path}: outcomes expected {expected_outcomes!r}, got {outcomes!r}") ++ if "entry_types" in contract: ++ entry_types = [entry.get("entry_type") for entry in actual_entries] ++ if contract.get("entry_types") != entry_types: ++ errors.append( ++ f"{case_path}: entry_types expected {contract.get('entry_types')!r}, got {entry_types!r}" ++ ) ++ if "notice_counts" in contract: ++ notice_counts = [ ++ cast(Mapping[str, object], entry.get("notice")).get("count") ++ for entry in actual_entries ++ if entry.get("entry_type") == "notice" and isinstance(entry.get("notice"), Mapping) ++ ] ++ if contract.get("notice_counts") != notice_counts: ++ errors.append( ++ f"{case_path}: notice_counts expected {contract.get('notice_counts')!r}, got {notice_counts!r}" ++ ) + return errors + + +@@ -129,15 +161,25 @@ def main(argv: list[str] | None = None) -> int: + for path in paths: + try: + case = _load_case(path) +- actual = render_case(case, case_path=path) +- failures.extend(_assert_compact_contract(case, actual, case_path=path)) ++ transcript = build_case_transcript(case, case_path=path) ++ actual = card_json_documents(transcript.cards) ++ transcript_document = transcript.to_document() ++ raw_entries = transcript_document.get("entries") ++ if not isinstance(raw_entries, list): ++ raise ValueError(f"{path}: renderer did not emit transcript entries") ++ actual_entries = [cast(JSONDocument, entry) for entry in raw_entries] ++ failures.extend(_assert_compact_contract(case, actual, actual_entries, case_path=path)) + if args.write: + case["expected_cards"] = actual ++ if case.get("freeze_entries") is True or "expected_entries" in case: ++ case["expected_entries"] = actual_entries + path.write_text(json.dumps(case, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + else: + expected = case.get("expected_cards") + if expected != actual: + failures.append(f"{path}: full expected_cards contract differs from renderer output") ++ if "expected_entries" in case and case.get("expected_entries") != actual_entries: ++ failures.append(f"{path}: full expected_entries contract differs from renderer output") + except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc: + failures.append(f"{path}: {exc}") + +diff --git a/devtools/render_semantic_card_registry.py b/devtools/render_semantic_card_registry.py +index 923d0bfcd46d1b1a3ce221f91a6d3dea2e66a665..a25047245df398590f68c072ac760118e1a78bc2 100644 +--- a/devtools/render_semantic_card_registry.py ++++ b/devtools/render_semantic_card_registry.py +@@ -24,12 +24,18 @@ MAP_SCHEMA_VERSION = "semantic-card-tool-map.v1" + def build_json_document() -> dict[str, object]: + rows = tool_mapping_rows() + counts = Counter(row.rendering_status for row in rows) +- providers = sorted({row.provider_family for row in rows}) ++ policies = provider_namespace_documents() ++ providers = sorted({str(policy["provider_family"]) for policy in policies}) + return { + "schema_version": MAP_SCHEMA_VERSION, + "card_schema_version": CARD_SCHEMA_VERSION, + "classification_policy": { +- "precedence": ["persisted_semantic_type", "exact_provider_tool_alias", "fallback"], ++ "precedence": [ ++ "structural_mcp_tool_identity", ++ "persisted_semantic_type", ++ "exact_provider_tool_alias", ++ "fallback", ++ ], + "unknown_tool": "fallback_raw_evidence", + "outcome_policy": "structural_fields_only", + "null_outcome": "unknown", +@@ -37,7 +43,7 @@ def build_json_document() -> dict[str, object]: + "provider_families": providers, + "card_kinds": [item.value for item in SemanticCardKind], + "status_counts": dict(sorted(counts.items())), +- "provider_namespace_policies": provider_namespace_documents(), ++ "provider_namespace_policies": policies, + "semantic_type_policies": semantic_type_policy_documents(), + "provider_semantic_coverage": [ + { +@@ -61,13 +67,13 @@ def build_markdown() -> str: + "# Semantic card tool map", + "", + "This is the review surface for the provider-neutral `semantic-card.v1` registry. ", +- "Persisted semantic type wins, then a repository-grounded exact provider/tool alias. ", +- "Provider namespaces are open: an unlisted tool always becomes a raw fallback card, ", ++ "Structural MCP identity wins, then persisted semantic type, then a repository-grounded ", ++ "exact provider/tool alias. Provider namespaces are open: an unlisted tool always becomes a raw fallback card, ", + "and names or prose are never fuzzily classified.", + "", +- "`launch` means the CLI launch slice has a specialized card. `model_only` means the ", +- "shared card model exists but public-surface completion is a follow-on. `fallback` ", +- "is intentional raw evidence, not a missing hidden heuristic.", ++ "`launch` means the shared CLI/API/web renderer has a specialized card. `model_only` means ", ++ "the shared card is intentionally generic at the presentation leaf. `fallback` is intentional ", ++ "raw evidence, not a missing hidden heuristic.", + "", + ] + lines.extend( +@@ -84,6 +90,22 @@ def build_markdown() -> str: + ) + for policy in semantic_type_policy_documents(): + lines.append(f"| `{policy['semantic_type']}` | `{policy['card_kind']}` | `{policy['rendering_status']}` |") ++ lines.extend( ++ [ ++ "", ++ "## Executable-origin policy", ++ "", ++ "Every `Origin` value has an explicit provider family and open-world fallback policy.", ++ "", ++ "| Origin | Provider family | Namespace | Grounded exact aliases | Unlisted behavior |", ++ "|---|---|---|---:|---|", ++ ] ++ ) ++ for policy in provider_namespace_documents(): ++ lines.append( ++ f"| `{policy['origin']}` | `{policy['provider_family']}` | `{policy['namespace']}` | " ++ f"{policy['grounded_exact_aliases']} | `{policy['unlisted_behavior']}` |" ++ ) + lines.extend(["", "## Provider exact-alias census", ""]) + + for provider in sorted(grouped): +@@ -105,11 +127,12 @@ def build_markdown() -> str: + [ + "## Gap-reading rules", + "", +- "- Every provider namespace is treated as open; the finite table is evidence-backed rather than aspirational.", ++ "- Every executable origin has an explicit open namespace; the exact-alias table is evidence-backed rather than aspirational.", ++ "- A structural `mcp__server__tool` identity is classified before provider aliases and exposes both server and tool coordinates.", + "- A provider can emit tool names not listed here. Those cards preserve exact raw input and result evidence.", + "- A persisted `semantic_type` can safely specialize a provider-private name because classification already happened upstream.", + "- `NULL` structural result fields remain `unknown`; a success-like sentence is not a success signal.", +- "- Web visual wiring, task deep links, richer attachment actions, and streaming cross-page pairing are bounded follow-ons.", ++ "- Cross-page pairing is orchestrated from a whole-session projection; a bounded storage-level pairing index remains a performance follow-on.", + "", + ] + ) +diff --git a/docs/generated/semantic-card-tool-map.json b/docs/generated/semantic-card-tool-map.json +index b00a7ebad44074f1bfa7a3231bc03f6e48866245..f74d1573c3c094f17949e74f865bdd048c32d877 100644 +--- a/docs/generated/semantic-card-tool-map.json ++++ b/docs/generated/semantic-card-tool-map.json +@@ -3,6 +3,7 @@ + "card_schema_version": "semantic-card.v1", + "classification_policy": { + "precedence": [ ++ "structural_mcp_tool_identity", + "persisted_semantic_type", + "exact_provider_tool_alias", + "fallback" +@@ -12,55 +13,111 @@ + "null_outcome": "unknown" + }, + "provider_families": [ ++ "antigravity", ++ "beads", + "chatgpt", ++ "claude-ai", + "claude-code", + "codex", + "gemini-cli", +- "hermes" ++ "grok", ++ "hermes", ++ "unknown" + ], + "card_kinds": [ + "shell", ++ "file_read", + "file_edit", ++ "search", ++ "web", + "task", ++ "mcp", + "lineage", + "attachment", + "fallback" + ], + "status_counts": { +- "fallback": 14, +- "launch": 12, +- "model_only": 2 ++ "fallback": 1, ++ "launch": 22, ++ "model_only": 5 + }, + "provider_namespace_policies": [ + { +- "provider_family": "chatgpt", +- "namespace": "open", +- "grounded_exact_aliases": 3, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" +- }, +- { ++ "origin": "claude-code-session", + "provider_family": "claude-code", + "namespace": "open", + "grounded_exact_aliases": 14, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" + }, + { ++ "origin": "codex-session", + "provider_family": "codex", + "namespace": "open", + "grounded_exact_aliases": 8, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" + }, + { ++ "origin": "gemini-cli-session", + "provider_family": "gemini-cli", + "namespace": "open", + "grounded_exact_aliases": 1, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" + }, + { ++ "origin": "hermes-session", + "provider_family": "hermes", + "namespace": "open", + "grounded_exact_aliases": 2, +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence" ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "antigravity-session", ++ "provider_family": "antigravity", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "beads-issue", ++ "provider_family": "beads", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "grok-export", ++ "provider_family": "grok", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "chatgpt-export", ++ "provider_family": "chatgpt", ++ "namespace": "open", ++ "grounded_exact_aliases": 3, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "claude-ai-export", ++ "provider_family": "claude-ai", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "aistudio-drive", ++ "provider_family": "gemini-cli", ++ "namespace": "open", ++ "grounded_exact_aliases": 1, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ }, ++ { ++ "origin": "unknown-export", ++ "provider_family": "unknown", ++ "namespace": "open", ++ "grounded_exact_aliases": 0, ++ "unlisted_behavior": "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" + } + ], + "semantic_type_policies": [ +@@ -68,70 +125,91 @@ + "semantic_type": "other", + "card_kind": "fallback", + "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": "the persisted semantic family is intentionally generic" + }, + { + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "card_kind": "file_read", ++ "rendering_status": "launch", ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "file_write", + "card_kind": "file_edit", + "rendering_status": "launch", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "file_edit", + "card_kind": "file_edit", + "rendering_status": "launch", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "shell", + "card_kind": "shell", + "rendering_status": "launch", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "git", + "card_kind": "shell", + "rendering_status": "launch", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "card_kind": "search", ++ "rendering_status": "launch", ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "card_kind": "web", ++ "rendering_status": "launch", ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "card_kind": "task", ++ "rendering_status": "model_only", ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "subagent", + "card_kind": "task", + "rendering_status": "model_only", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": null + }, + { + "semantic_type": "thinking", + "card_kind": "fallback", + "rendering_status": "fallback", +- "classification_basis": "persisted_semantic_type" ++ "classification_basis": "persisted_semantic_type", ++ "fallback_reason": "thinking is rendered from typed content blocks, not as a tool card" + } + ], + "provider_semantic_coverage": [ ++ { ++ "provider_family": "antigravity", ++ "semantic_types": [], ++ "exact_alias_count": 0 ++ }, ++ { ++ "provider_family": "beads", ++ "semantic_types": [], ++ "exact_alias_count": 0 ++ }, + { + "provider_family": "chatgpt", + "semantic_types": [ +@@ -141,6 +219,11 @@ + ], + "exact_alias_count": 3 + }, ++ { ++ "provider_family": "claude-ai", ++ "semantic_types": [], ++ "exact_alias_count": 0 ++ }, + { + "provider_family": "claude-code", + "semantic_types": [ +@@ -173,12 +256,22 @@ + ], + "exact_alias_count": 1 + }, ++ { ++ "provider_family": "grok", ++ "semantic_types": [], ++ "exact_alias_count": 0 ++ }, + { + "provider_family": "hermes", + "semantic_types": [ + "shell" + ], + "exact_alias_count": 2 ++ }, ++ { ++ "provider_family": "unknown", ++ "semantic_types": [], ++ "exact_alias_count": 0 + } + ], + "rows": [ +@@ -231,8 +324,8 @@ + "provider_family": "claude-code", + "tool_name": "Read", + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "file_read", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -240,8 +333,8 @@ + "provider_family": "claude-code", + "tool_name": "Grep", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -249,8 +342,8 @@ + "provider_family": "claude-code", + "tool_name": "Glob", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_base.py" + }, +@@ -267,8 +360,8 @@ + "provider_family": "claude-code", + "tool_name": "WebFetch", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -276,8 +369,8 @@ + "provider_family": "claude-code", + "tool_name": "WebSearch", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -285,8 +378,8 @@ + "provider_family": "claude-code", + "tool_name": "AskUserQuestion", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "model_only", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -294,8 +387,8 @@ + "provider_family": "claude-code", + "tool_name": "EnterPlanMode", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "model_only", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -303,8 +396,8 @@ + "provider_family": "claude-code", + "tool_name": "ExitPlanMode", + "semantic_type": "agent", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "task", ++ "rendering_status": "model_only", + "evidence_kind": "classifier_contract", + "evidence": "polylogue/archive/viewport/tools.py" + }, +@@ -339,8 +432,8 @@ + "provider_family": "codex", + "tool_name": "search", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_codex.py" + }, +@@ -357,8 +450,8 @@ + "provider_family": "codex", + "tool_name": "tool_search_call", + "semantic_type": "search", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "search", ++ "rendering_status": "launch", + "evidence_kind": "parser_record_type", + "evidence": "polylogue/sources/parsers/codex.py" + }, +@@ -366,8 +459,8 @@ + "provider_family": "codex", + "tool_name": "web_search_call", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "parser_record_type", + "evidence": "polylogue/sources/parsers/codex.py" + }, +@@ -384,8 +477,8 @@ + "provider_family": "gemini-cli", + "tool_name": "read_file", + "semantic_type": "file_read", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "file_read", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_local_agent.py" + }, +@@ -420,8 +513,8 @@ + "provider_family": "chatgpt", + "tool_name": "web", + "semantic_type": "web", +- "card_kind": "fallback", +- "rendering_status": "fallback", ++ "card_kind": "web", ++ "rendering_status": "launch", + "evidence_kind": "fixture_observed", + "evidence": "tests/unit/sources/test_parsers_chatgpt.py" + }, +diff --git a/docs/generated/semantic-card-tool-map.md b/docs/generated/semantic-card-tool-map.md +index 8075373970a065406e345553791d79dae4655dab..edbfdebd2af39050148c1f376efdb57a236092c4 100644 +--- a/docs/generated/semantic-card-tool-map.md ++++ b/docs/generated/semantic-card-tool-map.md +@@ -3,13 +3,13 @@ + # Semantic card tool map + + This is the review surface for the provider-neutral `semantic-card.v1` registry. +-Persisted semantic type wins, then a repository-grounded exact provider/tool alias. +-Provider namespaces are open: an unlisted tool always becomes a raw fallback card, ++Structural MCP identity wins, then persisted semantic type, then a repository-grounded ++exact provider/tool alias. Provider namespaces are open: an unlisted tool always becomes a raw fallback card, + and names or prose are never fuzzily classified. + +-`launch` means the CLI launch slice has a specialized card. `model_only` means the +-shared card model exists but public-surface completion is a follow-on. `fallback` +-is intentional raw evidence, not a missing hidden heuristic. ++`launch` means the shared CLI/API/web renderer has a specialized card. `model_only` means ++the shared card is intentionally generic at the presentation leaf. `fallback` is intentional ++raw evidence, not a missing hidden heuristic. + + ## Persisted semantic-family policy + +@@ -20,17 +20,35 @@ that cannot be enumerated in the exact-alias tables. + | Persisted semantic type | Card kind | Status | + |---|---|---| + | `other` | `fallback` | `fallback` | +-| `file_read` | `fallback` | `fallback` | ++| `file_read` | `file_read` | `launch` | + | `file_write` | `file_edit` | `launch` | + | `file_edit` | `file_edit` | `launch` | + | `shell` | `shell` | `launch` | + | `git` | `shell` | `launch` | +-| `search` | `fallback` | `fallback` | +-| `web` | `fallback` | `fallback` | +-| `agent` | `fallback` | `fallback` | ++| `search` | `search` | `launch` | ++| `web` | `web` | `launch` | ++| `agent` | `task` | `model_only` | + | `subagent` | `task` | `model_only` | + | `thinking` | `fallback` | `fallback` | + ++## Executable-origin policy ++ ++Every `Origin` value has an explicit provider family and open-world fallback policy. ++ ++| Origin | Provider family | Namespace | Grounded exact aliases | Unlisted behavior | ++|---|---|---|---:|---| ++| `claude-code-session` | `claude-code` | `open` | 14 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `codex-session` | `codex` | `open` | 8 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `gemini-cli-session` | `gemini-cli` | `open` | 1 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `hermes-session` | `hermes` | `open` | 2 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `antigravity-session` | `antigravity` | `open` | 0 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `beads-issue` | `beads` | `open` | 0 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `grok-export` | `grok` | `open` | 0 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `chatgpt-export` | `chatgpt` | `open` | 3 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `claude-ai-export` | `claude-ai` | `open` | 0 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `aistudio-drive` | `gemini-cli` | `open` | 1 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++| `unknown-export` | `unknown` | `open` | 0 | `structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence` | ++ + ## Provider exact-alias census + + ## chatgpt +@@ -39,25 +57,25 @@ that cannot be enumerated in the exact-alias tables. + |---|---|---|---|---|---| + | `canmore.update_textdoc` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | + | `dalle.text2im` | `other` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | +-| `web` | `web` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | ++| `web` | `web` | `web` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_chatgpt.py` | + + ## claude-code + + | Exact tool name | Semantic type | Card kind | Status | Basis | Evidence | + |---|---|---|---|---|---| +-| `AskUserQuestion` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `AskUserQuestion` | `agent` | `task` | `model_only` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `Bash` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + | `Edit` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `EnterPlanMode` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `ExitPlanMode` | `agent` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `Glob` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `Grep` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `EnterPlanMode` | `agent` | `task` | `model_only` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `ExitPlanMode` | `agent` | `task` | `model_only` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `Glob` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `Grep` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + | `MultiEdit` | `file_edit` | `file_edit` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `NotebookEdit` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `Read` | `file_read` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | ++| `Read` | `file_read` | `file_read` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + | `Task` | `subagent` | `task` | `model_only` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | +-| `WebFetch` | `web` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `WebSearch` | `web` | `fallback` | `fallback` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `WebFetch` | `web` | `web` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | ++| `WebSearch` | `web` | `web` | `launch` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | + | `Write` | `file_write` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_base.py` | + + ## codex +@@ -67,17 +85,17 @@ that cannot be enumerated in the exact-alias tables. + | `apply_patch` | `file_edit` | `file_edit` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | + | `exec_command` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_codex_event_stream_contract.py` | + | `local_shell_call` | `shell` | `shell` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | +-| `search` | `search` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | ++| `search` | `search` | `search` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | + | `shell` | `shell` | `shell` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_codex.py` | + | `spawn_agent` | `subagent` | `task` | `model_only` | `classifier_contract` | `polylogue/archive/viewport/tools.py` | +-| `tool_search_call` | `search` | `fallback` | `fallback` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | +-| `web_search_call` | `web` | `fallback` | `fallback` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | ++| `tool_search_call` | `search` | `search` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | ++| `web_search_call` | `web` | `web` | `launch` | `parser_record_type` | `polylogue/sources/parsers/codex.py` | + + ## gemini-cli + + | Exact tool name | Semantic type | Card kind | Status | Basis | Evidence | + |---|---|---|---|---|---| +-| `read_file` | `file_read` | `fallback` | `fallback` | `fixture_observed` | `tests/unit/sources/test_parsers_local_agent.py` | ++| `read_file` | `file_read` | `file_read` | `launch` | `fixture_observed` | `tests/unit/sources/test_parsers_local_agent.py` | + + ## hermes + +@@ -88,8 +106,9 @@ that cannot be enumerated in the exact-alias tables. + + ## Gap-reading rules + +-- Every provider namespace is treated as open; the finite table is evidence-backed rather than aspirational. ++- Every executable origin has an explicit open namespace; the exact-alias table is evidence-backed rather than aspirational. ++- A structural `mcp__server__tool` identity is classified before provider aliases and exposes both server and tool coordinates. + - A provider can emit tool names not listed here. Those cards preserve exact raw input and result evidence. + - A persisted `semantic_type` can safely specialize a provider-private name because classification already happened upstream. + - `NULL` structural result fields remain `unknown`; a success-like sentence is not a success signal. +-- Web visual wiring, task deep links, richer attachment actions, and streaming cross-page pairing are bounded follow-ons. ++- Cross-page pairing is orchestrated from a whole-session projection; a bounded storage-level pairing index remains a performance follow-on. +diff --git a/docs/plans/topology-target.yaml b/docs/plans/topology-target.yaml +index f7f9c974216bc0c67829c306b658e87429bb1728..700c140e238629b7ca97be5917de0f66b8828d2a 100644 +--- a/docs/plans/topology-target.yaml ++++ b/docs/plans/topology-target.yaml +@@ -973,7 +973,7 @@ files: + target: polylogue/cli/machine_main.py + owner: stable + - path: polylogue/cli/messages.py +- loc: 183 ++ loc: 180 + target: polylogue/cli/messages.py + owner: stable + - path: polylogue/cli/parser_diagnostics.py +@@ -1366,6 +1366,11 @@ files: + target: polylogue/core/timestamps.py + owner: core-primitive + reason: core primitive ++ - path: polylogue/core/tool_identity.py ++ loc: 48 ++ target: polylogue/core/tool_identity.py ++ owner: core-primitive ++ reason: core primitive + - path: polylogue/core/types.py + loc: 20 + target: polylogue/core/types.py +@@ -1502,7 +1507,7 @@ files: + target: polylogue/daemon/healthz.py + owner: stable + - path: polylogue/daemon/http.py +- loc: 4633 ++ loc: 4684 + target: polylogue/daemon/http.py + owner: stable + - path: polylogue/daemon/lifecycle.py +@@ -1607,7 +1612,7 @@ files: + target: polylogue/daemon/web_auth.py + owner: stable + - path: polylogue/daemon/web_shell.py +- loc: 3260 ++ loc: 3270 + target: polylogue/daemon/web_shell.py + owner: stable + - path: polylogue/daemon/web_shell_attachments.py +@@ -1631,7 +1636,7 @@ files: + target: polylogue/daemon/web_shell_provenance.py + owner: stable + - path: polylogue/daemon/web_shell_reader.py +- loc: 785 ++ loc: 794 + target: polylogue/daemon/web_shell_reader.py + owner: stable + - path: polylogue/daemon/web_shell_realtime.py +@@ -1643,7 +1648,7 @@ files: + target: polylogue/daemon/web_shell_selection.py + owner: stable + - path: polylogue/daemon/web_shell_semantic_cards.py +- loc: 191 ++ loc: 317 + target: polylogue/daemon/web_shell_semantic_cards.py + owner: stable + - path: polylogue/daemon/web_shell_similar.py +@@ -1904,7 +1909,7 @@ files: + target: polylogue/insights/timeline_renderer.py + owner: stable + - path: polylogue/insights/tool_usage.py +- loc: 223 ++ loc: 202 + target: polylogue/insights/tool_usage.py + owner: stable + - path: polylogue/insights/topology.py +@@ -2311,7 +2316,7 @@ files: + target: polylogue/readiness/claim_guard.py + owner: stable + - path: polylogue/rendering/block_models.py +- loc: 240 ++ loc: 257 + target: polylogue/rendering/block_models.py + owner: stable + cross_cut: { lifecycle: model } +@@ -2360,24 +2365,24 @@ files: + target: polylogue/rendering/renderers/html_template.py + owner: stable + - path: polylogue/rendering/semantic_card_models.py +- loc: 279 ++ loc: 455 + target: polylogue/rendering/semantic_card_models.py + owner: stable + cross_cut: { lifecycle: model } + - path: polylogue/rendering/semantic_card_placement.py +- loc: 94 ++ loc: 119 + target: polylogue/rendering/semantic_card_placement.py + owner: stable + - path: polylogue/rendering/semantic_card_registry.py +- loc: 421 ++ loc: 550 + target: polylogue/rendering/semantic_card_registry.py + owner: stable + - path: polylogue/rendering/semantic_cards.py +- loc: 678 ++ loc: 1367 + target: polylogue/rendering/semantic_cards.py + owner: stable + - path: polylogue/rendering/semantic_markdown.py +- loc: 147 ++ loc: 209 + target: polylogue/rendering/semantic_markdown.py + owner: stable + - path: polylogue/scenarios/__init__.py +@@ -3240,7 +3245,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_readiness.py +- loc: 818 ++ loc: 830 + target: TBD + owner: storage-domain + - path: polylogue/storage/archive_views.py +@@ -3525,7 +3530,7 @@ files: + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_reconciler.py +- loc: 1411 ++ loc: 1422 + target: TBD + owner: storage-domain + - path: polylogue/storage/raw_retention.py +@@ -3850,7 +3855,7 @@ files: + target: polylogue/storage/sqlite/archive_tiers/user_write.py + owner: stable + - path: polylogue/storage/sqlite/archive_tiers/write.py +- loc: 4257 ++ loc: 4272 + target: polylogue/storage/sqlite/archive_tiers/write.py + owner: stable + - path: polylogue/storage/sqlite/async_sqlite.py +diff --git a/docs/schemas/semantic-card-v1.schema.json b/docs/schemas/semantic-card-v1.schema.json +index c02d3f634a9a5e36181ed7781bbf03f7787417f8..f7f0ad286afe17652723c134ef924a045f4b2115 100644 +--- a/docs/schemas/semantic-card-v1.schema.json ++++ b/docs/schemas/semantic-card-v1.schema.json +@@ -3,84 +3,243 @@ + "$id": "https://polylogue.local/schemas/semantic-card-v1.schema.json", + "title": "Polylogue semantic transcript card", + "type": "object", +- "required": ["schema_version", "kind", "title", "source", "fields", "previews", "caveats"], ++ "required": [ ++ "schema_version", ++ "kind", ++ "title", ++ "source", ++ "fields", ++ "previews", ++ "caveats" ++ ], + "additionalProperties": false, + "properties": { +- "schema_version": {"const": "semantic-card.v1"}, +- "kind": {"enum": ["shell", "file_edit", "task", "lineage", "attachment", "fallback"]}, +- "title": {"type": "string", "minLength": 1}, +- "summary": {"type": "string"}, ++ "schema_version": { ++ "const": "semantic-card.v1" ++ }, ++ "kind": { ++ "enum": [ ++ "shell", ++ "file_read", ++ "file_edit", ++ "search", ++ "web", ++ "task", ++ "mcp", ++ "lineage", ++ "attachment", ++ "fallback" ++ ] ++ }, ++ "title": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "summary": { ++ "type": "string" ++ }, + "source": { + "type": "object", +- "required": ["session_id", "provider_family"], ++ "required": [ ++ "session_id", ++ "provider_family" ++ ], + "additionalProperties": false, + "properties": { +- "session_id": {"type": "string", "minLength": 1}, +- "provider_family": {"type": "string", "minLength": 1}, +- "message_id": {"type": "string"}, +- "block_id": {"type": "string"}, +- "block_index": {"type": "integer", "minimum": 0}, +- "tool_name": {"type": "string"}, +- "tool_id": {"type": "string"}, +- "result_message_id": {"type": "string"}, +- "result_block_id": {"type": "string"}, +- "result_block_index": {"type": "integer", "minimum": 0} ++ "session_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "provider_family": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "origin": { ++ "type": "string" ++ }, ++ "message_id": { ++ "type": "string" ++ }, ++ "block_id": { ++ "type": "string" ++ }, ++ "block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "tool_name": { ++ "type": "string" ++ }, ++ "tool_id": { ++ "type": "string" ++ }, ++ "attachment_id": { ++ "type": "string" ++ }, ++ "material_origin": { ++ "type": "string" ++ }, ++ "occurred_at": { ++ "type": "string" ++ }, ++ "duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "parent_message_id": { ++ "type": "string" ++ }, ++ "variant_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "is_active_path": { ++ "type": "boolean" ++ }, ++ "is_active_leaf": { ++ "type": "boolean" ++ }, ++ "inherited_prefix": { ++ "type": "boolean" ++ }, ++ "result_message_id": { ++ "type": "string" ++ }, ++ "result_block_id": { ++ "type": "string" ++ }, ++ "result_block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "result_duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "result_material_origin": { ++ "type": "string" ++ }, ++ "result_inherited_prefix": { ++ "type": "boolean" ++ } + } + }, + "outcome": { + "type": "object", +- "required": ["state"], ++ "required": [ ++ "state" ++ ], + "additionalProperties": false, + "properties": { +- "state": {"enum": ["succeeded", "failed", "unknown"]}, +- "is_error": {"type": "boolean"}, +- "exit_code": {"type": "integer"} ++ "state": { ++ "enum": [ ++ "succeeded", ++ "failed", ++ "unknown" ++ ] ++ }, ++ "is_error": { ++ "type": "boolean" ++ }, ++ "exit_code": { ++ "type": "integer" ++ } + } + }, + "fields": { + "type": "array", + "items": { + "type": "object", +- "required": ["label", "value"], ++ "required": [ ++ "label", ++ "value" ++ ], + "additionalProperties": false, + "properties": { +- "label": {"type": "string"}, +- "value": {"type": "string"} ++ "label": { ++ "type": "string" ++ }, ++ "value": { ++ "type": "string" ++ } + } + } + }, + "previews": { + "type": "array", +- "items": {"$ref": "#/$defs/preview"} ++ "items": { ++ "$ref": "#/$defs/preview" ++ } + }, + "raw_evidence": { + "type": "object", + "additionalProperties": false, + "properties": { +- "tool_input": {"type": "object"}, +- "tool_input_raw": {"type": "string"}, +- "result_preview": {"$ref": "#/$defs/preview"} ++ "tool_input": { ++ "type": "object" ++ }, ++ "tool_input_raw": { ++ "type": "string" ++ }, ++ "result_preview": { ++ "$ref": "#/$defs/preview" ++ } + } + }, +- "caveats": {"type": "array", "items": {"type": "string"}} ++ "caveats": { ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } + }, + "$defs": { + "preview": { + "type": "object", + "required": [ +- "kind", "text", "line_count", "omitted_lines", "omitted_characters", +- "truncated", "strategy", "encoding_replacements" ++ "kind", ++ "text", ++ "line_count", ++ "omitted_lines", ++ "omitted_characters", ++ "truncated", ++ "strategy", ++ "encoding_replacements" + ], + "additionalProperties": false, + "properties": { +- "kind": {"type": "string"}, +- "text": {"type": "string"}, +- "line_count": {"type": "integer", "minimum": 0}, +- "omitted_lines": {"type": "integer", "minimum": 0}, +- "omitted_characters": {"type": "integer", "minimum": 0}, +- "truncated": {"type": "boolean"}, +- "strategy": {"enum": ["full", "head_tail", "character_bounded"]}, +- "encoding_replacements": {"type": "integer", "minimum": 0} ++ "kind": { ++ "type": "string" ++ }, ++ "text": { ++ "type": "string" ++ }, ++ "line_count": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_lines": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_characters": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "truncated": { ++ "type": "boolean" ++ }, ++ "strategy": { ++ "enum": [ ++ "full", ++ "head_tail", ++ "character_bounded" ++ ] ++ }, ++ "encoding_replacements": { ++ "type": "integer", ++ "minimum": 0 ++ } + } + } + } +diff --git a/docs/schemas/semantic-transcript-v1.schema.json b/docs/schemas/semantic-transcript-v1.schema.json +new file mode 100644 +index 0000000000000000000000000000000000000000..b1aa66bdc5a26705e3126f82cf42f69a8ce7f59c +--- /dev/null ++++ b/docs/schemas/semantic-transcript-v1.schema.json +@@ -0,0 +1,490 @@ ++{ ++ "$schema": "https://json-schema.org/draft/2020-12/schema", ++ "$id": "https://polylogue.local/schemas/semantic-transcript-v1.schema.json", ++ "title": "Polylogue semantic transcript", ++ "type": "object", ++ "required": [ ++ "schema_version", ++ "session_id", ++ "entries" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "schema_version": { ++ "const": "semantic-transcript.v1" ++ }, ++ "session_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "entries": { ++ "type": "array", ++ "items": { ++ "$ref": "#/$defs/entry" ++ } ++ } ++ }, ++ "$defs": { ++ "card": { ++ "type": "object", ++ "required": [ ++ "schema_version", ++ "kind", ++ "title", ++ "source", ++ "fields", ++ "previews", ++ "caveats" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "schema_version": { ++ "const": "semantic-card.v1" ++ }, ++ "kind": { ++ "enum": [ ++ "shell", ++ "file_read", ++ "file_edit", ++ "search", ++ "web", ++ "task", ++ "mcp", ++ "lineage", ++ "attachment", ++ "fallback" ++ ] ++ }, ++ "title": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "summary": { ++ "type": "string" ++ }, ++ "source": { ++ "type": "object", ++ "required": [ ++ "session_id", ++ "provider_family" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "session_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "provider_family": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "origin": { ++ "type": "string" ++ }, ++ "message_id": { ++ "type": "string" ++ }, ++ "block_id": { ++ "type": "string" ++ }, ++ "block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "tool_name": { ++ "type": "string" ++ }, ++ "tool_id": { ++ "type": "string" ++ }, ++ "attachment_id": { ++ "type": "string" ++ }, ++ "material_origin": { ++ "type": "string" ++ }, ++ "occurred_at": { ++ "type": "string" ++ }, ++ "duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "parent_message_id": { ++ "type": "string" ++ }, ++ "variant_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "is_active_path": { ++ "type": "boolean" ++ }, ++ "is_active_leaf": { ++ "type": "boolean" ++ }, ++ "inherited_prefix": { ++ "type": "boolean" ++ }, ++ "result_message_id": { ++ "type": "string" ++ }, ++ "result_block_id": { ++ "type": "string" ++ }, ++ "result_block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "result_duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "result_material_origin": { ++ "type": "string" ++ }, ++ "result_inherited_prefix": { ++ "type": "boolean" ++ } ++ } ++ }, ++ "outcome": { ++ "type": "object", ++ "required": [ ++ "state" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "state": { ++ "enum": [ ++ "succeeded", ++ "failed", ++ "unknown" ++ ] ++ }, ++ "is_error": { ++ "type": "boolean" ++ }, ++ "exit_code": { ++ "type": "integer" ++ } ++ } ++ }, ++ "fields": { ++ "type": "array", ++ "items": { ++ "type": "object", ++ "required": [ ++ "label", ++ "value" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "label": { ++ "type": "string" ++ }, ++ "value": { ++ "type": "string" ++ } ++ } ++ } ++ }, ++ "previews": { ++ "type": "array", ++ "items": { ++ "$ref": "#/$defs/card/$defs/preview" ++ } ++ }, ++ "raw_evidence": { ++ "type": "object", ++ "additionalProperties": false, ++ "properties": { ++ "tool_input": { ++ "type": "object" ++ }, ++ "tool_input_raw": { ++ "type": "string" ++ }, ++ "result_preview": { ++ "$ref": "#/$defs/card/$defs/preview" ++ } ++ } ++ }, ++ "caveats": { ++ "type": "array", ++ "items": { ++ "type": "string" ++ } ++ } ++ }, ++ "$defs": { ++ "preview": { ++ "type": "object", ++ "required": [ ++ "kind", ++ "text", ++ "line_count", ++ "omitted_lines", ++ "omitted_characters", ++ "truncated", ++ "strategy", ++ "encoding_replacements" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "kind": { ++ "type": "string" ++ }, ++ "text": { ++ "type": "string" ++ }, ++ "line_count": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_lines": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "omitted_characters": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "truncated": { ++ "type": "boolean" ++ }, ++ "strategy": { ++ "enum": [ ++ "full", ++ "head_tail", ++ "character_bounded" ++ ] ++ }, ++ "encoding_replacements": { ++ "type": "integer", ++ "minimum": 0 ++ } ++ } ++ } ++ } ++ }, ++ "prose": { ++ "type": "object", ++ "required": [ ++ "message_id", ++ "role", ++ "message_type", ++ "provider_family", ++ "material_origin", ++ "text" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "message_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "role": { ++ "type": "string" ++ }, ++ "message_type": { ++ "type": "string" ++ }, ++ "provider_family": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "origin": { ++ "type": "string" ++ }, ++ "material_origin": { ++ "type": "string" ++ }, ++ "block_id": { ++ "type": "string" ++ }, ++ "block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "block_type": { ++ "type": "string" ++ }, ++ "occurred_at": { ++ "type": "string" ++ }, ++ "duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "parent_message_id": { ++ "type": "string" ++ }, ++ "variant_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "is_active_path": { ++ "type": "boolean" ++ }, ++ "is_active_leaf": { ++ "type": "boolean" ++ }, ++ "inherited_prefix": { ++ "type": "boolean" ++ }, ++ "text": { ++ "type": "string" ++ }, ++ "language": { ++ "type": "string" ++ } ++ } ++ }, ++ "notice_source": { ++ "type": "object", ++ "required": [ ++ "message_id", ++ "block_index", ++ "block_type", ++ "role", ++ "message_type", ++ "provider_family", ++ "material_origin" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "message_id": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "role": { ++ "type": "string" ++ }, ++ "message_type": { ++ "type": "string" ++ }, ++ "provider_family": { ++ "type": "string", ++ "minLength": 1 ++ }, ++ "origin": { ++ "type": "string" ++ }, ++ "material_origin": { ++ "type": "string" ++ }, ++ "block_id": { ++ "type": "string" ++ }, ++ "block_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "block_type": { ++ "type": "string" ++ }, ++ "occurred_at": { ++ "type": "string" ++ }, ++ "duration_ms": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "parent_message_id": { ++ "type": "string" ++ }, ++ "variant_index": { ++ "type": "integer", ++ "minimum": 0 ++ }, ++ "is_active_path": { ++ "type": "boolean" ++ }, ++ "is_active_leaf": { ++ "type": "boolean" ++ }, ++ "inherited_prefix": { ++ "type": "boolean" ++ } ++ } ++ }, ++ "notice": { ++ "type": "object", ++ "required": [ ++ "kind", ++ "count", ++ "sources" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "kind": { ++ "const": "empty_thinking" ++ }, ++ "count": { ++ "type": "integer", ++ "minimum": 1 ++ }, ++ "sources": { ++ "type": "array", ++ "minItems": 1, ++ "items": { ++ "$ref": "#/$defs/notice_source" ++ } ++ } ++ } ++ }, ++ "entry": { ++ "oneOf": [ ++ { ++ "type": "object", ++ "required": [ ++ "entry_type", ++ "card" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "entry_type": { ++ "const": "card" ++ }, ++ "card": { ++ "$ref": "#/$defs/card" ++ } ++ } ++ }, ++ { ++ "type": "object", ++ "required": [ ++ "entry_type", ++ "prose" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "entry_type": { ++ "const": "prose" ++ }, ++ "prose": { ++ "$ref": "#/$defs/prose" ++ } ++ } ++ }, ++ { ++ "type": "object", ++ "required": [ ++ "entry_type", ++ "notice" ++ ], ++ "additionalProperties": false, ++ "properties": { ++ "entry_type": { ++ "const": "notice" ++ }, ++ "notice": { ++ "$ref": "#/$defs/notice" ++ } ++ } ++ } ++ ] ++ } ++ } ++} +diff --git a/docs/topology-status.md b/docs/topology-status.md +index d8e0e49740260b6a6a23dd76a91192d6d66d485e..fe7a55f34b9150a440addbc3132d7a1cb09c2ce5 100644 +--- a/docs/topology-status.md ++++ b/docs/topology-status.md +@@ -24,7 +24,7 @@ Generated by `devtools render topology-status`. Reads `docs/plans/topology-targe + | archive-semantic | — | 12 | 12 | 0 | 0 | + | archive-session | — | 18 | 18 | 0 | 0 | + | archive-viewport | — | 6 | 6 | 0 | 0 | +-| core-primitive | — | 26 | 26 | 0 | 0 | ++| core-primitive | — | 27 | 27 | 0 | 0 | + + ### Summary + +@@ -32,8 +32,8 @@ Generated by `devtools render topology-status`. Reads `docs/plans/topology-targe + - **Kernel** (polylogue/ root): 7 + - **Primitives** (storage-root): 18 + - **TBD** (cell needs explicit assignment): 9 +-- **Total declared**: 1007 +-- **Realized polylogue/**/*.py**: 1007 files declared ++- **Total declared**: 1008 ++- **Realized polylogue/**/*.py**: 1008 files declared + + ### TBD cells (require explicit routing) + +diff --git a/polylogue/cli/messages.py b/polylogue/cli/messages.py +index 7288f4859243f83d208a272c8fe8aa417c52b187..93550428f2b5f9dc0f38248d04b2ce5612829d1a 100644 +--- a/polylogue/cli/messages.py ++++ b/polylogue/cli/messages.py +@@ -14,7 +14,7 @@ from polylogue.cli.shared.types import AppEnv + from polylogue.config import Config + from polylogue.rendering.semantic_cards import ( + build_semantic_transcript, +- lineage_descriptor_from_topology, ++ lineage_descriptor_from_session, + ) + from polylogue.rendering.semantic_markdown import render_semantic_transcript_markdown + from polylogue.surfaces.payloads import ( +@@ -103,13 +103,10 @@ def run_messages( + click.echo(_json.dumps(line)) + else: + # Keep archive access at the existing CLI orchestration seam. +- # Card construction below is pure and receives fully hydrated +- # messages plus an already-read topology descriptor. ++ # The renderer receives bounded facts from the already-read ++ # session row rather than hydrating a whole lineage family. + session = await api.get_session(session_id) +- topology = await api.get_session_topology(session_id) +- lineage = ( +- lineage_descriptor_from_topology(topology, session_id=session_id) if topology is not None else None +- ) ++ lineage = lineage_descriptor_from_session(session) if session is not None else None + transcript = build_semantic_transcript( + messages, + session_id=session_id, +diff --git a/polylogue/core/tool_identity.py b/polylogue/core/tool_identity.py +new file mode 100644 +index 0000000000000000000000000000000000000000..f68a12345cb2a70452fcde748e461284e09658e0 +--- /dev/null ++++ b/polylogue/core/tool_identity.py +@@ -0,0 +1,48 @@ ++"""Structural identities encoded in normalized tool names. ++ ++This module contains protocol-level parsing only. It deliberately does not ++classify what a tool *does* from prose or from an open provider namespace. ++""" ++ ++from __future__ import annotations ++ ++from dataclasses import dataclass ++ ++MCP_TOOL_PREFIX = "mcp__" ++ ++ ++@dataclass(frozen=True, slots=True) ++class MCPToolIdentity: ++ """The server/tool coordinates carried by an MCP tool name.""" ++ ++ raw_name: str ++ server: str ++ tool: str ++ ++ ++def parse_mcp_tool_name(tool_name: str | None) -> MCPToolIdentity | None: ++ """Parse ``mcp____`` without guessing missing segments. ++ ++ The tool segment may itself contain ``__``; only the first separator after ++ the prefix divides the server identity from the server-local tool name. ++ Malformed or non-MCP names return ``None`` so callers preserve an explicit ++ unknown/fallback state instead of inventing a server. ++ """ ++ ++ if not tool_name or not tool_name.startswith(MCP_TOOL_PREFIX): ++ return None ++ remainder = tool_name[len(MCP_TOOL_PREFIX) :] ++ server, separator, tool = remainder.partition("__") ++ if not separator or not server or not tool: ++ return None ++ return MCPToolIdentity(raw_name=tool_name, server=server, tool=tool) ++ ++ ++def extract_mcp_server(tool_name: str | None) -> str | None: ++ """Return the structurally encoded MCP server, when present.""" ++ ++ identity = parse_mcp_tool_name(tool_name) ++ return identity.server if identity is not None else None ++ ++ ++__all__ = ["extract_mcp_server", "MCP_TOOL_PREFIX", "MCPToolIdentity", "parse_mcp_tool_name"] +diff --git a/polylogue/daemon/http.py b/polylogue/daemon/http.py +index 63da0ce1d2f7af51ecc38669cf3afbe54e5b4f8b..487634b8b9262594ef9a601285ae9fb181c5ee2e 100644 +--- a/polylogue/daemon/http.py ++++ b/polylogue/daemon/http.py +@@ -73,6 +73,10 @@ from polylogue.rendering.semantic_card_placement import ( + SemanticCardPlacement, + semantic_card_placement_for_messages, + ) ++from polylogue.rendering.semantic_cards import ( ++ lineage_descriptor_from_archive_envelope, ++ lineage_descriptor_from_session, ++) + from polylogue.storage.sqlite.archive_tiers import ARCHIVE_VERSION_BY_TIER + from polylogue.storage.sqlite.archive_tiers.types import ArchiveTier + from polylogue.surfaces.payloads import ( +@@ -2850,7 +2854,10 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + # file-edit / task / attachment registry the CLI renders to Markdown + # (``cli/messages.py``), projected per-message for the web reader. + card_placement = semantic_card_placement_for_messages( +- conv.messages.to_list(), session_id=session_id, provider_family=conv.origin ++ conv.messages.to_list(), ++ session_id=session_id, ++ provider_family=conv.origin, ++ lineage=lineage_descriptor_from_session(conv), + ) + return { + "id": session_id, +@@ -2875,6 +2882,9 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "timestamp": msg.timestamp.isoformat() if msg.timestamp else None, + "message_type": _message_type_value(msg), + "material_origin": _material_origin_value(msg), ++ "duration_ms": msg.duration_ms, ++ "parent_message_id": msg.parent_id, ++ "variant_index": msg.branch_index, + "word_count": msg.word_count, + "has_tool_use": bool(msg.has_tool_use) if hasattr(msg, "has_tool_use") else False, + "has_thinking": bool(msg.has_thinking) if hasattr(msg, "has_thinking") else False, +@@ -2883,6 +2893,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + msg.text, + has_paste=bool(msg.has_paste) if hasattr(msg, "has_paste") else False, + ), ++ "semantic_entries": card_placement.entries_for(str(msg.id)), + "semantic_cards": card_placement.cards_for(str(msg.id)), + "semantic_card_suppressed": card_placement.is_suppressed(str(msg.id)), + "attachments": [ +@@ -2893,6 +2904,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + for msg in conv.messages + ], + "attachments": session_attachments, ++ "semantic_entries": list(card_placement.session_entries), + "tags": conv.tags, + "branch_type": str(conv.branch_type) if conv.branch_type else None, + "parent_id": str(conv.parent_id) if conv.parent_id else None, +@@ -2926,6 +2938,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + self._archive_message_payload( + session_id, + message, ++ semantic_entries=card_placement.entries_for(str(message.message_id)), + semantic_cards=card_placement.cards_for(str(message.message_id)), + semantic_card_suppressed=card_placement.is_suppressed(str(message.message_id)), + ) +@@ -2963,11 +2976,12 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "word_count": word_count, + "messages": messages, + "attachments": session_attachments, ++ "semantic_entries": list(card_placement.session_entries), + "tags": list(summary.tags), +- "branch_type": None, +- "parent_id": None, +- "repo": None, +- "cwd_display": None, ++ "branch_type": envelope.branch_type, ++ "parent_id": envelope.parent_session_id, ++ "repo": envelope.git_repository_url, ++ "cwd_display": next(iter(envelope.working_directories), None), + "model": None, + "flags": None, + "summary": None, +@@ -2975,21 +2989,13 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + } + + def _archive_semantic_card_placement(self, envelope: ArchiveSessionEnvelope) -> SemanticCardPlacement: +- """Build the same semantic-card index as the DB-backed path (#ap7). +- +- Reuses the archive→domain message conversion (``api.archive``) so the +- card registry sees the exact ``Message``/block shape the CLI and the +- DB-backed reader already build cards from — one registry, one +- classification path, two output backends. +- """ +- +- from polylogue.api.archive import _archive_message_to_domain +- from polylogue.core.enums import Origin ++ """Place the archive's exact composed rows through the shared renderer.""" + +- origin = Origin.from_string(envelope.origin) +- messages = [_archive_message_to_domain(message, origin=origin) for message in envelope.messages] + return semantic_card_placement_for_messages( +- messages, session_id=envelope.session_id, provider_family=envelope.origin ++ envelope.messages, ++ session_id=envelope.session_id, ++ provider_family=envelope.origin, ++ lineage=lineage_descriptor_from_archive_envelope(envelope), + ) + + def _archive_message_attachments(self, session_id: str, message: ArchiveMessageRow) -> list[dict[str, object]]: +@@ -3009,6 +3015,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + session_id: str, + message: ArchiveMessageRow, + *, ++ semantic_entries: Sequence[JSONDocument] = (), + semantic_cards: Sequence[JSONDocument] = (), + semantic_card_suppressed: bool = False, + ) -> dict[str, object]: +@@ -3025,11 +3032,21 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "timestamp": message.occurred_at, + "message_type": message.message_type, + "material_origin": message.material_origin, ++ "duration_ms": message.duration_ms, ++ "parent_message_id": message.parent_message_id, ++ "variant_index": message.variant_index, ++ "is_active_path": message.is_active_path, ++ "is_active_leaf": message.is_active_leaf, ++ "source_session_id": message.source_session_id, ++ "inherited_prefix": ( ++ message.source_session_id != session_id if message.source_session_id is not None else None ++ ), + "word_count": message.word_count, + "has_tool_use": bool(message.has_tool_use), + "has_thinking": bool(message.has_thinking), + "has_paste_evidence": has_paste, + "paste_spans": envelope_paste_spans(text, has_paste=has_paste), ++ "semantic_entries": list(semantic_entries), + "semantic_cards": list(semantic_cards), + "semantic_card_suppressed": semantic_card_suppressed, + "attachments": self._archive_message_attachments(session_id, message), +@@ -3914,6 +3931,14 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + offset=offset, + ) + session_id = str(conv_id) ++ session = await poly.get_session(conv_id) ++ source_messages = session.messages.to_list() if session is not None else messages ++ placement = semantic_card_placement_for_messages( ++ source_messages, ++ session_id=session_id, ++ provider_family=session.origin if session is not None else None, ++ lineage=lineage_descriptor_from_session(session) if session is not None else None, ++ ) + return { + "messages": [ + { +@@ -3926,10 +3951,25 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + "timestamp": msg.timestamp.isoformat() if msg.timestamp else None, + "message_type": _message_type_value(msg), + "material_origin": _material_origin_value(msg), ++ "duration_ms": msg.duration_ms, ++ "parent_message_id": msg.parent_id, ++ "variant_index": msg.branch_index, + "word_count": msg.word_count, ++ "has_tool_use": bool(msg.has_tool_use), ++ "has_thinking": bool(msg.has_thinking), ++ "has_paste_evidence": bool(msg.has_paste), ++ "paste_spans": envelope_paste_spans(msg.text, has_paste=bool(msg.has_paste)), ++ "semantic_entries": placement.entries_for(str(msg.id)), ++ "semantic_cards": placement.cards_for(str(msg.id)), ++ "semantic_card_suppressed": placement.is_suppressed(str(msg.id)), ++ "attachments": [ ++ attachment_to_envelope(att, session_id=session_id, message_id=msg.id) ++ for att in (msg.attachments or []) ++ ], + } + for msg in messages + ], ++ "semantic_entries": list(placement.session_entries), + "total": total, + "limit": limit, + "offset": offset, +@@ -3952,8 +3992,19 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + return {"messages": [], "total": 0, "limit": limit, "offset": offset} + messages = list(envelope.messages) + page = messages[offset : offset + limit] ++ placement = self._archive_semantic_card_placement(envelope) + return { +- "messages": [self._archive_message_payload(envelope.session_id, message) for message in page], ++ "messages": [ ++ self._archive_message_payload( ++ envelope.session_id, ++ message, ++ semantic_entries=placement.entries_for(str(message.message_id)), ++ semantic_cards=placement.cards_for(str(message.message_id)), ++ semantic_card_suppressed=placement.is_suppressed(str(message.message_id)), ++ ) ++ for message in page ++ ], ++ "semantic_entries": list(placement.session_entries), + "total": len(messages), + "limit": limit, + "offset": offset, +diff --git a/polylogue/daemon/web_shell.py b/polylogue/daemon/web_shell.py +index a6f88d659be1244fba3831b9c77a9c1c37c12b7d..f20c9f4f1babe8ff1adb2be5c93f5d831f62b1e9 100644 +--- a/polylogue/daemon/web_shell.py ++++ b/polylogue/daemon/web_shell.py +@@ -1017,6 +1017,7 @@ async function loadSession(id, updateURL) { + var payloads = await Promise.all([fetchJSON(route, {timeoutMs: 10000}), fetchJSON(messagesRoute, {timeoutMs: 10000})]); + var data = payloads[0]; + data.messages = payloads[1].messages || []; ++ data.semantic_entries = payloads[1].semantic_entries || data.semantic_entries || []; + data.total = payloads[1].total; + // Prefix-sharing sessions persist only their divergent tail in the + // summary row, while the message route recomposes the logical transcript. +@@ -1061,6 +1062,7 @@ async function loadMoreSessionMessages() { + }); + c.total = payload.total; + c.message_page = {limit: payload.limit, offset: payload.offset}; ++ if (Array.isArray(payload.semantic_entries)) c.semantic_entries = payload.semantic_entries; + } catch(e) { + c.messagePageError = routeErrorDetails(e, route); + } +@@ -2060,12 +2062,15 @@ function renderMain() { + msgEl.innerHTML = readViewSelector + renderReadViewExecution(c, state.selectedReadView); + return; + } ++ var sessionSemanticEntries = semanticSessionEntriesHtml(c); + if (!c.messages) { +- msgEl.innerHTML = readViewSelector + '

Loading messages...

'; ++ msgEl.innerHTML = readViewSelector + sessionSemanticEntries ++ + '

Loading messages...

'; + return; + } + if (c.messages.length === 0) { +- msgEl.innerHTML = readViewSelector + '

No messages

This session has no message content.

'; ++ msgEl.innerHTML = readViewSelector + sessionSemanticEntries ++ + '

No messages

This session has no message content.

'; + return; + } + var pageControl = ''; +@@ -2077,7 +2082,8 @@ function renderMain() { + pageControl = '

' + esc(String(c.messages.length)) + ' of ' + esc(String(c.total)) + + ' messages loaded.

'; + } +- msgEl.innerHTML = readViewSelector + renderEvidenceStrip(c) + messageBlocksHtml(c.messages) + pageControl; ++ msgEl.innerHTML = readViewSelector + sessionSemanticEntries + renderEvidenceStrip(c) ++ + messageBlocksHtml(c.messages) + pageControl; + } + + function renderReadViewExecution(c, viewId) { +@@ -2223,10 +2229,14 @@ function renderCorrelationReadView(payload) { + return html; + } + ++function semanticSessionEntriesHtml(session) { ++ if (typeof _polySemanticSessionEntriesHtml !== 'function') return ''; ++ return _polySemanticSessionEntriesHtml(session); ++} ++ + function messageBlocksHtml(messages) { +- // Delegates to the MK3 reader slice (#1202) which owns per-message +- // action rails, fold policies (tool / thinking / code), and the +- // keyboard-focused message card. Defined in web_shell_reader.py. ++ // Delegates framing to the MK3 reader slice while semantic entry ++ // classification remains authoritative in rendering/semantic_cards.py. + return renderMessageBlocks(messages); + } + +diff --git a/polylogue/daemon/web_shell_reader.py b/polylogue/daemon/web_shell_reader.py +index 939ab221e042804c384edd3e20e2a6f5835385e9..dd9406bcae439d52947d8b5ff92b5a4cfac1d6c1 100644 +--- a/polylogue/daemon/web_shell_reader.py ++++ b/polylogue/daemon/web_shell_reader.py +@@ -399,14 +399,18 @@ function _polyIsThinking(m) { + || m.has_thinking === true; + } + ++function _polySemanticEntriesForMessage(m) { ++ // The daemon attaches the shared semantic transcript projection to every ++ // message. Presence of ``semantic_entries`` is authoritative even when the ++ // array is empty: an empty array means the message was structurally absorbed ++ // into another entry, not that this leaf should re-classify raw role/text. ++ if (!m || !Array.isArray(m.semantic_entries)) return null; ++ if (typeof _polySemanticEntriesHtml !== 'function') return null; ++ return _polySemanticEntriesHtml(m); ++} ++ + function _polySemanticCardsForMessage(m) { +- // Semantic transcript cards (#ap7): the daemon session-detail routes +- // attach ``semantic_cards``/``semantic_card_suppressed`` to every message +- // from the same registry the CLI renders to Markdown +- // (``rendering/semantic_cards.py``). ``_polySemanticCardsHtml`` is the +- // optional HTML backend for that registry (``web_shell_semantic_cards.py``) +- // — absent it, tool/thinking blocks fall back to the pre-#ap7 generic fold +- // so unknown/older payload shapes never render worse than before. ++ // Compatibility for older daemon payloads and isolated visual harnesses. + if (!m || !Array.isArray(m.semantic_cards) || !m.semantic_cards.length) return ''; + if (typeof _polySemanticCardsHtml !== 'function') return ''; + return _polySemanticCardsHtml(m); +@@ -421,9 +425,13 @@ function renderMessageBlocks(messages) { + // already inside that card's preview. + if (m && m.semantic_card_suppressed) return ''; + var role = (m.role || '').toLowerCase(); +- var isTool = _polyIsTool(m); +- var isThinking = _polyIsThinking(m); +- var semanticCardsHtml = _polySemanticCardsForMessage(m); ++ var semanticEntriesHtml = _polySemanticEntriesForMessage(m); ++ var hasSemanticEntries = semanticEntriesHtml !== null; ++ // Raw-role classification remains only as a compatibility fallback for ++ // payloads produced before semantic-transcript.v1. ++ var isTool = !hasSemanticEntries && _polyIsTool(m); ++ var isThinking = !hasSemanticEntries && _polyIsThinking(m); ++ var semanticCardsHtml = hasSemanticEntries ? '' : _polySemanticCardsForMessage(m); + var tsHtml = m.timestamp + ? '' + + new Date(m.timestamp).toLocaleTimeString() + '' +@@ -435,7 +443,8 @@ function renderMessageBlocks(messages) { + if (isTool) blockClass += ' tool-block'; + var anchor = m.anchor || ('message-' + (m.id || '')); + var body; +- if (semanticCardsHtml) body = semanticCardsHtml; ++ if (hasSemanticEntries) body = semanticEntriesHtml; ++ else if (semanticCardsHtml) body = semanticCardsHtml; + else if (isTool) body = _polyToolFoldHtml(m); + else if (isThinking) body = _polyThinkingFoldHtml(m); + else if (typeof _polyHasPaste === 'function' && _polyHasPaste(m)) { +@@ -446,7 +455,7 @@ function renderMessageBlocks(messages) { + // MK3 attachment strip (#1199). Prepended above the body so the + // cards stay visible regardless of fold variant. The hook stays + // optional because not every message renderer includes attachments. +- var attachmentStrip = (typeof _polyAttachmentStripHtml === 'function') ++ var attachmentStrip = (!hasSemanticEntries && typeof _polyAttachmentStripHtml === 'function') + ? _polyAttachmentStripHtml(m) : ''; + if (attachmentStrip) body = attachmentStrip + body; + var rail = _polyActionRailHtml(m, convId); +diff --git a/polylogue/daemon/web_shell_semantic_cards.py b/polylogue/daemon/web_shell_semantic_cards.py +index b059b2d09d6e109062c1e2fc0d0e02908c725b74..200f6fb80bba4ac1b33d53c400af254744c74309 100644 +--- a/polylogue/daemon/web_shell_semantic_cards.py ++++ b/polylogue/daemon/web_shell_semantic_cards.py +@@ -1,25 +1,15 @@ +-"""Web reader rendering for provider-neutral semantic transcript cards (#ap7). +- +-The daemon session-detail routes (``daemon/http.py``, +-``_do_get_session``/``_do_archive_get_session``) attach a ``semantic_cards`` +-array (and a ``semantic_card_suppressed`` flag) to every message payload, +-built by :mod:`polylogue.rendering.semantic_card_placement` from the exact +-same :class:`polylogue.rendering.semantic_card_models.SemanticCard` registry +-the CLI renders to Markdown (``cli/messages.py`` / +-``rendering/semantic_markdown.py``). This module is the HTML backend for that +-one shared registry: it turns ``SemanticCard.to_document()`` JSON straight +-into DOM, with no independent tool-classification logic of its own. +- +-Follows the existing reader-slice pattern (``web_shell_reader.py``, +-``web_shell_attachments.py``): a CSS block, a JS block installed as a +-``_polyXxxHtml`` hook that ``renderMessageBlocks`` calls when present, wired +-into ``web_shell.py`` through ``__SEMANTIC_CARD_CSS__``/``__SEMANTIC_CARD_JS__`` +-placeholders. ++"""HTML backend for the provider-neutral semantic transcript document. ++ ++Daemon routes attach ``semantic_entries`` produced by the same pure renderer ++that the CLI presents as Markdown. This leaf formats those typed documents; it ++does not classify raw roles, prose, tool names, or outcomes. + """ + + from __future__ import annotations + + SEMANTIC_CARD_CSS = r""" ++.sem-entries { display: flex; flex-direction: column; gap: 6px; margin: 4px 0; } ++.sem-session-entries { margin: 0 10px 8px; } + .sem-cards { display: flex; flex-direction: column; gap: 6px; margin: 4px 0; } + .sem-card { + border: 1px solid var(--border); border-radius: var(--radius); +@@ -27,8 +17,12 @@ SEMANTIC_CARD_CSS = r""" + border-left: 3px solid var(--role-tool); + } + .sem-card.sem-card-shell { border-left-color: var(--accent); } ++.sem-card.sem-card-file_read { border-left-color: var(--accent-soft); } + .sem-card.sem-card-file_edit { border-left-color: var(--active); } ++.sem-card.sem-card-search { border-left-color: var(--role-user); } ++.sem-card.sem-card-web { border-left-color: var(--role-user); } + .sem-card.sem-card-task { border-left-color: var(--warn); } ++.sem-card.sem-card-mcp { border-left-color: var(--role-tool); } + .sem-card.sem-card-lineage { border-left-color: var(--text-dim); } + .sem-card.sem-card-attachment { border-left-color: var(--text-dim); } + .sem-card.sem-card-fallback { border-left-color: var(--border-strong); } +@@ -76,15 +70,30 @@ SEMANTIC_CARD_CSS = r""" + margin: 4px 0 0; padding-left: 16px; font-size: var(--small); color: var(--text-dim); + } + .sem-card-caveats li { margin: 1px 0; } ++.sem-source { margin-top: 4px; font-size: 10px; color: var(--text-dim); } ++.sem-source summary { cursor: pointer; user-select: none; } ++.sem-source code { display: block; margin-top: 2px; white-space: pre-wrap; word-break: break-word; } ++.sem-prose { min-width: 0; } ++.sem-prose-meta { ++ display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 3px; ++ font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); ++} ++.sem-prose-meta .chip { padding: 0 4px; } ++.sem-notice { ++ border: 1px dashed var(--border-strong); border-radius: var(--radius); ++ background: var(--panel-subtle); color: var(--role-thinking); ++ padding: 5px 9px; font-size: var(--small); ++} ++.sem-notice strong { font-family: var(--font-mono); font-weight: 500; } ++.sem-notice details { margin-top: 2px; } ++.sem-notice code { display: block; white-space: pre-wrap; word-break: break-word; } + """ + + +-# Drop-in renderer for the ``renderMessageBlocks`` semantic-card hooks. +-# Exposes ``_polySemanticCardsHtml(m)`` (the public hook name the reader +-# slice looks up) plus its private helpers. + SEMANTIC_CARD_JS = r""" + var SEM_CARD_LABEL = { +- shell: 'shell', file_edit: 'edit', task: 'task', lineage: 'lineage', ++ shell: 'shell', file_read: 'read', file_edit: 'edit', search: 'search', ++ web: 'web', task: 'task', mcp: 'mcp', lineage: 'lineage', + attachment: 'attachment', fallback: 'tool' + }; + +@@ -103,6 +112,7 @@ function _polySemCardOutcomeHtml(card) { + var state = card.outcome.state || 'unknown'; + var label = state === 'succeeded' ? 'ok' : (state === 'failed' ? 'FAILED' : 'unknown'); + var bits = []; ++ if (typeof card.outcome.is_error === 'boolean') bits.push('is_error=' + card.outcome.is_error); + if (typeof card.outcome.exit_code === 'number') bits.push('exit ' + card.outcome.exit_code); + var title = bits.length ? (label + ' (' + bits.join(', ') + ')') : label; + return '' +@@ -120,7 +130,9 @@ function _polySemCardFieldsHtml(card) { + function _polySemCardPreviewHtml(preview) { + var lines = preview.line_count || 0; + var meta = lines + ' line' + (lines === 1 ? '' : 's'); +- if (preview.truncated) meta += ', truncated'; ++ if (preview.omitted_lines) meta += ', ' + preview.omitted_lines + ' omitted'; ++ if (preview.omitted_characters) meta += ', ' + preview.omitted_characters + ' chars omitted'; ++ if (preview.encoding_replacements) meta += ', ' + preview.encoding_replacements + ' replacements'; + var body = preview.kind === 'diff' + ? '
' + _polyDiffLinesHtml(preview.text) + '
' + : '
' + esc(preview.text || '') + '
'; +@@ -142,6 +154,26 @@ function _polySemCardCaveatsHtml(card) { + }).join('') + ''; + } + ++function _polySemSourceBits(source) { ++ source = source || {}; ++ var keys = [ ++ 'session_id', 'provider_family', 'origin', 'message_id', 'block_id', 'block_index', ++ 'tool_name', 'tool_id', 'attachment_id', 'material_origin', 'occurred_at', 'duration_ms', ++ 'parent_message_id', 'variant_index', 'is_active_path', 'is_active_leaf', 'inherited_prefix', ++ 'result_message_id', 'result_block_id', 'result_block_index', 'result_duration_ms', ++ 'result_material_origin', 'result_inherited_prefix' ++ ]; ++ return keys.filter(function(key) { return source[key] !== undefined && source[key] !== null; }) ++ .map(function(key) { return key + '=' + String(source[key]); }); ++} ++ ++function _polySemSourceHtml(source) { ++ var bits = _polySemSourceBits(source); ++ if (!bits.length) return ''; ++ return '
evidence' ++ + esc(bits.join('\n')) + '
'; ++} ++ + function _polySemCardAnchor(card) { + var src = card.source || {}; + var token = src.block_id || src.result_block_id +@@ -178,10 +210,104 @@ function _polySemanticCardHtml(card) { + + _polySemCardFieldsHtml(card) + + previewsHtml + + _polySemCardCaveatsHtml(card) ++ + _polySemSourceHtml(card.source) + + ''; + } + +-// Exposed under the public hook name renderMessageBlocks looks up. ++function _polySemProseMetaBits(prose) { ++ var bits = []; ++ if (prose.block_type) bits.push(prose.block_type); ++ if (prose.language) bits.push(prose.language); ++ if (prose.material_origin) bits.push('material:' + prose.material_origin); ++ if (prose.variant_index !== undefined && prose.variant_index !== null) bits.push('variant:' + prose.variant_index); ++ if (prose.is_active_path === true) bits.push('active path'); ++ if (prose.is_active_leaf === true) bits.push('active leaf'); ++ if (prose.inherited_prefix === true) bits.push('inherited prefix'); ++ return bits; ++} ++ ++function _polySemanticProseHtml(prose, entryIndex) { ++ prose = prose || {}; ++ var type = prose.block_type || ''; ++ var text = prose.text || ''; ++ var metaBits = _polySemProseMetaBits(prose); ++ var meta = metaBits.length ++ ? '
' + metaBits.map(function(bit) { ++ return '' + esc(bit) + ''; ++ }).join('') + '
' ++ : ''; ++ var body; ++ if (type === 'thinking' || type === 'reasoning') { ++ body = '' ++ + '
' ++ + '[' + esc(type) + ']' ++ + 'typed block' ++ + '
' ++ + '
' + esc(text) + '
'; ++ } else if (type === 'code') { ++ body = _polyCodeBlockHtml({lang: prose.language || 'code', body: text}, entryIndex || 0); ++ } else { ++ body = text ? '
' + esc(text) + '
' : ''; ++ } ++ var sourceBits = _polySemSourceBits({ ++ provider_family: prose.provider_family, ++ origin: prose.origin, ++ message_id: prose.message_id, ++ block_id: prose.block_id, ++ block_index: prose.block_index, ++ material_origin: prose.material_origin, ++ occurred_at: prose.occurred_at, ++ duration_ms: prose.duration_ms, ++ parent_message_id: prose.parent_message_id, ++ variant_index: prose.variant_index, ++ is_active_path: prose.is_active_path, ++ is_active_leaf: prose.is_active_leaf, ++ inherited_prefix: prose.inherited_prefix ++ }); ++ var source = sourceBits.length ++ ? '
source' + esc(sourceBits.join('\n')) + '
' ++ : ''; ++ return '
' ++ + meta + body + source + '
'; ++} ++ ++function _polySemanticNoticeHtml(notice) { ++ notice = notice || {}; ++ var count = Number(notice.count || ((notice.sources || []).length)); ++ var label = notice.kind === 'empty_thinking' ? 'thinking absent' : (notice.kind || 'notice'); ++ var refs = (notice.sources || []).map(function(source) { ++ var coord = source.block_id || ('index:' + source.block_index); ++ return 'message:' + source.message_id + '/block:' + coord + '/type:' + source.block_type; ++ }); ++ var detail = refs.length ++ ? '
' + esc(String(count)) + ' source coordinate' + (count === 1 ? '' : 's') ++ + '' + esc(refs.join('\n')) + '
' ++ : ''; ++ return '
' ++ + '' + esc(label) + ' · ' + esc(String(count)) + ' typed block' + (count === 1 ? '' : 's') ++ + detail + '
'; ++} ++ ++function _polySemanticEntryHtml(entry, entryIndex) { ++ if (!entry) return ''; ++ if (entry.entry_type === 'card' && entry.card) return _polySemanticCardHtml(entry.card); ++ if (entry.entry_type === 'prose' && entry.prose) return _polySemanticProseHtml(entry.prose, entryIndex); ++ if (entry.entry_type === 'notice' && entry.notice) return _polySemanticNoticeHtml(entry.notice); ++ return ''; ++} ++ ++function _polySemanticEntriesHtml(m) { ++ if (!m || !Array.isArray(m.semantic_entries)) return ''; ++ return '
' + m.semantic_entries.map(_polySemanticEntryHtml).join('') + '
'; ++} ++ ++function _polySemanticSessionEntriesHtml(c) { ++ if (!c || !Array.isArray(c.semantic_entries) || !c.semantic_entries.length) return ''; ++ return '
' ++ + c.semantic_entries.map(_polySemanticEntryHtml).join('') + '
'; ++} ++ ++// Compatibility hook for consumers that still request card-only payloads. + function _polySemanticCardsHtml(m) { + if (!m || !Array.isArray(m.semantic_cards) || !m.semantic_cards.length) return ''; + return '
' + m.semantic_cards.map(_polySemanticCardHtml).join('') + '
'; +diff --git a/polylogue/insights/tool_usage.py b/polylogue/insights/tool_usage.py +index 98e19cc8f7eab0d4c727b793ddf3a99503c6e96f..f88e18b879d03d9b480b4d148e35bcae1a132f30 100644 +--- a/polylogue/insights/tool_usage.py ++++ b/polylogue/insights/tool_usage.py +@@ -21,6 +21,7 @@ from __future__ import annotations + from collections.abc import Sequence + from typing import TYPE_CHECKING + ++from polylogue.core.tool_identity import extract_mcp_server + from polylogue.insights.archive import PaginatedInsightQuery + from polylogue.insights.archive_models import ( + ARCHIVE_INSIGHT_CONTRACT_VERSION, +@@ -37,28 +38,6 @@ if TYPE_CHECKING: + TOOL_USAGE_INSIGHT_VERSION = 1 + """Insight materializer version for ``tool_usage`` envelopes.""" + +-_MCP_TOOL_PREFIX = "mcp__" +- +- +-def extract_mcp_server(tool_name: str) -> str | None: +- """Return the MCP server identity prefix of a tool name, if any. +- +- MCP tool names follow the ``mcp____`` convention used by +- Claude Code, Codex, and the Polylogue MCP catalog. The server segment +- is the second underscored component. ``None`` is returned for tool +- names that do not match this shape so downstream callers can keep the +- coverage signal honest instead of inventing a synthetic server. +- """ +- +- if not tool_name or not tool_name.startswith(_MCP_TOOL_PREFIX): +- return None +- remainder = tool_name[len(_MCP_TOOL_PREFIX) :] +- if "__" not in remainder: +- return None +- server, _, _ = remainder.partition("__") +- return server or None +- +- + class ToolUsageEntry(ArchiveInsightModel): + """One per-(origin, tool, action_kind) rollup row.""" + +diff --git a/polylogue/rendering/block_models.py b/polylogue/rendering/block_models.py +index f3bef291e4185cf39cbfb3b566ace510a65badae..412e873467d757c4924f491afe3d733fb5e15244 100644 +--- a/polylogue/rendering/block_models.py ++++ b/polylogue/rendering/block_models.py +@@ -7,7 +7,6 @@ from collections.abc import Mapping, Sequence + from dataclasses import dataclass + + from polylogue.core.json import JSONDocument, json_document +-from polylogue.storage.runtime import BlockRecord + + + @dataclass(frozen=True, slots=True) +@@ -68,6 +67,35 @@ def _object_mapping_with_string_keys(value: object) -> dict[str, object] | None: + return normalized + + ++def _block_attribute_mapping(value: object) -> dict[str, object] | None: ++ """Project a block-shaped dataclass/model without importing its layer.""" ++ ++ block_type = getattr(value, "type", getattr(value, "block_type", None)) ++ if block_type is None: ++ return None ++ result: dict[str, object] = {"type": getattr(block_type, "value", block_type)} ++ attribute_names = ( ++ "text", ++ "language", ++ "url", ++ "name", ++ "mime_type", ++ "block_id", ++ "tool_name", ++ "tool_id", ++ "tool_input", ++ "metadata", ++ "semantic_type", ++ "tool_result_is_error", ++ "tool_result_exit_code", ++ ) ++ for name in attribute_names: ++ candidate = getattr(value, name, None) ++ if candidate is not None: ++ result[name] = getattr(candidate, "value", candidate) ++ return result ++ ++ + def _optional_string(value: object) -> str | None: + return value if isinstance(value, str) else None + +@@ -150,8 +178,13 @@ def _coerce_optional_int(value: object) -> int | None: + def _coerce_mapping_block(block: Mapping[str, object]) -> RenderableBlock: + block_type = str(block.get("type", "text")) + source = _mapping_with_string_keys(block.get("source")) or {} +- metadata = _mapping_with_string_keys(block.get("metadata")) ++ raw_metadata = block.get("metadata") ++ metadata = _mapping_with_string_keys(raw_metadata) ++ if metadata is None and isinstance(raw_metadata, str): ++ metadata = _json_mapping(raw_metadata) + name = block.get("name") or block.get("title") ++ if name is None and metadata is not None: ++ name = metadata.get("name") + text, replacements = _first_text( + block.get("thinking"), + block.get("text"), +@@ -171,10 +204,19 @@ def _coerce_mapping_block(block: Mapping[str, object]) -> RenderableBlock: + return RenderableBlock( + type=block_type, + text=text, +- language=_optional_string(block.get("language")), +- url=_first_string(block.get("url"), source.get("url")), ++ language=_first_string(block.get("language"), metadata.get("language") if metadata is not None else None), ++ url=_first_string( ++ block.get("url"), ++ source.get("url"), ++ metadata.get("url") if metadata is not None else None, ++ ), + name=str(name) if isinstance(name, str) else None, +- mime_type=_first_string(block.get("media_type"), block.get("mime_type")), ++ mime_type=_first_string( ++ block.get("media_type"), ++ block.get("mime_type"), ++ metadata.get("media_type") if metadata is not None else None, ++ metadata.get("mime_type") if metadata is not None else None, ++ ), + block_id=_first_string(block.get("block_id"), block.get("id")), + tool_name=tool_name, + tool_id=_first_string(block.get("tool_use_id"), block.get("tool_id"), block.get("id")), +@@ -188,38 +230,13 @@ def _coerce_mapping_block(block: Mapping[str, object]) -> RenderableBlock: + ) + + +-def _coerce_record_block(block: BlockRecord) -> RenderableBlock: +- metadata = _json_mapping(block.metadata) +- mime_type = _optional_string(metadata.get("media_type")) if metadata is not None else None +- text, replacements = _optional_text(block.text) +- tool_input = _json_mapping(block.tool_input) +- return RenderableBlock( +- type=block.type.value, +- text=text, +- language=_optional_string(metadata.get("language")) if metadata is not None else None, +- url=_optional_string(metadata.get("url")) if metadata is not None else None, +- name=_optional_string(metadata.get("name")) if metadata is not None else None, +- mime_type=mime_type, +- block_id=block.block_id, +- tool_name=block.tool_name, +- tool_id=block.tool_id, +- tool_input=tool_input, +- tool_input_raw=block.tool_input if block.tool_input and tool_input is None else None, +- metadata=metadata, +- semantic_type=block.semantic_type.value if block.semantic_type is not None else None, +- tool_result_is_error=_coerce_optional_bool(block.tool_result_is_error), +- tool_result_exit_code=block.tool_result_exit_code, +- text_encoding_replacements=replacements, +- ) +- +- + def coerce_renderable_block(value: object) -> RenderableBlock | None: + """Normalize arbitrary block payloads into the rendering contract.""" + if isinstance(value, RenderableBlock): + return value +- if isinstance(value, BlockRecord): +- return _coerce_record_block(value) + mapping = _object_mapping_with_string_keys(value) ++ if mapping is None: ++ mapping = _block_attribute_mapping(value) + if mapping is not None: + return _coerce_mapping_block(mapping) + return None +diff --git a/polylogue/rendering/semantic_card_models.py b/polylogue/rendering/semantic_card_models.py +index c629ab91c3c13d1a8f59b1ad1f5a8af2f8625a62..d7ce81177cb827caafacdab500badbce850b84bb 100644 +--- a/polylogue/rendering/semantic_card_models.py ++++ b/polylogue/rendering/semantic_card_models.py +@@ -1,4 +1,4 @@ +-"""Pure data contracts for provider-neutral semantic transcript cards.""" ++"""Pure data contracts for provider-neutral semantic transcripts.""" + + from __future__ import annotations + +@@ -9,14 +9,19 @@ from typing import Literal + from polylogue.core.json import JSONDocument, is_json_value + + CARD_SCHEMA_VERSION: Literal["semantic-card.v1"] = "semantic-card.v1" ++TRANSCRIPT_SCHEMA_VERSION: Literal["semantic-transcript.v1"] = "semantic-transcript.v1" + + + class SemanticCardKind(str, Enum): + """Closed card vocabulary shared by CLI and web payloads.""" + + SHELL = "shell" ++ FILE_READ = "file_read" + FILE_EDIT = "file_edit" ++ SEARCH = "search" ++ WEB = "web" + TASK = "task" ++ MCP = "mcp" + LINEAGE = "lineage" + ATTACHMENT = "attachment" + FALLBACK = "fallback" +@@ -38,34 +43,82 @@ class PreviewStrategy(str, Enum): + CHARACTER_BOUNDED = "character_bounded" + + ++class SemanticNoticeKind(str, Enum): ++ """Typed compact notices that retain otherwise-empty evidence.""" ++ ++ EMPTY_THINKING = "empty_thinking" ++ ++ ++class LineageAvailability(str, Enum): ++ """How much lineage authority the supplied descriptor carries.""" ++ ++ COMPLETE = "complete" ++ PARTIAL = "partial" ++ UNAVAILABLE = "unavailable" ++ ++ ++class LineageAuthority(str, Enum): ++ """Substrate that established the lineage descriptor.""" ++ ++ ARCHIVE_ENVELOPE = "archive_envelope" ++ SESSION_ROW = "session_row" ++ TOPOLOGY = "topology" ++ ++ + @dataclass(frozen=True, slots=True) + class SemanticCardSource: +- """Exact source coordinates retained by a semantic card.""" ++ """Exact source coordinates and message metadata retained by a card.""" + + session_id: str + provider_family: str = "unknown" ++ origin: str | None = None + message_id: str | None = None + block_id: str | None = None + block_index: int | None = None + tool_name: str | None = None + tool_id: str | None = None ++ attachment_id: str | None = None ++ material_origin: str | None = None ++ occurred_at: str | None = None ++ duration_ms: int | None = None ++ parent_message_id: str | None = None ++ variant_index: int | None = None ++ is_active_path: bool | None = None ++ is_active_leaf: bool | None = None ++ inherited_prefix: bool | None = None + result_message_id: str | None = None + result_block_id: str | None = None + result_block_index: int | None = None ++ result_duration_ms: int | None = None ++ result_material_origin: str | None = None ++ result_inherited_prefix: bool | None = None + + def to_document(self) -> JSONDocument: + return _without_none( + { + "session_id": self.session_id, + "provider_family": self.provider_family, ++ "origin": self.origin, + "message_id": self.message_id, + "block_id": self.block_id, + "block_index": self.block_index, + "tool_name": self.tool_name, + "tool_id": self.tool_id, ++ "attachment_id": self.attachment_id, ++ "material_origin": self.material_origin, ++ "occurred_at": self.occurred_at, ++ "duration_ms": self.duration_ms, ++ "parent_message_id": self.parent_message_id, ++ "variant_index": self.variant_index, ++ "is_active_path": self.is_active_path, ++ "is_active_leaf": self.is_active_leaf, ++ "inherited_prefix": self.inherited_prefix, + "result_message_id": self.result_message_id, + "result_block_id": self.result_block_id, + "result_block_index": self.result_block_index, ++ "result_duration_ms": self.result_duration_ms, ++ "result_material_origin": self.result_material_origin, ++ "result_inherited_prefix": self.result_inherited_prefix, + } + ) + +@@ -178,27 +231,48 @@ class SemanticCard: + + @dataclass(frozen=True, slots=True) + class LineageDescriptor: +- """Pure lineage input supplied by a caller that already read topology.""" ++ """Bounded lineage input supplied by a caller that already read authority.""" + + session_id: str +- root_session_id: str ++ provider_family: str = "unknown" ++ origin: str | None = None ++ root_session_id: str | None = None + parent_session_id: str | None = None + parent_native_id: str | None = None + relation: str = "unknown" +- resolved: bool = True ++ resolved: bool | None = None + cycle_detected: bool = False ++ lineage_complete: bool | None = None ++ lineage_truncation_reason: str | None = None ++ inherited_prefix: bool | None = None ++ branch_point_message_id: str | None = None ++ active_leaf_message_id: str | None = None ++ authority: LineageAuthority = LineageAuthority.SESSION_ROW ++ availability: LineageAvailability = LineageAvailability.PARTIAL + + + @dataclass(frozen=True, slots=True) + class TranscriptProse: +- """Prose entry retained alongside semantic cards.""" ++ """Typed prose/code/thinking entry with exact authoredness and placement.""" + + message_id: str + role: str + message_type: str + text: str ++ provider_family: str = "unknown" ++ origin: str | None = None ++ material_origin: str = "unknown" + block_id: str | None = None ++ block_index: int | None = None + block_type: str | None = None ++ language: str | None = None ++ occurred_at: str | None = None ++ duration_ms: int | None = None ++ parent_message_id: str | None = None ++ variant_index: int | None = None ++ is_active_path: bool | None = None ++ is_active_leaf: bool | None = None ++ inherited_prefix: bool | None = None + + def to_document(self) -> JSONDocument: + return _without_none( +@@ -206,43 +280,139 @@ class TranscriptProse: + "message_id": self.message_id, + "role": self.role, + "message_type": self.message_type, ++ "provider_family": self.provider_family, ++ "origin": self.origin, ++ "material_origin": self.material_origin, + "text": self.text, + "block_id": self.block_id, ++ "block_index": self.block_index, + "block_type": self.block_type, ++ "language": self.language, ++ "occurred_at": self.occurred_at, ++ "duration_ms": self.duration_ms, ++ "parent_message_id": self.parent_message_id, ++ "variant_index": self.variant_index, ++ "is_active_path": self.is_active_path, ++ "is_active_leaf": self.is_active_leaf, ++ "inherited_prefix": self.inherited_prefix, + } + ) + + ++@dataclass(frozen=True, slots=True) ++class TranscriptNoticeSource: ++ """One exact typed-block coordinate represented by a compact notice.""" ++ ++ message_id: str ++ block_index: int ++ block_type: str ++ block_id: str | None = None ++ role: str = "unknown" ++ message_type: str = "message" ++ provider_family: str = "unknown" ++ origin: str | None = None ++ material_origin: str = "unknown" ++ occurred_at: str | None = None ++ duration_ms: int | None = None ++ parent_message_id: str | None = None ++ variant_index: int | None = None ++ is_active_path: bool | None = None ++ is_active_leaf: bool | None = None ++ inherited_prefix: bool | None = None ++ ++ def to_document(self) -> JSONDocument: ++ return _without_none( ++ { ++ "message_id": self.message_id, ++ "block_index": self.block_index, ++ "block_type": self.block_type, ++ "block_id": self.block_id, ++ "role": self.role, ++ "message_type": self.message_type, ++ "provider_family": self.provider_family, ++ "origin": self.origin, ++ "material_origin": self.material_origin, ++ "occurred_at": self.occurred_at, ++ "duration_ms": self.duration_ms, ++ "parent_message_id": self.parent_message_id, ++ "variant_index": self.variant_index, ++ "is_active_path": self.is_active_path, ++ "is_active_leaf": self.is_active_leaf, ++ "inherited_prefix": self.inherited_prefix, ++ } ++ ) ++ ++ ++@dataclass(frozen=True, slots=True) ++class TranscriptNotice: ++ """Compact operator presentation that retains every source coordinate.""" ++ ++ kind: SemanticNoticeKind ++ sources: tuple[TranscriptNoticeSource, ...] ++ ++ def __post_init__(self) -> None: ++ if not self.sources: ++ raise ValueError("semantic transcript notice requires at least one source") ++ ++ @property ++ def count(self) -> int: ++ return len(self.sources) ++ ++ def to_document(self) -> JSONDocument: ++ return { ++ "kind": self.kind.value, ++ "count": self.count, ++ "sources": [source.to_document() for source in self.sources], ++ } ++ ++ + @dataclass(frozen=True, slots=True) + class SemanticTranscriptEntry: +- """Exactly one ordered prose or card entry.""" ++ """Exactly one ordered prose, card, or typed notice entry.""" + + prose: TranscriptProse | None = None + card: SemanticCard | None = None ++ notice: TranscriptNotice | None = None + + def __post_init__(self) -> None: +- if (self.prose is None) == (self.card is None): +- raise ValueError("semantic transcript entry requires exactly one of prose or card") ++ if sum(item is not None for item in (self.prose, self.card, self.notice)) != 1: ++ raise ValueError("semantic transcript entry requires exactly one of prose, card, or notice") ++ ++ @property ++ def primary_message_id(self) -> str | None: ++ if self.prose is not None: ++ return self.prose.message_id ++ if self.card is not None: ++ return self.card.source.message_id or self.card.source.result_message_id ++ assert self.notice is not None ++ return self.notice.sources[0].message_id + + def to_document(self) -> JSONDocument: + if self.card is not None: + return {"entry_type": "card", "card": self.card.to_document()} +- assert self.prose is not None +- return {"entry_type": "prose", "prose": self.prose.to_document()} ++ if self.prose is not None: ++ return {"entry_type": "prose", "prose": self.prose.to_document()} ++ assert self.notice is not None ++ return {"entry_type": "notice", "notice": self.notice.to_document()} + + + @dataclass(frozen=True, slots=True) + class SemanticTranscript: + session_id: str + entries: tuple[SemanticTranscriptEntry, ...] = field(default_factory=tuple) ++ schema_version: Literal["semantic-transcript.v1"] = TRANSCRIPT_SCHEMA_VERSION + + @property + def cards(self) -> tuple[SemanticCard, ...]: + return tuple(entry.card for entry in self.entries if entry.card is not None) + ++ @property ++ def notices(self) -> tuple[TranscriptNotice, ...]: ++ return tuple(entry.notice for entry in self.entries if entry.notice is not None) ++ + def to_document(self) -> JSONDocument: + return { +- "schema_version": "semantic-transcript.v1", ++ "schema_version": self.schema_version, + "session_id": self.session_id, + "entries": [entry.to_document() for entry in self.entries], + } +@@ -264,6 +434,8 @@ __all__ = [ + "CARD_SCHEMA_VERSION", + "card_json_documents", + "CardOutcomeState", ++ "LineageAuthority", ++ "LineageAvailability", + "LineageDescriptor", + "PreviewStrategy", + "SemanticCard", +@@ -273,7 +445,11 @@ __all__ = [ + "SemanticCardPreview", + "SemanticCardRawEvidence", + "SemanticCardSource", ++ "SemanticNoticeKind", + "SemanticTranscript", + "SemanticTranscriptEntry", ++ "TRANSCRIPT_SCHEMA_VERSION", ++ "TranscriptNotice", ++ "TranscriptNoticeSource", + "TranscriptProse", + ] +diff --git a/polylogue/rendering/semantic_card_placement.py b/polylogue/rendering/semantic_card_placement.py +index aff24fae9ebc8a4f4be934286dba9007fa8fed23..de1537631612a2157784068609eb9f32370b254a 100644 +--- a/polylogue/rendering/semantic_card_placement.py ++++ b/polylogue/rendering/semantic_card_placement.py +@@ -1,17 +1,8 @@ +-"""Per-message semantic-card placement shared by every card-consuming surface. +- +-``rendering/semantic_cards.py`` builds one ordered :class:`SemanticTranscript` +-(pure, no archive access) that the CLI already renders to Markdown +-(``cli/messages.py``). This module derives a second, equally pure projection +-of that same transcript: a lookup from message id to the cards whose primary +-evidence lives on that message, plus the set of messages whose entire content +-was already absorbed into a card (a paired tool-result message, most often). +- +-Surfaces that render one message at a time — the daemon web reader today — +-consume this instead of re-deriving tool semantics from raw block flags. This +-keeps CLI Markdown and web HTML two renderers over one shared registry +-(``rendering/semantic_card_registry.py``) rather than two independent +-tool-classification implementations drifting apart. ++"""Per-message placement of one shared semantic transcript document. ++ ++The pure renderer emits ordered prose, notices, cards, and session-level ++lineage. Message-oriented surfaces consume this placement rather than ++reclassifying raw roles, block flags, or prose at the presentation leaf. + """ + + from __future__ import annotations +@@ -19,76 +10,110 @@ from __future__ import annotations + from collections.abc import Mapping, Sequence + from dataclasses import dataclass + +-from polylogue.archive.message.models import Message + from polylogue.core.json import JSONDocument ++from polylogue.rendering.semantic_card_models import LineageDescriptor + from polylogue.rendering.semantic_cards import build_semantic_transcript + + + @dataclass(frozen=True, slots=True) + class SemanticCardPlacement: +- """Card documents and suppression, keyed by the message that owns them.""" ++ """Semantic entry documents and compatibility card views by message id.""" + ++ entries_by_message_id: Mapping[str, tuple[JSONDocument, ...]] ++ session_entries: tuple[JSONDocument, ...] + cards_by_message_id: Mapping[str, tuple[JSONDocument, ...]] + suppressed_message_ids: frozenset[str] + ++ def entries_for(self, message_id: str) -> list[JSONDocument]: ++ """All semantic entries owned by *message_id*, in transcript order.""" ++ ++ return list(self.entries_by_message_id.get(message_id, ())) ++ + def cards_for(self, message_id: str) -> list[JSONDocument]: +- """Cards whose primary evidence is on *message_id*, in transcript order.""" ++ """Compatibility view containing only cards owned by *message_id*.""" + + return list(self.cards_by_message_id.get(message_id, ())) + + def is_suppressed(self, message_id: str) -> bool: +- """Whether *message_id* is fully absorbed into another message's card. +- +- A shell/edit/task card pairs a tool-use message with its tool-result +- message by exact ``tool_id``. The card carries both message ids +- (``source.message_id`` and ``source.result_message_id``); the result +- message renders nothing of its own, or the operator would see the +- same evidence twice — once inside the card, once as a bare +- tool-result block. +- """ ++ """Whether a pure protocol row is fully absorbed into a paired card.""" + + return message_id in self.suppressed_message_ids + + +-_EMPTY_PLACEMENT = SemanticCardPlacement(cards_by_message_id={}, suppressed_message_ids=frozenset()) ++# The historical public name remains valid while the object now places the ++# complete transcript rather than cards alone. ++SemanticTranscriptPlacement = SemanticCardPlacement ++ ++_EMPTY_PLACEMENT = SemanticCardPlacement( ++ entries_by_message_id={}, ++ session_entries=(), ++ cards_by_message_id={}, ++ suppressed_message_ids=frozenset(), ++) + + + def semantic_card_placement_for_messages( +- messages: Sequence[Message | Mapping[str, object] | object], ++ messages: Sequence[Mapping[str, object] | object], + *, + session_id: str, + provider_family: str | None = None, ++ lineage: LineageDescriptor | None = None, + ) -> SemanticCardPlacement: +- """Index a session's semantic cards by the message that should render them. ++ """Place one shared semantic transcript into message/session envelopes. + +- Accepts the same message shapes as +- :func:`polylogue.rendering.semantic_cards.build_semantic_transcript` +- (domain ``Message`` objects or message-shaped mappings) so DB-backed and +- archive-backed session reads can share one call. ++ Paired result rows are suppressed only when the renderer emitted no ++ independently meaningful entry for that result message. This preserves ++ mixed protocol/context rows while preventing duplicate pure result output. + """ + +- if not messages: ++ if not messages and lineage is None: + return _EMPTY_PLACEMENT + transcript = build_semantic_transcript( + messages, + session_id=session_id, + provider_family=provider_family, ++ lineage=lineage, + ) ++ entries_by_message: dict[str, list[JSONDocument]] = {} + cards_by_message: dict[str, list[JSONDocument]] = {} +- suppressed: set[str] = set() +- for card in transcript.cards: +- primary_id = card.source.message_id or card.source.result_message_id +- if primary_id is not None: +- cards_by_message.setdefault(primary_id, []).append(card.to_document()) +- result_id = card.source.result_message_id +- if result_id is not None and result_id != primary_id: +- suppressed.add(result_id) +- if not cards_by_message and not suppressed: ++ session_entries: list[JSONDocument] = [] ++ independent_message_ids: set[str] = set() ++ paired_result_ids: set[str] = set() ++ ++ for entry in transcript.entries: ++ document = entry.to_document() ++ primary_id = entry.primary_message_id ++ if primary_id is None: ++ session_entries.append(document) ++ else: ++ entries_by_message.setdefault(primary_id, []).append(document) ++ independent_message_ids.add(primary_id) ++ ++ if entry.prose is not None: ++ independent_message_ids.add(entry.prose.message_id) ++ elif entry.notice is not None: ++ independent_message_ids.update(source.message_id for source in entry.notice.sources) ++ elif entry.card is not None: ++ card = entry.card ++ if primary_id is not None: ++ cards_by_message.setdefault(primary_id, []).append(card.to_document()) ++ result_id = card.source.result_message_id ++ if result_id is not None and result_id != primary_id: ++ paired_result_ids.add(result_id) ++ ++ suppressed = paired_result_ids - independent_message_ids ++ if not entries_by_message and not session_entries and not cards_by_message and not suppressed: + return _EMPTY_PLACEMENT + return SemanticCardPlacement( ++ entries_by_message_id={key: tuple(value) for key, value in entries_by_message.items()}, ++ session_entries=tuple(session_entries), + cards_by_message_id={key: tuple(value) for key, value in cards_by_message.items()}, + suppressed_message_ids=frozenset(suppressed), + ) + + +-__all__ = ["SemanticCardPlacement", "semantic_card_placement_for_messages"] ++__all__ = [ ++ "SemanticCardPlacement", ++ "SemanticTranscriptPlacement", ++ "semantic_card_placement_for_messages", ++] +diff --git a/polylogue/rendering/semantic_card_registry.py b/polylogue/rendering/semantic_card_registry.py +index 95ebafa6949726403ce0a5518f942eeb9af2e4e0..d85cf507cca1dd24c5f64b6bf3c0516eed11a97c 100644 +--- a/polylogue/rendering/semantic_card_registry.py ++++ b/polylogue/rendering/semantic_card_registry.py +@@ -1,10 +1,8 @@ +-"""Explicit provider/tool registry for semantic transcript cards. ++"""Explicit provider/tool registry for semantic transcript rendering. + +-Classification is exact and reviewable. Persisted ``semantic_type`` wins; +-otherwise only aliases listed here specialize. Unknown names remain fallback +-cards with raw evidence. The registry records only aliases grounded in the +-repository's parser fixtures, parser record types, or standing classifier +-contract; it does not speculate about a provider's open tool namespace. ++Persisted semantic types, structural protocol identities, and exact aliases are ++separate evidence classes. Unknown names remain generic fallback cards with ++raw evidence; no classification is derived from tool prose. + """ + + from __future__ import annotations +@@ -12,12 +10,19 @@ from __future__ import annotations + from dataclasses import dataclass + from typing import Literal + +-from polylogue.core.enums import SemanticBlockType ++from polylogue.core.enums import Origin, SemanticBlockType + from polylogue.core.json import JSONDocument ++from polylogue.core.tool_identity import parse_mcp_tool_name + from polylogue.rendering.semantic_card_models import SemanticCardKind + + MappingEvidenceKind = Literal["fixture_observed", "parser_record_type", "classifier_contract"] + RenderingStatus = Literal["launch", "model_only", "fallback"] ++ClassificationBasis = Literal[ ++ "mcp_tool_identity", ++ "persisted_semantic_type", ++ "exact_alias", ++ "generic_fallback", ++] + + + @dataclass(frozen=True, slots=True) +@@ -42,10 +47,36 @@ class ToolMapping: + } + + +-# These are exact aliases found in committed parser fixtures, parser record +-# vocabulary, or the existing provider-neutral classifier. Provider tool-name +-# spaces remain open: a name absent here is a raw fallback unless upstream has +-# already persisted a trusted semantic_type. ++@dataclass(frozen=True, slots=True) ++class ToolClassification: ++ """One explicit classification decision used by every presentation leaf.""" ++ ++ card_kind: SemanticCardKind ++ semantic_type: str ++ basis: ClassificationBasis ++ rendering_status: RenderingStatus ++ reason: str ++ ++ ++@dataclass(frozen=True, slots=True) ++class OriginPolicy: ++ origin: Origin ++ provider_family: str ++ namespace: Literal["open", "closed"] = "open" ++ unlisted_behavior: str = "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ ++ def to_document(self, *, grounded_exact_aliases: int) -> JSONDocument: ++ return { ++ "origin": self.origin.value, ++ "provider_family": self.provider_family, ++ "namespace": self.namespace, ++ "grounded_exact_aliases": grounded_exact_aliases, ++ "unlisted_behavior": self.unlisted_behavior, ++ } ++ ++ ++# Exact aliases found in committed parser fixtures, parser record vocabulary, ++# or the existing provider-neutral classifier. Provider namespaces stay open. + _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + # Claude Code: observed parser fixtures. + ToolMapping( +@@ -97,8 +128,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Read", + "file_read", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.FILE_READ, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -106,8 +137,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Grep", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -115,8 +146,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "Glob", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_base.py", + ), +@@ -135,8 +166,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "WebFetch", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -144,8 +175,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "WebSearch", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -153,8 +184,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "AskUserQuestion", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "model_only", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -162,8 +193,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "EnterPlanMode", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "model_only", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -171,8 +202,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "claude-code", + "ExitPlanMode", + "agent", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.TASK, ++ "model_only", + "classifier_contract", + "polylogue/archive/viewport/tools.py", + ), +@@ -209,8 +240,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "search", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_codex.py", + ), +@@ -227,8 +258,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "tool_search_call", + "search", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.SEARCH, ++ "launch", + "parser_record_type", + "polylogue/sources/parsers/codex.py", + ), +@@ -236,8 +267,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "codex", + "web_search_call", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "parser_record_type", + "polylogue/sources/parsers/codex.py", + ), +@@ -257,8 +288,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "gemini-cli", + "read_file", + "file_read", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.FILE_READ, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_local_agent.py", + ), +@@ -295,8 +326,8 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + "chatgpt", + "web", + "web", +- SemanticCardKind.FALLBACK, +- "fallback", ++ SemanticCardKind.WEB, ++ "launch", + "fixture_observed", + "tests/unit/sources/test_parsers_chatgpt.py", + ), +@@ -311,94 +342,186 @@ _TOOL_MAPPINGS: tuple[ToolMapping, ...] = ( + ), + ) + ++ + _TOOL_MAPPING_INDEX = { + (mapping.provider_family.casefold(), mapping.tool_name.casefold()): mapping for mapping in _TOOL_MAPPINGS + } + + _SEMANTIC_CARD_KIND: dict[str, SemanticCardKind] = { ++ SemanticBlockType.FILE_READ.value: SemanticCardKind.FILE_READ, ++ SemanticBlockType.FILE_WRITE.value: SemanticCardKind.FILE_EDIT, ++ SemanticBlockType.FILE_EDIT.value: SemanticCardKind.FILE_EDIT, + SemanticBlockType.SHELL.value: SemanticCardKind.SHELL, + SemanticBlockType.GIT.value: SemanticCardKind.SHELL, +- SemanticBlockType.FILE_EDIT.value: SemanticCardKind.FILE_EDIT, +- SemanticBlockType.FILE_WRITE.value: SemanticCardKind.FILE_EDIT, ++ SemanticBlockType.SEARCH.value: SemanticCardKind.SEARCH, ++ SemanticBlockType.WEB.value: SemanticCardKind.WEB, ++ SemanticBlockType.AGENT.value: SemanticCardKind.TASK, + SemanticBlockType.SUBAGENT.value: SemanticCardKind.TASK, + } + +-_PROVIDER_FAMILIES = ("chatgpt", "claude-code", "codex", "gemini-cli", "hermes") ++_SEMANTIC_RENDERING_STATUS: dict[str, RenderingStatus] = { ++ SemanticBlockType.FILE_READ.value: "launch", ++ SemanticBlockType.FILE_WRITE.value: "launch", ++ SemanticBlockType.FILE_EDIT.value: "launch", ++ SemanticBlockType.SHELL.value: "launch", ++ SemanticBlockType.GIT.value: "launch", ++ SemanticBlockType.SEARCH.value: "launch", ++ SemanticBlockType.WEB.value: "launch", ++ SemanticBlockType.AGENT.value: "model_only", ++ SemanticBlockType.SUBAGENT.value: "model_only", ++} ++ ++_SEMANTIC_FALLBACK_REASON: dict[str, str] = { ++ SemanticBlockType.OTHER.value: "the persisted semantic family is intentionally generic", ++ SemanticBlockType.THINKING.value: "thinking is rendered from typed content blocks, not as a tool card", ++} ++ ++_ORIGIN_POLICIES: tuple[OriginPolicy, ...] = ( ++ OriginPolicy(Origin.CLAUDE_CODE_SESSION, "claude-code"), ++ OriginPolicy(Origin.CODEX_SESSION, "codex"), ++ OriginPolicy(Origin.GEMINI_CLI_SESSION, "gemini-cli"), ++ OriginPolicy(Origin.HERMES_SESSION, "hermes"), ++ OriginPolicy(Origin.ANTIGRAVITY_SESSION, "antigravity"), ++ OriginPolicy(Origin.BEADS_ISSUE, "beads"), ++ OriginPolicy(Origin.GROK_EXPORT, "grok"), ++ OriginPolicy(Origin.CHATGPT_EXPORT, "chatgpt"), ++ OriginPolicy(Origin.CLAUDE_AI_EXPORT, "claude-ai"), ++ OriginPolicy(Origin.AISTUDIO_DRIVE, "gemini-cli"), ++ OriginPolicy(Origin.UNKNOWN_EXPORT, "unknown"), ++) ++_ORIGIN_POLICY_INDEX = {policy.origin.value: policy for policy in _ORIGIN_POLICIES} + + + def normalize_provider_family(value: object) -> str: +- """Normalize provider/origin values to registry families.""" ++ """Normalize an executable origin/provider token to a registry family.""" + + raw = getattr(value, "value", value) + token = str(raw or "unknown").strip().lower().replace("_", "-") ++ policy = _ORIGIN_POLICY_INDEX.get(token) ++ if policy is not None: ++ return policy.provider_family + aliases = { +- "claude-code-session": "claude-code", +- "codex-session": "codex", +- "gemini-cli-session": "gemini-cli", +- "hermes-session": "hermes", +- "chatgpt-export": "chatgpt", +- "claude-ai-export": "claude-ai", +- "aistudio-drive": "gemini-cli", ++ "claude-code": "claude-code", ++ "codex": "codex", ++ "gemini": "gemini-cli", ++ "gemini-cli": "gemini-cli", ++ "hermes": "hermes", ++ "chatgpt": "chatgpt", ++ "claude-ai": "claude-ai", ++ "antigravity": "antigravity", ++ "beads": "beads", ++ "grok": "grok", ++ "drive": "gemini-cli", + } + return aliases.get(token, token) + + ++def classify_tool( ++ *, ++ provider_family: str, ++ tool_name: str | None, ++ semantic_type: str | None, ++) -> ToolClassification: ++ """Resolve one explicit card policy without prose-derived semantics.""" ++ ++ mcp_identity = parse_mcp_tool_name(tool_name) ++ normalized_semantic = semantic_type.strip().lower() if semantic_type else None ++ if mcp_identity is not None: ++ return ToolClassification( ++ card_kind=SemanticCardKind.MCP, ++ semantic_type=normalized_semantic or SemanticBlockType.OTHER.value, ++ basis="mcp_tool_identity", ++ rendering_status="launch", ++ reason=f"tool name structurally identifies MCP server {mcp_identity.server!r}", ++ ) ++ if normalized_semantic: ++ kind = _SEMANTIC_CARD_KIND.get(normalized_semantic) ++ if kind is not None: ++ return ToolClassification( ++ card_kind=kind, ++ semantic_type=normalized_semantic, ++ basis="persisted_semantic_type", ++ rendering_status=_SEMANTIC_RENDERING_STATUS[normalized_semantic], ++ reason="persisted semantic_type is authoritative", ++ ) ++ return ToolClassification( ++ card_kind=SemanticCardKind.FALLBACK, ++ semantic_type=normalized_semantic, ++ basis="persisted_semantic_type", ++ rendering_status="fallback", ++ reason=_SEMANTIC_FALLBACK_REASON.get( ++ normalized_semantic, ++ "the persisted semantic family has no specialized card policy", ++ ), ++ ) ++ if tool_name: ++ row = _TOOL_MAPPING_INDEX.get((normalize_provider_family(provider_family), tool_name.casefold())) ++ if row is not None: ++ return ToolClassification( ++ card_kind=row.card_kind, ++ semantic_type=row.semantic_type, ++ basis="exact_alias", ++ rendering_status=row.rendering_status, ++ reason=f"exact alias is grounded by {row.evidence_kind}: {row.evidence}", ++ ) ++ return ToolClassification( ++ card_kind=SemanticCardKind.FALLBACK, ++ semantic_type=SemanticBlockType.OTHER.value, ++ basis="generic_fallback", ++ rendering_status="fallback", ++ reason="no persisted semantic_type, MCP identity, or grounded exact alias is present", ++ ) ++ ++ ++def card_kind_for_tool( ++ *, ++ provider_family: str, ++ tool_name: str | None, ++ semantic_type: str | None, ++) -> SemanticCardKind: ++ """Compatibility wrapper returning the shared classification's card kind.""" ++ ++ return classify_tool( ++ provider_family=provider_family, ++ tool_name=tool_name, ++ semantic_type=semantic_type, ++ ).card_kind ++ ++ + def tool_mapping_rows() -> tuple[ToolMapping, ...]: + return _TOOL_MAPPINGS + + +-def provider_namespace_documents() -> list[JSONDocument]: +- """Describe the honest behavior outside the finite exact-alias table.""" ++def origin_policy_rows() -> tuple[OriginPolicy, ...]: ++ return _ORIGIN_POLICIES + +- counts = dict.fromkeys(_PROVIDER_FAMILIES, 0) ++ ++def origin_policy_documents() -> list[JSONDocument]: ++ counts: dict[str, int] = {} + for row in _TOOL_MAPPINGS: +- counts[row.provider_family] += 1 ++ counts[row.provider_family] = counts.get(row.provider_family, 0) + 1 + return [ +- { +- "provider_family": provider, +- "namespace": "open", +- "grounded_exact_aliases": counts[provider], +- "unlisted_behavior": "persisted_semantic_type_then_fallback_raw_evidence", +- } +- for provider in _PROVIDER_FAMILIES ++ policy.to_document(grounded_exact_aliases=counts.get(policy.provider_family, 0)) ++ for policy in _ORIGIN_POLICIES + ] + + +-def card_kind_for_tool( +- *, +- provider_family: str, +- tool_name: str | None, +- semantic_type: str | None, +-) -> SemanticCardKind: +- """Resolve a card kind from persisted semantics or exact aliases.""" ++def provider_namespace_documents() -> list[JSONDocument]: ++ """Backward-compatible name for the exhaustive executable-origin policy.""" + +- if semantic_type: +- persisted = _SEMANTIC_CARD_KIND.get(semantic_type.strip().lower()) +- if persisted is not None: +- return persisted +- if tool_name: +- row = _TOOL_MAPPING_INDEX.get((normalize_provider_family(provider_family), tool_name.casefold())) +- if row is not None: +- return row.card_kind +- return SemanticCardKind.FALLBACK ++ return origin_policy_documents() + + + def semantic_type_policy_documents() -> list[JSONDocument]: +- """Map every persisted semantic family to its current honest card behavior.""" +- +- status_by_type: dict[str, RenderingStatus] = { +- SemanticBlockType.FILE_WRITE.value: "launch", +- SemanticBlockType.FILE_EDIT.value: "launch", +- SemanticBlockType.SHELL.value: "launch", +- SemanticBlockType.GIT.value: "launch", +- SemanticBlockType.SUBAGENT.value: "model_only", +- } ++ """Map every persisted semantic family to an explicit renderer behavior.""" ++ + return [ + { + "semantic_type": semantic_type.value, + "card_kind": _SEMANTIC_CARD_KIND.get(semantic_type.value, SemanticCardKind.FALLBACK).value, +- "rendering_status": status_by_type.get(semantic_type.value, "fallback"), ++ "rendering_status": _SEMANTIC_RENDERING_STATUS.get(semantic_type.value, "fallback"), + "classification_basis": "persisted_semantic_type", ++ "fallback_reason": _SEMANTIC_FALLBACK_REASON.get(semantic_type.value), + } + for semantic_type in SemanticBlockType + ] +@@ -410,11 +533,17 @@ def tool_mapping_documents() -> list[JSONDocument]: + + __all__ = [ + "card_kind_for_tool", ++ "ClassificationBasis", ++ "classify_tool", + "MappingEvidenceKind", + "normalize_provider_family", ++ "OriginPolicy", ++ "origin_policy_documents", ++ "origin_policy_rows", + "provider_namespace_documents", + "RenderingStatus", + "semantic_type_policy_documents", ++ "ToolClassification", + "ToolMapping", + "tool_mapping_documents", + "tool_mapping_rows", +diff --git a/polylogue/rendering/semantic_cards.py b/polylogue/rendering/semantic_cards.py +index 5f4cf28e4a13d866b0bb82f3867692ad9af907d4..b7ccbd3d407940f126504154f46f10ea76a12dfd 100644 +--- a/polylogue/rendering/semantic_cards.py ++++ b/polylogue/rendering/semantic_cards.py +@@ -4,18 +4,21 @@ from __future__ import annotations + + import difflib + import json +-from collections import defaultdict, deque ++from collections import defaultdict + from collections.abc import Iterable, Mapping, Sequence + from dataclasses import dataclass, replace ++from datetime import datetime + from enum import Enum ++from typing import Protocol + +-from polylogue.archive.message.models import Message + from polylogue.core.enums import BlockType + from polylogue.core.json import JSONDocument +-from polylogue.insights.topology import SessionTopology, TopologyEdgeKind ++from polylogue.core.tool_identity import parse_mcp_tool_name + from polylogue.rendering.block_models import RenderableBlock, coerce_renderable_blocks + from polylogue.rendering.semantic_card_models import ( + CardOutcomeState, ++ LineageAuthority, ++ LineageAvailability, + LineageDescriptor, + PreviewStrategy, + SemanticCard, +@@ -25,63 +28,133 @@ from polylogue.rendering.semantic_card_models import ( + SemanticCardPreview, + SemanticCardRawEvidence, + SemanticCardSource, ++ SemanticNoticeKind, + SemanticTranscript, + SemanticTranscriptEntry, ++ TranscriptNotice, ++ TranscriptNoticeSource, + TranscriptProse, + ) +-from polylogue.rendering.semantic_card_registry import card_kind_for_tool, normalize_provider_family ++from polylogue.rendering.semantic_card_registry import ToolClassification, classify_tool, normalize_provider_family + + DEFAULT_PREVIEW_HEAD_LINES = 48 + DEFAULT_PREVIEW_TAIL_LINES = 16 + DEFAULT_PREVIEW_MAX_CHARS = 16_000 + ++_THINKING_BLOCK_TYPES = {BlockType.THINKING.value, BlockType.REASONING.value} ++_ATTACHMENT_BLOCK_TYPES = {BlockType.IMAGE.value, BlockType.DOCUMENT.value, "file"} ++_TOOL_BLOCK_TYPES = {BlockType.TOOL_USE.value, BlockType.TOOL_RESULT.value} ++ ++ ++class _TopologyEdgeLike(Protocol): ++ child_id: object ++ parent_id: object ++ parent_native_id: str | None ++ kind: object ++ resolved: bool ++ ++ ++class _SessionTopologyLike(Protocol): ++ root_id: object ++ edges: Sequence[_TopologyEdgeLike] ++ cycle_detected: bool ++ ++ ++@dataclass(frozen=True, slots=True) ++class RenderableAttachment: ++ """Provider-neutral attachment evidence retained by the renderer.""" ++ ++ attachment_id: str | None = None ++ name: str | None = None ++ mime_type: str | None = None ++ size_bytes: int | None = None ++ path: str | None = None ++ source_url: str | None = None ++ caption: str | None = None ++ upload_origin: str | None = None ++ + + @dataclass(frozen=True, slots=True) + class RenderableMessage: +- """Minimal message projection needed by the pure renderer.""" ++ """Message projection needed by the pure renderer.""" + + id: str + role: str + message_type: str + text: str | None + provider_family: str ++ origin: str | None ++ material_origin: str + blocks: tuple[RenderableBlock, ...] ++ attachments: tuple[RenderableAttachment, ...] = () ++ occurred_at: str | None = None ++ duration_ms: int | None = None + parent_id: str | None = None +- branch_index: int = 0 ++ variant_index: int | None = None ++ is_active_path: bool | None = None ++ is_active_leaf: bool | None = None ++ inherited_prefix: bool | None = None ++ source_session_id: str | None = None ++ ++ ++@dataclass(frozen=True, slots=True) ++class _BlockCoordinate: ++ message_index: int ++ block_index: int + + + @dataclass(frozen=True, slots=True) + class _ResultMatch: ++ coordinate: _BlockCoordinate + message: RenderableMessage +- block_index: int + block: RenderableBlock + + +-def coerce_renderable_message(value: Message | Mapping[str, object] | object) -> RenderableMessage: +- """Normalize a domain message or message-shaped fixture.""" ++def coerce_renderable_message(value: Mapping[str, object] | object) -> RenderableMessage: ++ """Normalize a domain/archive message or a message-shaped fixture.""" + + if isinstance(value, Mapping): + raw_id = value.get("id", value.get("message_id", "")) + raw_role = value.get("role", "unknown") + raw_message_type = value.get("message_type", "message") + raw_text = value.get("text") +- raw_provider = value.get("provider", value.get("provider_family", "unknown")) ++ raw_origin = value.get("origin") ++ raw_provider = value.get("provider_family", value.get("provider", raw_origin or "unknown")) ++ raw_material_origin = value.get("material_origin", "unknown") + raw_blocks = value.get("blocks", value.get("content_blocks", ())) +- raw_parent_id = value.get("parent_id") +- raw_branch_index = value.get("branch_index", 0) ++ raw_attachments = value.get("attachments", ()) ++ raw_occurred_at = value.get("occurred_at", value.get("timestamp")) ++ raw_duration = value.get("duration_ms") ++ raw_parent_id = value.get("parent_id", value.get("parent_message_id")) ++ raw_variant_index = value.get("variant_index", value.get("branch_index")) ++ raw_active_path = value.get("is_active_path") ++ raw_active_leaf = value.get("is_active_leaf") ++ raw_inherited_prefix = value.get("inherited_prefix") ++ raw_source_session_id = value.get("source_session_id") + else: + raw_id = getattr(value, "id", getattr(value, "message_id", "")) + raw_role = getattr(value, "role", "unknown") + raw_message_type = getattr(value, "message_type", "message") + raw_text = getattr(value, "text", None) +- raw_provider = getattr(value, "provider", "unknown") ++ raw_origin = getattr(value, "origin", None) ++ raw_provider = getattr(value, "provider_family", getattr(value, "provider", raw_origin or "unknown")) ++ raw_material_origin = getattr(value, "material_origin", "unknown") + raw_blocks = getattr(value, "blocks", getattr(value, "content_blocks", ())) +- raw_parent_id = getattr(value, "parent_id", None) +- raw_branch_index = getattr(value, "branch_index", 0) ++ raw_attachments = getattr(value, "attachments", ()) ++ raw_occurred_at = getattr(value, "occurred_at", getattr(value, "timestamp", None)) ++ raw_duration = getattr(value, "duration_ms", None) ++ raw_parent_id = getattr(value, "parent_id", getattr(value, "parent_message_id", None)) ++ raw_variant_index = getattr(value, "variant_index", getattr(value, "branch_index", None)) ++ raw_active_path = getattr(value, "is_active_path", None) ++ raw_active_leaf = getattr(value, "is_active_leaf", None) ++ raw_inherited_prefix = getattr(value, "inherited_prefix", None) ++ raw_source_session_id = getattr(value, "source_session_id", None) + + raw_role = _enum_like_value(raw_role) + raw_message_type = _enum_like_value(raw_message_type) ++ raw_origin = _enum_like_value(raw_origin) + raw_provider = _enum_like_value(raw_provider) ++ raw_material_origin = _enum_like_value(raw_material_origin) + + blocks_input: Sequence[object] | None + if isinstance(raw_blocks, Sequence) and not isinstance(raw_blocks, (str, bytes, bytearray)): +@@ -89,35 +162,64 @@ def coerce_renderable_message(value: Message | Mapping[str, object] | object) -> + else: + blocks_input = None + +- if isinstance(raw_branch_index, bool): +- branch_index = 0 +- elif isinstance(raw_branch_index, (int, str)): +- try: +- branch_index = int(raw_branch_index or 0) +- except ValueError: +- branch_index = 0 +- else: +- branch_index = 0 +- +- if isinstance(raw_text, str): +- text = raw_text +- elif isinstance(raw_text, (bytes, bytearray)): +- text = bytes(raw_text).decode("utf-8", errors="replace") ++ attachments_input: Sequence[object] | None ++ if isinstance(raw_attachments, Sequence) and not isinstance(raw_attachments, (str, bytes, bytearray)): ++ attachments_input = raw_attachments + else: +- text = None ++ attachments_input = None + ++ text = _optional_text(raw_text) ++ origin = str(raw_origin) if raw_origin is not None else None + return RenderableMessage( + id=str(raw_id), + role=str(raw_role), + message_type=str(raw_message_type), + text=text, + provider_family=normalize_provider_family(raw_provider), ++ origin=origin, ++ material_origin=str(raw_material_origin or "unknown"), + blocks=coerce_renderable_blocks(blocks_input), ++ attachments=_coerce_renderable_attachments(attachments_input), ++ occurred_at=_iso_timestamp(raw_occurred_at), ++ duration_ms=_optional_int(raw_duration), + parent_id=str(raw_parent_id) if raw_parent_id is not None else None, +- branch_index=branch_index, ++ variant_index=_optional_int(raw_variant_index), ++ is_active_path=_optional_bool(raw_active_path), ++ is_active_leaf=_optional_bool(raw_active_leaf), ++ inherited_prefix=_optional_bool(raw_inherited_prefix), ++ source_session_id=str(raw_source_session_id) if raw_source_session_id is not None else None, + ) + + ++def _coerce_renderable_attachments(values: Sequence[object] | None) -> tuple[RenderableAttachment, ...]: ++ if values is None: ++ return () ++ attachments: list[RenderableAttachment] = [] ++ for value in values: ++ if isinstance(value, Mapping): ++ get = value.get ++ else: ++ get = lambda key, default=None, item=value: getattr(item, key, default) ++ attachment_id = get("id", get("attachment_id")) ++ name = get("name", get("display_name")) ++ mime_type = get("mime_type", get("media_type")) ++ size_bytes = get("size_bytes", get("byte_count")) ++ source_url = get("source_url", get("url")) ++ attachments.append( ++ RenderableAttachment( ++ attachment_id=str(attachment_id) if attachment_id is not None else None, ++ name=str(name) if name is not None else None, ++ mime_type=str(mime_type) if mime_type is not None else None, ++ size_bytes=_optional_int(size_bytes), ++ path=str(get("path")) if get("path") is not None else None, ++ source_url=str(source_url) if source_url is not None else None, ++ caption=str(get("caption")) if get("caption") is not None else None, ++ upload_origin=str(get("upload_origin")) if get("upload_origin") is not None else None, ++ ) ++ ) ++ return tuple(attachments) ++ ++ + def _enum_like_value(value: object) -> object: + if isinstance(value, Enum): + return value.value +@@ -125,75 +227,236 @@ def _enum_like_value(value: object) -> object: + return candidate if isinstance(candidate, str) else value + + +-def lineage_descriptor_from_topology(topology: SessionTopology, *, session_id: str) -> LineageDescriptor | None: +- """Project an already-read topology into pure lineage-card input.""" ++def _optional_text(value: object) -> str | None: ++ if isinstance(value, str): ++ return value ++ if isinstance(value, (bytes, bytearray)): ++ return bytes(value).decode("utf-8", errors="replace") ++ return None ++ ++ ++def _optional_int(value: object) -> int | None: ++ if value is None or isinstance(value, bool): ++ return None ++ if isinstance(value, int): ++ return value ++ if isinstance(value, str): ++ try: ++ return int(value.strip()) ++ except ValueError: ++ return None ++ return None ++ ++ ++def _optional_bool(value: object) -> bool | None: ++ if value is None: ++ return None ++ if isinstance(value, bool): ++ return value ++ if isinstance(value, int) and value in (0, 1): ++ return bool(value) ++ if isinstance(value, str): ++ normalized = value.strip().lower() ++ if normalized in {"true", "1"}: ++ return True ++ if normalized in {"false", "0"}: ++ return False ++ return None ++ ++ ++def _iso_timestamp(value: object) -> str | None: ++ if value is None: ++ return None ++ if isinstance(value, datetime): ++ return value.isoformat() ++ isoformat = getattr(value, "isoformat", None) ++ if callable(isoformat): ++ rendered = isoformat() ++ return str(rendered) if rendered is not None else None ++ return str(value) ++ ++ ++def lineage_descriptor_from_session(session: object) -> LineageDescriptor: ++ """Project one session row without hydrating a full topology family.""" ++ ++ session_id = str(getattr(session, "id", getattr(session, "session_id", ""))) ++ parent = getattr(session, "parent_id", getattr(session, "parent_session_id", None)) ++ branch = _enum_like_value(getattr(session, "branch_type", None)) ++ origin_value = _enum_like_value(getattr(session, "origin", None)) ++ parent_id = str(parent) if parent is not None else None ++ relation = str(branch) if branch is not None else "unknown" ++ availability = ( ++ LineageAvailability.PARTIAL ++ if parent_id is not None or relation != "unknown" ++ else LineageAvailability.UNAVAILABLE ++ ) ++ return LineageDescriptor( ++ session_id=session_id, ++ provider_family=normalize_provider_family(origin_value), ++ origin=str(origin_value) if origin_value is not None else None, ++ parent_session_id=parent_id, ++ relation=relation, ++ resolved=None, ++ lineage_complete=None, ++ authority=LineageAuthority.SESSION_ROW, ++ availability=availability, ++ ) ++ ++ ++def lineage_descriptor_from_archive_envelope(envelope: object) -> LineageDescriptor: ++ """Project exact bounded lineage/composition authority from an archive read.""" ++ ++ session_id = str(getattr(envelope, "session_id")) ++ parent = getattr(envelope, "parent_session_id", None) ++ stored_root = getattr(envelope, "root_session_id", None) ++ root = str(stored_root) if stored_root is not None else (session_id if parent is None else None) ++ branch = getattr(envelope, "branch_type", None) ++ relation = str(branch) if branch is not None else ("root" if parent is None else "unknown") ++ complete = _optional_bool(getattr(envelope, "lineage_complete", None)) ++ truncation = getattr(envelope, "lineage_truncation_reason", None) ++ truncation_value = str(_enum_like_value(truncation)) if truncation is not None else None ++ inheritance = getattr(envelope, "lineage_inheritance", None) ++ inherited_prefix = None if inheritance is None else str(inheritance) == "prefix-sharing" ++ if inheritance is None and parent is None: ++ inherited_prefix = False ++ availability = LineageAvailability.COMPLETE ++ if complete is False or root is None: ++ availability = LineageAvailability.PARTIAL ++ origin_value = _enum_like_value(getattr(envelope, "origin", None)) ++ return LineageDescriptor( ++ session_id=session_id, ++ provider_family=normalize_provider_family(origin_value), ++ origin=str(origin_value) if origin_value is not None else None, ++ root_session_id=root, ++ parent_session_id=str(parent) if parent is not None else None, ++ relation=relation, ++ resolved=True if parent is not None and inherited_prefix is True and complete is True else None, ++ lineage_complete=complete, ++ lineage_truncation_reason=truncation_value, ++ inherited_prefix=inherited_prefix, ++ branch_point_message_id=_optional_string(getattr(envelope, "lineage_branch_point_message_id", None)), ++ active_leaf_message_id=_optional_string(getattr(envelope, "active_leaf_message_id", None)), ++ authority=LineageAuthority.ARCHIVE_ENVELOPE, ++ availability=availability, ++ ) ++ ++ ++def lineage_descriptor_from_topology( ++ topology: _SessionTopologyLike, ++ *, ++ session_id: str, ++) -> LineageDescriptor | None: ++ """Project an already-read topology into bounded lineage-card input.""" + + target_edge = next((edge for edge in topology.edges if str(edge.child_id) == session_id), None) + if target_edge is None: +- if topology.cycle_detected: +- return LineageDescriptor( +- session_id=session_id, +- root_session_id=str(topology.root_id), +- cycle_detected=True, +- ) +- return None +- relation = target_edge.kind.value ++ if str(topology.root_id) != session_id and not topology.cycle_detected: ++ return None ++ return LineageDescriptor( ++ session_id=session_id, ++ root_session_id=str(topology.root_id), ++ relation="root" if str(topology.root_id) == session_id else "unknown", ++ cycle_detected=topology.cycle_detected, ++ authority=LineageAuthority.TOPOLOGY, ++ availability=LineageAvailability.PARTIAL, ++ ) + return LineageDescriptor( + session_id=session_id, + root_session_id=str(topology.root_id), + parent_session_id=str(target_edge.parent_id) if target_edge.parent_id is not None else None, + parent_native_id=target_edge.parent_native_id, +- relation=relation, ++ relation=target_edge.kind.value, + resolved=target_edge.resolved, + cycle_detected=topology.cycle_detected, ++ authority=LineageAuthority.TOPOLOGY, ++ availability=LineageAvailability.PARTIAL, + ) + + + def build_semantic_transcript( +- messages: Sequence[Message | Mapping[str, object] | object], ++ messages: Sequence[Mapping[str, object] | object], + *, + session_id: str, + lineage: LineageDescriptor | None = None, + provider_family: str | None = None, + ) -> SemanticTranscript: +- """Build ordered prose/card entries without archive access. ++ """Build one ordered document for prose, thinking, tools, and attachments. + +- Result pairing is exact by ``tool_id`` and FIFO within duplicate IDs. Text +- never influences classification or outcome. A caller may supply the session +- origin as ``provider_family`` when legacy message rows carry ``unknown``. ++ Tool pairing is structural by ``tool_id`` and FIFO coordinates, independent ++ of serialization order. Empty typed thinking/reasoning blocks compact into ++ one notice per contiguous run while retaining every exact source coordinate. ++ No card kind or outcome is inferred from prose. + """ + + normalized: list[RenderableMessage] = [] + session_provider = normalize_provider_family(provider_family) if provider_family else None + for raw in messages: + message = coerce_renderable_message(raw) ++ inherited_prefix = message.inherited_prefix ++ if inherited_prefix is None and message.source_session_id is not None: ++ inherited_prefix = message.source_session_id != session_id + if session_provider and message.provider_family in {"", "unknown"}: + message = replace(message, provider_family=session_provider) ++ if inherited_prefix != message.inherited_prefix: ++ message = replace(message, inherited_prefix=inherited_prefix) + normalized.append(message) + +- result_index: dict[str, deque[_ResultMatch]] = defaultdict(deque) +- for message in normalized: +- for block_index, block in enumerate(message.blocks): +- if block.type == BlockType.TOOL_RESULT.value and block.tool_id: +- result_index[block.tool_id].append(_ResultMatch(message, block_index, block)) +- +- consumed_results: set[tuple[str, int]] = set() ++ result_by_use, paired_results = _pair_tool_results(normalized) + entries: list[SemanticTranscriptEntry] = [] ++ pending_empty_thinking: list[TranscriptNoticeSource] = [] ++ ++ def flush_empty_thinking() -> None: ++ if not pending_empty_thinking: ++ return ++ entries.append( ++ SemanticTranscriptEntry( ++ notice=TranscriptNotice( ++ kind=SemanticNoticeKind.EMPTY_THINKING, ++ sources=tuple(pending_empty_thinking), ++ ) ++ ) ++ ) ++ pending_empty_thinking.clear() ++ + if lineage is not None: + entries.append(SemanticTranscriptEntry(card=_build_lineage_card(lineage))) + +- for message in normalized: ++ for message_index, message in enumerate(normalized): + if not message.blocks: +- if message.text: ++ flush_empty_thinking() ++ if message.text and message.text.strip(): + entries.append(SemanticTranscriptEntry(prose=_prose_for_message(message, message.text))) ++ for attachment in message.attachments: ++ entries.append( ++ SemanticTranscriptEntry( ++ card=_build_attachment_envelope_card( ++ session_id=session_id, ++ message=message, ++ attachment=attachment, ++ ) ++ ) ++ ) + continue + +- emitted_text_block = False ++ emitted_block_attachment_keys: set[tuple[object, ...]] = set() + for block_index, block in enumerate(message.blocks): ++ coordinate = _BlockCoordinate(message_index, block_index) ++ if block.type in _THINKING_BLOCK_TYPES: ++ if block.text is None or not block.text.strip(): ++ pending_empty_thinking.append(_notice_source(message, block_index, block)) ++ else: ++ flush_empty_thinking() ++ entries.append( ++ SemanticTranscriptEntry( ++ prose=_prose_for_block(message, block_index=block_index, block=block) ++ ) ++ ) ++ continue ++ ++ # Any typed non-thinking row terminates an absence run, even when the ++ # row is subsequently absorbed into a paired card. ++ flush_empty_thinking() + if block.type == BlockType.TOOL_USE.value: +- result = _take_result(result_index, block.tool_id) +- if result is not None: +- consumed_results.add((result.message.id, result.block_index)) + entries.append( + SemanticTranscriptEntry( + card=_build_tool_card( +@@ -201,13 +464,13 @@ def build_semantic_transcript( + message=message, + block_index=block_index, + block=block, +- result=result, ++ result=result_by_use.get(coordinate), + ) + ) + ) + continue + if block.type == BlockType.TOOL_RESULT.value: +- if (message.id, block_index) not in consumed_results: ++ if coordinate not in paired_results: + entries.append( + SemanticTranscriptEntry( + card=_build_orphan_result_card( +@@ -219,10 +482,10 @@ def build_semantic_transcript( + ) + ) + continue +- if block.type in {BlockType.IMAGE.value, BlockType.DOCUMENT.value, "file"}: ++ if block.type in _ATTACHMENT_BLOCK_TYPES: + entries.append( + SemanticTranscriptEntry( +- card=_build_attachment_card( ++ card=_build_attachment_block_card( + session_id=session_id, + message=message, + block_index=block_index, +@@ -230,37 +493,83 @@ def build_semantic_transcript( + ) + ) + ) ++ emitted_block_attachment_keys.add(_block_attachment_identity(block)) + continue +- if block.text: ++ if block.text is not None and block.text != "": + entries.append( + SemanticTranscriptEntry( +- prose=TranscriptProse( +- message_id=message.id, +- role=message.role, +- message_type=message.message_type, +- text=block.text, +- block_id=block.block_id, +- block_type=block.type, +- ) ++ prose=_prose_for_block(message, block_index=block_index, block=block) + ) + ) +- emitted_text_block = True + +- # Preserve legacy ordinary prose only when typed blocks did not already +- # carry it. Aggregate tool-use/result text is not emitted a second time. +- if message.text and not emitted_text_block and message.message_type == "message": ++ if _should_emit_message_text(message): ++ flush_empty_thinking() ++ assert message.text is not None + entries.append(SemanticTranscriptEntry(prose=_prose_for_message(message, message.text))) + ++ for attachment in message.attachments: ++ if _attachment_matches_block(attachment, emitted_block_attachment_keys): ++ continue ++ flush_empty_thinking() ++ entries.append( ++ SemanticTranscriptEntry( ++ card=_build_attachment_envelope_card( ++ session_id=session_id, ++ message=message, ++ attachment=attachment, ++ ) ++ ) ++ ) ++ ++ flush_empty_thinking() + return SemanticTranscript(session_id=session_id, entries=tuple(entries)) + + +-def _take_result(index: Mapping[str, deque[_ResultMatch]], tool_id: str | None) -> _ResultMatch | None: +- if not tool_id: +- return None +- queue = index.get(tool_id) +- if not queue: +- return None +- return queue.popleft() ++def _pair_tool_results( ++ messages: Sequence[RenderableMessage], ++) -> tuple[dict[_BlockCoordinate, _ResultMatch], frozenset[_BlockCoordinate]]: ++ uses: dict[str, list[_BlockCoordinate]] = defaultdict(list) ++ results: dict[str, list[_ResultMatch]] = defaultdict(list) ++ for message_index, message in enumerate(messages): ++ for block_index, block in enumerate(message.blocks): ++ coordinate = _BlockCoordinate(message_index, block_index) ++ if block.type == BlockType.TOOL_USE.value and block.tool_id: ++ uses[block.tool_id].append(coordinate) ++ elif block.type == BlockType.TOOL_RESULT.value and block.tool_id: ++ results[block.tool_id].append(_ResultMatch(coordinate, message, block)) ++ ++ result_by_use: dict[_BlockCoordinate, _ResultMatch] = {} ++ paired_results: set[_BlockCoordinate] = set() ++ for tool_id, use_coordinates in uses.items(): ++ for use_coordinate, result in zip(use_coordinates, results.get(tool_id, ()), strict=False): ++ result_by_use[use_coordinate] = result ++ paired_results.add(result.coordinate) ++ return result_by_use, frozenset(paired_results) ++ ++ ++def _notice_source( ++ message: RenderableMessage, ++ block_index: int, ++ block: RenderableBlock, ++) -> TranscriptNoticeSource: ++ return TranscriptNoticeSource( ++ message_id=message.id, ++ block_index=block_index, ++ block_type=block.type, ++ block_id=block.block_id, ++ role=message.role, ++ message_type=message.message_type, ++ provider_family=message.provider_family, ++ origin=message.origin, ++ material_origin=message.material_origin, ++ occurred_at=message.occurred_at, ++ duration_ms=message.duration_ms, ++ parent_message_id=message.parent_id, ++ variant_index=message.variant_index, ++ is_active_path=message.is_active_path, ++ is_active_leaf=message.is_active_leaf, ++ inherited_prefix=message.inherited_prefix, ++ ) + + + def _prose_for_message(message: RenderableMessage, text: str) -> TranscriptProse: +@@ -268,7 +577,104 @@ def _prose_for_message(message: RenderableMessage, text: str) -> TranscriptProse + message_id=message.id, + role=message.role, + message_type=message.message_type, ++ provider_family=message.provider_family, ++ origin=message.origin, ++ material_origin=message.material_origin, + text=text, ++ occurred_at=message.occurred_at, ++ duration_ms=message.duration_ms, ++ parent_message_id=message.parent_id, ++ variant_index=message.variant_index, ++ is_active_path=message.is_active_path, ++ is_active_leaf=message.is_active_leaf, ++ inherited_prefix=message.inherited_prefix, ++ ) ++ ++ ++def _prose_for_block( ++ message: RenderableMessage, ++ *, ++ block_index: int, ++ block: RenderableBlock, ++) -> TranscriptProse: ++ assert block.text is not None ++ return TranscriptProse( ++ message_id=message.id, ++ role=message.role, ++ message_type=message.message_type, ++ provider_family=message.provider_family, ++ origin=message.origin, ++ material_origin=message.material_origin, ++ text=block.text, ++ block_id=block.block_id, ++ block_index=block_index, ++ block_type=block.type, ++ language=block.language, ++ occurred_at=message.occurred_at, ++ duration_ms=message.duration_ms, ++ parent_message_id=message.parent_id, ++ variant_index=message.variant_index, ++ is_active_path=message.is_active_path, ++ is_active_leaf=message.is_active_leaf, ++ inherited_prefix=message.inherited_prefix, ++ ) ++ ++ ++def _should_emit_message_text(message: RenderableMessage) -> bool: ++ text = message.text ++ if text is None or not text.strip(): ++ return False ++ block_texts = [block.text for block in message.blocks if block.text is not None and block.text != ""] ++ exact_aggregates = set(block_texts) ++ if block_texts: ++ exact_aggregates.add("\n".join(block_texts)) ++ exact_aggregates.add("\n\n".join(block_texts)) ++ if text in exact_aggregates: ++ return False ++ if message.message_type == "tool_use" and any( ++ block.type == BlockType.TOOL_USE.value for block in message.blocks ++ ): ++ # Recipient/tool envelopes sometimes retain serialized invocation JSON ++ # in message.text. The typed TOOL_USE block is the authority. ++ return False ++ return True ++ ++ ++def _source_for_message( ++ *, ++ session_id: str, ++ message: RenderableMessage, ++ block: RenderableBlock | None = None, ++ block_index: int | None = None, ++ result: _ResultMatch | None = None, ++ attachment_id: str | None = None, ++) -> SemanticCardSource: ++ result_message = result.message if result is not None else None ++ result_block = result.block if result is not None else None ++ return SemanticCardSource( ++ session_id=session_id, ++ provider_family=message.provider_family, ++ origin=message.origin, ++ message_id=message.id, ++ block_id=block.block_id if block is not None else None, ++ block_index=block_index, ++ tool_name=block.tool_name if block is not None else None, ++ tool_id=block.tool_id if block is not None else None, ++ attachment_id=attachment_id, ++ material_origin=message.material_origin, ++ occurred_at=message.occurred_at, ++ duration_ms=message.duration_ms, ++ parent_message_id=message.parent_id, ++ variant_index=message.variant_index, ++ is_active_path=message.is_active_path, ++ is_active_leaf=message.is_active_leaf, ++ inherited_prefix=message.inherited_prefix, ++ result_message_id=result_message.id if result_message is not None else None, ++ result_block_id=result_block.block_id if result_block is not None else None, ++ result_block_index=result.coordinate.block_index if result is not None else None, ++ result_duration_ms=result_message.duration_ms if result_message is not None else None, ++ result_material_origin=result_message.material_origin if result_message is not None else None, ++ result_inherited_prefix=result_message.inherited_prefix if result_message is not None else None, + ) + + +@@ -281,22 +687,17 @@ def _build_tool_card( + result: _ResultMatch | None, + ) -> SemanticCard: + result_block = result.block if result is not None else None +- kind = card_kind_for_tool( ++ classification = classify_tool( + provider_family=message.provider_family, + tool_name=block.tool_name, + semantic_type=block.semantic_type, + ) +- source = SemanticCardSource( ++ source = _source_for_message( + session_id=session_id, +- provider_family=message.provider_family, +- message_id=message.id, +- block_id=block.block_id, ++ message=message, ++ block=block, + block_index=block_index, +- tool_name=block.tool_name, +- tool_id=block.tool_id, +- result_message_id=result.message.id if result is not None else None, +- result_block_id=result_block.block_id if result_block is not None else None, +- result_block_index=result.block_index if result is not None else None, ++ result=result, + ) + outcome, outcome_caveats = _outcome_from_result(result_block) + common_caveats: list[str] = list(outcome_caveats) +@@ -305,13 +706,57 @@ def _build_tool_card( + if result_block is None: + common_caveats.append("no structurally paired tool result is present") + +- if kind is SemanticCardKind.SHELL: +- return _build_shell_card(block, result_block, source, outcome, tuple(common_caveats)) +- if kind is SemanticCardKind.FILE_EDIT: +- return _build_file_edit_card(block, result_block, source, outcome, tuple(common_caveats)) +- if kind is SemanticCardKind.TASK: +- return _build_task_card(block, result_block, source, outcome, tuple(common_caveats)) +- return _build_fallback_card(block, result_block, source, outcome, tuple(common_caveats)) ++ if classification.card_kind is SemanticCardKind.SHELL: ++ builder = _build_shell_card ++ elif classification.card_kind is SemanticCardKind.FILE_READ: ++ builder = _build_file_read_card ++ elif classification.card_kind is SemanticCardKind.FILE_EDIT: ++ builder = _build_file_edit_card ++ elif classification.card_kind is SemanticCardKind.SEARCH: ++ builder = _build_search_card ++ elif classification.card_kind is SemanticCardKind.WEB: ++ builder = _build_web_card ++ elif classification.card_kind is SemanticCardKind.TASK: ++ builder = _build_task_card ++ elif classification.card_kind is SemanticCardKind.MCP: ++ builder = _build_mcp_card ++ else: ++ builder = None ++ if builder is not None: ++ return builder( ++ block, ++ result_block, ++ source, ++ outcome, ++ tuple(common_caveats), ++ classification, ++ ) ++ return _build_fallback_card( ++ block, ++ result_block, ++ source, ++ outcome, ++ tuple(common_caveats), ++ classification, ++ ) ++ ++ ++def _base_tool_fields( ++ block: RenderableBlock, ++ classification: ToolClassification, ++ *, ++ duration_ms: int | None, ++) -> list[SemanticCardField]: ++ fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ fields.append(SemanticCardField("semantic family", classification.semantic_type)) ++ fields.append(SemanticCardField("classification", classification.basis)) ++ if duration_ms is not None: ++ fields.append(SemanticCardField("duration", f"{duration_ms} ms")) ++ return fields ++ ++ ++def _effective_duration(source: SemanticCardSource) -> int | None: ++ return source.result_duration_ms if source.result_duration_ms is not None else source.duration_ms + + + def _build_shell_card( +@@ -320,11 +765,16 @@ def _build_shell_card( + source: SemanticCardSource, + outcome: SemanticCardOutcome, + caveats: tuple[str, ...], ++ classification: ToolClassification, + ) -> SemanticCard: + command = _first_scalar(block.tool_input, ("command", "cmd", "script")) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ cwd = _first_scalar(block.tool_input, ("cwd", "working_directory", "workdir")) ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) + if command: + fields.append(SemanticCardField("command", command)) ++ if cwd: ++ fields.append(SemanticCardField("cwd", cwd)) ++ local_caveats = caveats + (() if command else ("no exact command field is present in tool input",)) + previews = _result_previews(result, kind="output") + return SemanticCard( + kind=SemanticCardKind.SHELL, +@@ -335,7 +785,40 @@ def _build_shell_card( + fields=tuple(fields), + previews=previews, + raw_evidence=_raw_evidence(block, result), +- caveats=_deduplicate(caveats + _preview_caveats(previews)), ++ caveats=_deduplicate(local_caveats + _preview_caveats(previews)), ++ ) ++ ++ ++def _build_file_read_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++ classification: ToolClassification, ++) -> SemanticCard: ++ path = _first_scalar(block.tool_input, ("file_path", "path", "file", "filename")) ++ offset = _first_scalar(block.tool_input, ("offset", "start", "line_start")) ++ limit = _first_scalar(block.tool_input, ("limit", "count", "line_count", "end")) ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) ++ if path: ++ fields.append(SemanticCardField("path", path)) ++ if offset: ++ fields.append(SemanticCardField("offset", offset)) ++ if limit: ++ fields.append(SemanticCardField("limit", limit)) ++ local_caveats = caveats + (() if path else ("no exact path field is present in tool input",)) ++ previews = _result_previews(result, kind="content") ++ return SemanticCard( ++ kind=SemanticCardKind.FILE_READ, ++ title="File read", ++ summary=path, ++ source=source, ++ outcome=outcome, ++ fields=tuple(fields), ++ previews=previews, ++ raw_evidence=_raw_evidence(block, result), ++ caveats=_deduplicate(local_caveats + _preview_caveats(previews)), + ) + + +@@ -345,6 +828,7 @@ def _build_file_edit_card( + source: SemanticCardSource, + outcome: SemanticCardOutcome, + caveats: tuple[str, ...], ++ classification: ToolClassification, + ) -> SemanticCard: + path = _first_scalar(block.tool_input, ("file_path", "path", "file", "filename")) + diff_text = _edit_diff(block.tool_input, path=path) +@@ -362,12 +846,14 @@ def _build_file_edit_card( + encoding_replacements=result.text_encoding_replacements, + ) + ) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) + if path: + fields.append(SemanticCardField("path", path)) ++ else: ++ local_caveats.append("no exact path field is present in tool input") + return SemanticCard( + kind=SemanticCardKind.FILE_EDIT, +- title="File edit", ++ title="File write" if classification.semantic_type == "file_write" else "File edit", + summary=path, + source=source, + outcome=outcome, +@@ -378,16 +864,79 @@ def _build_file_edit_card( + ) + + ++def _build_search_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++ classification: ToolClassification, ++) -> SemanticCard: ++ query = _first_scalar(block.tool_input, ("query", "pattern", "regex", "search_term", "glob")) ++ scope = _first_scalar(block.tool_input, ("path", "root", "directory", "cwd", "include")) ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) ++ if query: ++ fields.append(SemanticCardField("query", query)) ++ if scope: ++ fields.append(SemanticCardField("scope", scope)) ++ local_caveats = caveats + (() if query else ("no exact query/pattern field is present in tool input",)) ++ previews = _result_previews(result, kind="matches") ++ return SemanticCard( ++ kind=SemanticCardKind.SEARCH, ++ title="Search", ++ summary=query or scope, ++ source=source, ++ outcome=outcome, ++ fields=tuple(fields), ++ previews=previews, ++ raw_evidence=_raw_evidence(block, result), ++ caveats=_deduplicate(local_caveats + _preview_caveats(previews)), ++ ) ++ ++ ++def _build_web_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++ classification: ToolClassification, ++) -> SemanticCard: ++ url = _first_scalar(block.tool_input, ("url", "uri", "href")) ++ query = _first_web_query(block.tool_input) ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) ++ if url: ++ fields.append(SemanticCardField("url", url)) ++ if query: ++ fields.append(SemanticCardField("query", query)) ++ local_caveats = caveats ++ if not url and not query: ++ local_caveats += ("no exact URL or query field is present in tool input",) ++ previews = _result_previews(result, kind="result") ++ return SemanticCard( ++ kind=SemanticCardKind.WEB, ++ title="Web request", ++ summary=url or query, ++ source=source, ++ outcome=outcome, ++ fields=tuple(fields), ++ previews=previews, ++ raw_evidence=_raw_evidence(block, result), ++ caveats=_deduplicate(local_caveats + _preview_caveats(previews)), ++ ) ++ ++ + def _build_task_card( + block: RenderableBlock, + result: RenderableBlock | None, + source: SemanticCardSource, + outcome: SemanticCardOutcome, + caveats: tuple[str, ...], ++ classification: ToolClassification, + ) -> SemanticCard: +- prompt = _first_scalar(block.tool_input, ("prompt", "task", "instructions", "description")) ++ prompt = _first_scalar(block.tool_input, ("prompt", "task", "instructions", "description", "question")) + agent_type = _first_scalar(block.tool_input, ("subagent_type", "agent_type", "agent")) +- fields = [SemanticCardField("tool", block.tool_name or "unknown")] ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) + if agent_type: + fields.append(SemanticCardField("agent", agent_type)) + if prompt: +@@ -395,7 +944,7 @@ def _build_task_card( + previews = _result_previews(result, kind="result") + return SemanticCard( + kind=SemanticCardKind.TASK, +- title="Task / subagent", ++ title="Task / delegation", + summary=prompt or agent_type, + source=source, + outcome=outcome, +@@ -406,25 +955,64 @@ def _build_task_card( + ) + + ++def _build_mcp_card( ++ block: RenderableBlock, ++ result: RenderableBlock | None, ++ source: SemanticCardSource, ++ outcome: SemanticCardOutcome, ++ caveats: tuple[str, ...], ++ classification: ToolClassification, ++) -> SemanticCard: ++ identity = parse_mcp_tool_name(block.tool_name) ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) ++ if identity is not None: ++ fields.append(SemanticCardField("MCP server", identity.server)) ++ fields.append(SemanticCardField("MCP tool", identity.tool)) ++ target = _first_scalar( ++ block.tool_input, ++ ("resource", "path", "url", "uri", "query", "command", "name", "id"), ++ ) ++ if target: ++ fields.append(SemanticCardField("target", target)) ++ input_preview = _input_preview(block) ++ previews = (() if input_preview is None else (input_preview,)) + _result_previews(result, kind="result") ++ return SemanticCard( ++ kind=SemanticCardKind.MCP, ++ title="MCP tool call", ++ summary=(f"{identity.server}/{identity.tool}" if identity is not None else block.tool_name), ++ source=source, ++ outcome=outcome, ++ fields=tuple(fields), ++ previews=previews, ++ raw_evidence=_raw_evidence(block, result), ++ caveats=_deduplicate(caveats + _preview_caveats(previews)), ++ ) ++ ++ + def _build_fallback_card( + block: RenderableBlock, + result: RenderableBlock | None, + source: SemanticCardSource, + outcome: SemanticCardOutcome, + caveats: tuple[str, ...], ++ classification: ToolClassification, + ) -> SemanticCard: + input_preview = _input_preview(block) + previews = (() if input_preview is None else (input_preview,)) + _result_previews(result, kind="result") ++ fields = _base_tool_fields(block, classification, duration_ms=_effective_duration(source)) + return SemanticCard( + kind=SemanticCardKind.FALLBACK, + title=f"Tool evidence · {block.tool_name or 'unknown'}", + source=source, + outcome=outcome, +- fields=(SemanticCardField("tool", block.tool_name or "unknown"),), ++ fields=tuple(fields), + previews=previews, + raw_evidence=_raw_evidence(block, result), + caveats=_deduplicate( +- ("tool classification is unknown; raw evidence is shown without a guessed semantic card type",) ++ ( ++ f"generic fallback policy: {classification.reason}", ++ "raw evidence is shown without a guessed specialized card type", ++ ) + + caveats + + _preview_caveats(previews) + ), +@@ -440,18 +1028,18 @@ def _build_orphan_result_card( + ) -> SemanticCard: + outcome, outcome_caveats = _outcome_from_result(block) + previews = _result_previews(block, kind="result") ++ source = _source_for_message( ++ session_id=session_id, ++ message=message, ++ block=block, ++ block_index=block_index, ++ ) + return SemanticCard( + kind=SemanticCardKind.FALLBACK, + title="Unpaired tool result", +- source=SemanticCardSource( +- session_id=session_id, +- provider_family=message.provider_family, +- result_message_id=message.id, +- result_block_id=block.block_id, +- result_block_index=block_index, +- tool_id=block.tool_id, +- ), ++ source=source, + outcome=outcome, ++ fields=(SemanticCardField("tool id", block.tool_id or "unknown"),), + previews=previews, + raw_evidence=SemanticCardRawEvidence(result_preview=previews[0] if previews else None), + caveats=_deduplicate( +@@ -462,7 +1050,7 @@ def _build_orphan_result_card( + ) + + +-def _build_attachment_card( ++def _build_attachment_block_card( + *, + session_id: str, + message: RenderableMessage, +@@ -480,40 +1068,116 @@ def _build_attachment_card( + kind=SemanticCardKind.ATTACHMENT, + title="Attachment", + summary=block.name or block.mime_type or block.type, +- source=SemanticCardSource( ++ source=_source_for_message( + session_id=session_id, +- provider_family=message.provider_family, +- message_id=message.id, +- block_id=block.block_id, ++ message=message, ++ block=block, + block_index=block_index, ++ attachment_id=block.block_id, + ), + fields=tuple(fields), +- caveats=("attachment bytes are not embedded in the transcript card",), ++ caveats=("attachment bytes are not embedded in the transcript document",), + ) + + ++def _build_attachment_envelope_card( ++ *, ++ session_id: str, ++ message: RenderableMessage, ++ attachment: RenderableAttachment, ++) -> SemanticCard: ++ fields: list[SemanticCardField] = [] ++ if attachment.name: ++ fields.append(SemanticCardField("name", attachment.name)) ++ if attachment.mime_type: ++ fields.append(SemanticCardField("media type", attachment.mime_type)) ++ if attachment.size_bytes is not None: ++ fields.append(SemanticCardField("size", f"{attachment.size_bytes} bytes")) ++ if attachment.path: ++ fields.append(SemanticCardField("path", attachment.path)) ++ if attachment.source_url: ++ fields.append(SemanticCardField("source URL", attachment.source_url)) ++ if attachment.upload_origin: ++ fields.append(SemanticCardField("upload origin", attachment.upload_origin)) ++ if attachment.caption: ++ fields.append(SemanticCardField("caption", attachment.caption)) ++ return SemanticCard( ++ kind=SemanticCardKind.ATTACHMENT, ++ title="Attachment", ++ summary=attachment.name or attachment.mime_type or attachment.attachment_id, ++ source=_source_for_message( ++ session_id=session_id, ++ message=message, ++ attachment_id=attachment.attachment_id, ++ ), ++ fields=tuple(fields), ++ caveats=("attachment bytes are not embedded in the transcript document",), ++ ) ++ ++ ++def _block_attachment_identity(block: RenderableBlock) -> tuple[object, ...]: ++ return (block.block_id, block.url, block.name, block.mime_type) ++ ++ ++def _attachment_matches_block( ++ attachment: RenderableAttachment, ++ block_keys: set[tuple[object, ...]], ++) -> bool: ++ if not block_keys: ++ return False ++ for block_id, url, name, mime_type in block_keys: ++ if attachment.attachment_id is not None and attachment.attachment_id == block_id: ++ return True ++ comparable = tuple(value for value in (attachment.source_url, attachment.name, attachment.mime_type) if value) ++ if comparable and comparable == tuple(value for value in (url, name, mime_type) if value): ++ return True ++ return False ++ ++ + def _build_lineage_card(lineage: LineageDescriptor) -> SemanticCard: + fields = [ + SemanticCardField("session", f"session:{lineage.session_id}"), +- SemanticCardField("root", f"session:{lineage.root_session_id}"), ++ SemanticCardField("authority", lineage.authority.value), ++ SemanticCardField("availability", lineage.availability.value), + SemanticCardField("relation", lineage.relation), + ] ++ if lineage.root_session_id: ++ fields.append(SemanticCardField("root", f"session:{lineage.root_session_id}")) + if lineage.parent_session_id: + fields.append(SemanticCardField("parent", f"session:{lineage.parent_session_id}")) + elif lineage.parent_native_id: + fields.append(SemanticCardField("native parent", lineage.parent_native_id)) ++ if lineage.lineage_complete is not None: ++ fields.append(SemanticCardField("composed transcript", "complete" if lineage.lineage_complete else "incomplete")) ++ if lineage.inherited_prefix is not None: ++ fields.append(SemanticCardField("inherited prefix", "yes" if lineage.inherited_prefix else "no")) ++ if lineage.branch_point_message_id: ++ fields.append(SemanticCardField("branch point", f"message:{lineage.branch_point_message_id}")) ++ if lineage.active_leaf_message_id: ++ fields.append(SemanticCardField("active leaf", f"message:{lineage.active_leaf_message_id}")) ++ + caveats: list[str] = [] +- if not lineage.resolved: ++ if lineage.root_session_id is None: ++ caveats.append("root identity is unavailable from this bounded lineage authority") ++ if lineage.lineage_complete is None: ++ caveats.append("transcript-composition completeness is unknown from this bounded lineage authority") ++ if lineage.resolved is False: + caveats.append("the provider-native parent has not resolved to a stored session") +- if lineage.relation == TopologyEdgeKind.UNKNOWN.value: +- caveats.append("the parent edge is structural but its narrower lineage relation is unknown") ++ if lineage.relation == "unknown": ++ caveats.append("the structural lineage relation is unknown") + if lineage.cycle_detected: + caveats.append("a cycle was detected in the session topology") ++ if lineage.lineage_truncation_reason: ++ caveats.append(f"composed transcript is truncated: {lineage.lineage_truncation_reason}") + return SemanticCard( + kind=SemanticCardKind.LINEAGE, + title=f"Lineage boundary · {lineage.relation}", + summary=f"session:{lineage.session_id}", +- source=SemanticCardSource(session_id=lineage.session_id), ++ source=SemanticCardSource( ++ session_id=lineage.session_id, ++ provider_family=lineage.provider_family, ++ origin=lineage.origin, ++ ), + fields=tuple(fields), + caveats=tuple(caveats), + ) +@@ -549,6 +1213,28 @@ def _first_scalar(document: JSONDocument | None, keys: Iterable[str]) -> str | N + return None + + ++def _first_web_query(document: JSONDocument | None) -> str | None: ++ """Extract a query only from explicit web-tool input structure.""" ++ ++ scalar = _first_scalar(document, ("query", "q", "search_query")) ++ if scalar is not None or document is None: ++ return scalar ++ search_queries = document.get("search_query") ++ if not isinstance(search_queries, list): ++ return None ++ for item in search_queries: ++ if not isinstance(item, dict): ++ continue ++ value = item.get("q", item.get("query")) ++ if isinstance(value, (str, int, float)) and not isinstance(value, bool): ++ return str(value) ++ return None ++ ++ ++def _optional_string(value: object) -> str | None: ++ return str(value) if value is not None else None ++ ++ + def _edit_diff(document: JSONDocument | None, *, path: str | None) -> str | None: + if document is None: + return None +@@ -673,6 +1359,9 @@ def _deduplicate(values: Iterable[str]) -> tuple[str, ...]: + __all__ = [ + "build_semantic_transcript", + "coerce_renderable_message", ++ "lineage_descriptor_from_archive_envelope", ++ "lineage_descriptor_from_session", + "lineage_descriptor_from_topology", ++ "RenderableAttachment", + "RenderableMessage", + ] +diff --git a/polylogue/rendering/semantic_markdown.py b/polylogue/rendering/semantic_markdown.py +index 73303e928c85a9693802022b73bf7605a1980989..3b5131aef985d027e6e3439b82cc9dbd333f37a8 100644 +--- a/polylogue/rendering/semantic_markdown.py ++++ b/polylogue/rendering/semantic_markdown.py +@@ -1,4 +1,4 @@ +-"""Markdown rendering for pure semantic transcript cards.""" ++"""Markdown presentation for the shared semantic transcript document.""" + + from __future__ import annotations + +@@ -9,13 +9,15 @@ from polylogue.rendering.semantic_card_models import ( + CardOutcomeState, + SemanticCard, + SemanticCardPreview, ++ SemanticNoticeKind, + SemanticTranscript, ++ TranscriptNotice, + TranscriptProse, + ) + + + def render_semantic_transcript_markdown(transcript: SemanticTranscript) -> str: +- """Render an ordered semantic transcript without archive access.""" ++ """Render one ordered semantic transcript without reclassification.""" + + rendered: list[str] = [] + for entry in transcript.entries: +@@ -23,6 +25,8 @@ def render_semantic_transcript_markdown(transcript: SemanticTranscript) -> str: + rendered.append(render_semantic_card_markdown(entry.card)) + elif entry.prose is not None: + rendered.append(_render_prose(entry.prose)) ++ elif entry.notice is not None: ++ rendered.append(_render_notice(entry.notice)) + if not rendered: + return "" + return "\n\n---\n\n".join(rendered).rstrip() + "\n" +@@ -39,19 +43,33 @@ def render_semantic_card_markdown(card: SemanticCard) -> str: + for field in card.fields: + parts.append(_render_inline_or_fenced(field.label, field.value)) + +- source_bits = [f"session:{card.source.session_id}"] +- if card.source.message_id: +- source_bits.append(f"message:{card.source.message_id}") +- if card.source.block_id: +- source_bits.append(f"block:{card.source.block_id}") +- elif card.source.block_index is not None: ++ source_bits = [f"session:{card.source.session_id}", f"provider:{card.source.provider_family}"] ++ _append_source_bit(source_bits, "origin", card.source.origin) ++ _append_source_bit(source_bits, "message", card.source.message_id) ++ _append_source_bit(source_bits, "block", card.source.block_id) ++ if card.source.block_id is None and card.source.block_index is not None: + source_bits.append(f"block-index:{card.source.block_index}") +- if card.source.result_message_id: +- source_bits.append(f"result-message:{card.source.result_message_id}") +- if card.source.result_block_id: +- source_bits.append(f"result-block:{card.source.result_block_id}") +- elif card.source.result_block_index is not None: ++ _append_source_bit(source_bits, "tool", card.source.tool_name) ++ _append_source_bit(source_bits, "tool-id", card.source.tool_id) ++ _append_source_bit(source_bits, "attachment", card.source.attachment_id) ++ _append_source_bit(source_bits, "material", card.source.material_origin) ++ _append_source_bit(source_bits, "occurred-at", card.source.occurred_at) ++ if card.source.duration_ms is not None: ++ source_bits.append(f"duration-ms:{card.source.duration_ms}") ++ _append_source_bit(source_bits, "parent-message", card.source.parent_message_id) ++ if card.source.variant_index is not None: ++ source_bits.append(f"variant:{card.source.variant_index}") ++ _append_bool_source_bit(source_bits, "active-path", card.source.is_active_path) ++ _append_bool_source_bit(source_bits, "active-leaf", card.source.is_active_leaf) ++ _append_bool_source_bit(source_bits, "inherited-prefix", card.source.inherited_prefix) ++ _append_source_bit(source_bits, "result-message", card.source.result_message_id) ++ _append_source_bit(source_bits, "result-block", card.source.result_block_id) ++ if card.source.result_block_id is None and card.source.result_block_index is not None: + source_bits.append(f"result-block-index:{card.source.result_block_index}") ++ if card.source.result_duration_ms is not None: ++ source_bits.append(f"result-duration-ms:{card.source.result_duration_ms}") ++ _append_source_bit(source_bits, "result-material", card.source.result_material_origin) ++ _append_bool_source_bit(source_bits, "result-inherited-prefix", card.source.result_inherited_prefix) + parts.append("- evidence: " + ", ".join(_inline_code(value) for value in source_bits)) + + if card.outcome is not None: +@@ -68,20 +86,64 @@ def render_semantic_card_markdown(card: SemanticCard) -> str: + + + def render_semantic_cards_markdown(cards: Iterable[SemanticCard]) -> str: +- """Render cards without prose for server-side fallback surfaces.""" ++ """Render cards without prose for compatibility fallback surfaces.""" + + return "\n\n---\n\n".join(render_semantic_card_markdown(card) for card in cards) + + + def _render_prose(prose: TranscriptProse) -> str: + label = f"**{prose.role} · {prose.message_type}**" +- refs = [f"message:{prose.message_id}"] +- if prose.block_id: +- refs.append(f"block:{prose.block_id}") + if prose.block_type: + label += f" · {_inline_code(prose.block_type)}" ++ refs = [f"message:{prose.message_id}", f"provider:{prose.provider_family}"] ++ _append_source_bit(refs, "origin", prose.origin) ++ _append_source_bit(refs, "material", prose.material_origin) ++ _append_source_bit(refs, "block", prose.block_id) ++ if prose.block_id is None and prose.block_index is not None: ++ refs.append(f"block-index:{prose.block_index}") ++ _append_source_bit(refs, "occurred-at", prose.occurred_at) ++ if prose.duration_ms is not None: ++ refs.append(f"duration-ms:{prose.duration_ms}") ++ _append_source_bit(refs, "parent-message", prose.parent_message_id) ++ if prose.variant_index is not None: ++ refs.append(f"variant:{prose.variant_index}") ++ _append_bool_source_bit(refs, "active-path", prose.is_active_path) ++ _append_bool_source_bit(refs, "active-leaf", prose.is_active_leaf) ++ _append_bool_source_bit(refs, "inherited-prefix", prose.inherited_prefix) + rendered_refs = ", ".join(_inline_code(ref) for ref in refs) +- return f"{label} · {rendered_refs}\n\n{prose.text}" ++ ++ if prose.block_type == "code": ++ language = prose.language or "text" ++ body = _fenced(prose.text, language=language) ++ else: ++ body = prose.text ++ return f"{label} · {rendered_refs}\n\n{body}" ++ ++ ++def _render_notice(notice: TranscriptNotice) -> str: ++ if notice.kind is SemanticNoticeKind.EMPTY_THINKING: ++ noun = "typed block" if notice.count == 1 else "typed blocks" ++ heading = f"**thinking absent · {notice.count} {noun}**" ++ else: # pragma: no cover - closed enum keeps this defensive branch honest ++ heading = f"**{notice.kind.value} · {notice.count} sources**" ++ refs: list[str] = [] ++ for source in notice.sources: ++ coordinate = source.block_id or f"index:{source.block_index}" ++ ref = f"message:{source.message_id}/block:{coordinate}/type:{source.block_type}" ++ if source.inherited_prefix is not None: ++ ref += f"/inherited:{str(source.inherited_prefix).lower()}" ++ refs.append(ref) ++ return heading + "\n\n- evidence: " + ", ".join(_inline_code(ref) for ref in refs) ++ ++ ++def _append_source_bit(bits: list[str], label: str, value: str | None) -> None: ++ if value is not None: ++ bits.append(f"{label}:{value}") ++ ++ ++def _append_bool_source_bit(bits: list[str], label: str, value: bool | None) -> None: ++ if value is not None: ++ bits.append(f"{label}:{str(value).lower()}") + + + def _status_label(card: SemanticCard) -> str | None: +@@ -107,6 +169,8 @@ def _render_preview(preview: SemanticCardPreview) -> str: + "input": "json", + "raw_input": "text", + "output": "text", ++ "content": "text", ++ "matches": "text", + "result": "text", + "raw_result": "text", + }.get(preview.kind, "text") +@@ -134,8 +198,6 @@ def _inline_code(value: str) -> str: + """Render a Markdown code span without corrupting values containing ticks.""" + + fence = "`" * max(1, _longest_backtick_run(value) + 1) +- # CommonMark requires padding when content starts or ends with a backtick, +- # and padding is harmless for other embedded-tick values. + padding = " " if "`" in value or value.startswith(" ") or value.endswith(" ") else "" + return f"{fence}{padding}{value}{padding}{fence}" + +diff --git a/polylogue/storage/sqlite/archive_tiers/write.py b/polylogue/storage/sqlite/archive_tiers/write.py +index 46ca47b86ba3da0946e4b4c09e92c4e4c9365d9c..ba5dc6d96a273fe5e680362c71e964789be96569 100644 +--- a/polylogue/storage/sqlite/archive_tiers/write.py ++++ b/polylogue/storage/sqlite/archive_tiers/write.py +@@ -120,6 +120,10 @@ class ArchiveMessageRow: + duration_ms: int = 0 + parent_message_id: str | None = None + attachments: tuple[ArchiveAttachmentRow, ...] = () ++ # Exact composition provenance for semantic transcript rendering. Parent ++ # rows retain their original source session when a prefix-sharing child is ++ # composed, so inherited evidence is not guessed from position. ++ source_session_id: str | None = None + + + @dataclass(frozen=True, slots=True) +@@ -161,6 +165,11 @@ class ArchiveSessionEnvelope: + # divergent tail when the parent's branch point was hard-deleted. + lineage_complete: bool = True + lineage_truncation_reason: LineageTruncationReason | None = None ++ # Bounded composition facts established during this exact archive read. ++ # ``none`` is explicit rather than NULL so callers can distinguish a root ++ # or ordinary child from an unavailable composition signal. ++ lineage_inheritance: str = "none" ++ lineage_branch_point_message_id: str | None = None + + + @dataclass(frozen=True, slots=True) +@@ -927,6 +936,7 @@ def read_archive_session_envelope( + duration_ms=int(message["duration_ms"] or 0), + parent_message_id=message["parent_message_id"], + attachments=tuple(attachments_by_message.get(message["message_id"], ())), ++ source_session_id=str(session["session_id"]), + ) + ) + +@@ -944,8 +954,12 @@ def read_archive_session_envelope( + # composed view includes that parent's transcript. + lineage_complete = True + lineage_truncation_reason: LineageTruncationReason | None = None ++ lineage_inheritance = "none" ++ lineage_branch_point_message_id: str | None = None + edge = _prefix_sharing_edge_sync(conn, str(session["session_id"])) + if edge is not None: ++ lineage_inheritance = "prefix-sharing" ++ parent_session_id, lineage_branch_point_message_id = edge + if _depth >= _MAX_LINEAGE_DEPTH: + lineage_complete = False + lineage_truncation_reason = LINEAGE_TRUNCATION_DEPTH_LIMIT +@@ -955,14 +969,13 @@ def read_archive_session_envelope( + session["session_id"], + ) + else: +- parent_session_id, branch_point_message_id = edge + parent_envelope = read_archive_session_envelope(conn, parent_session_id, _depth=_depth + 1) + parent_messages = parent_envelope.messages + prefix: list[ArchiveMessageRow] = [] + found = False + for parent_message in parent_messages: + prefix.append(parent_message) +- if parent_message.message_id == branch_point_message_id: ++ if parent_message.message_id == lineage_branch_point_message_id: + found = True + break + # Dangling branch point (parent message hard-deleted): keep this +@@ -993,6 +1006,8 @@ def read_archive_session_envelope( + messages=tuple(messages), + lineage_complete=lineage_complete, + lineage_truncation_reason=lineage_truncation_reason, ++ lineage_inheritance=lineage_inheritance, ++ lineage_branch_point_message_id=lineage_branch_point_message_id, + parent_session_id=session["parent_session_id"], + root_session_id=session["root_session_id"], + branch_type=session["branch_type"], +diff --git a/tests/data/semantic_cards/cases/attachment-card.json b/tests/data/semantic_cards/cases/attachment-card.json +index 54a668bbba70ec3f068cf6e69e55b5947002a983..a39efe7e8bd8cc8be592a91566ff48985806d434 100644 +--- a/tests/data/semantic_cards/cases/attachment-card.json ++++ b/tests/data/semantic_cards/cases/attachment-card.json +@@ -40,7 +40,10 @@ + "provider_family": "chatgpt", + "message_id": "m-attachment", + "block_id": "b-image", +- "block_index": 0 ++ "block_index": 0, ++ "tool_id": "b-image", ++ "attachment_id": "b-image", ++ "material_origin": "unknown" + }, + "fields": [ + { +@@ -58,7 +61,7 @@ + ], + "previews": [], + "caveats": [ +- "attachment bytes are not embedded in the transcript card" ++ "attachment bytes are not embedded in the transcript document" + ], + "summary": "diagram.png" + } +diff --git a/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json b/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json +index 8f45488e639a01f30ca529cbaed3c498e075b0e5..f78e34207c0d1251786bc22b3552bb9b70a6d4ec 100644 +--- a/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json ++++ b/tests/data/semantic_cards/cases/chatgpt-canmore-edit.json +@@ -62,15 +62,25 @@ + "block_index": 0, + "tool_name": "canmore.update_textdoc", + "tool_id": "c1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "canmore.update_textdoc" + }, ++ { ++ "label": "semantic family", ++ "value": "file_edit" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "path", + "value": "canvas.md" +diff --git a/tests/data/semantic_cards/cases/chatgpt-web-fallback.json b/tests/data/semantic_cards/cases/chatgpt-web.json +similarity index 78% +rename from tests/data/semantic_cards/cases/chatgpt-web-fallback.json +rename to tests/data/semantic_cards/cases/chatgpt-web.json +index 0e998139892364e858fe0d8d6d0803176cd43dd2..94877a5b0506d36c0cb9b81eb4e24f283963c8fa 100644 +--- a/tests/data/semantic_cards/cases/chatgpt-web-fallback.json ++++ b/tests/data/semantic_cards/cases/chatgpt-web.json +@@ -3,7 +3,7 @@ + "claim": "Known but unsupported web tools remain raw fallback cards; the renderer does not invent a web card.", + "oracle": "Hand-authored compact contract plus exact frozen card JSON.", + "provider_family": "chatgpt", +- "session_id": "fixture:chatgpt-web-fallback", ++ "session_id": "fixture:chatgpt-web", + "messages": [ + { + "id": "m-use", +@@ -45,7 +45,7 @@ + "expected_contract": { + "card_count": 1, + "kinds": [ +- "fallback" ++ "web" + ], + "outcomes": [ + "succeeded" +@@ -54,37 +54,41 @@ + "expected_cards": [ + { + "schema_version": "semantic-card.v1", +- "kind": "fallback", +- "title": "Tool evidence · web", ++ "kind": "web", ++ "title": "Web request", + "source": { +- "session_id": "fixture:chatgpt-web-fallback", ++ "session_id": "fixture:chatgpt-web", + "provider_family": "chatgpt", + "message_id": "m-use", + "block_id": "b-use", + "block_index": 0, + "tool_name": "web", + "tool_id": "c1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "web" ++ }, ++ { ++ "label": "semantic family", ++ "value": "web" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, ++ { ++ "label": "query", ++ "value": "semantic transcript renderer" + } + ], + "previews": [ +- { +- "kind": "input", +- "text": "{\n \"search_query\": [\n {\n \"q\": \"semantic transcript renderer\"\n }\n ]\n}", +- "line_count": 7, +- "omitted_lines": 0, +- "omitted_characters": 0, +- "truncated": false, +- "strategy": "full", +- "encoding_replacements": 0 +- }, + { + "kind": "result", + "text": "results returned", +@@ -96,9 +100,8 @@ + "encoding_replacements": 0 + } + ], +- "caveats": [ +- "tool classification is unknown; raw evidence is shown without a guessed semantic card type" +- ], ++ "caveats": [], ++ "summary": "semantic transcript renderer", + "outcome": { + "state": "succeeded", + "is_error": false +diff --git a/tests/data/semantic_cards/cases/claude-bash-explicit-success.json b/tests/data/semantic_cards/cases/claude-bash-explicit-success.json +index b76b9a1229b39ee0c5538d399eadff8a30450be6..51d598ba0439951f62917d941304640dd7109a1a 100644 +--- a/tests/data/semantic_cards/cases/claude-bash-explicit-success.json ++++ b/tests/data/semantic_cards/cases/claude-bash-explicit-success.json +@@ -61,15 +61,25 @@ + "block_index": 0, + "tool_name": "Bash", + "tool_id": "t1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "Bash" + }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "command", + "value": "pytest -q" +diff --git a/tests/data/semantic_cards/cases/claude-edit-diff.json b/tests/data/semantic_cards/cases/claude-edit-diff.json +index ea8d45b4e1fb3a6e4f30bca5f2c22478386826fe..9887c19713d077bef7dd9b37c7b4452087ecbd53 100644 +--- a/tests/data/semantic_cards/cases/claude-edit-diff.json ++++ b/tests/data/semantic_cards/cases/claude-edit-diff.json +@@ -62,15 +62,25 @@ + "block_index": 0, + "tool_name": "Edit", + "tool_id": "t1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "Edit" + }, ++ { ++ "label": "semantic family", ++ "value": "file_edit" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "path", + "value": "src/app.py" +diff --git a/tests/data/semantic_cards/cases/claude-interleaved-subagents.json b/tests/data/semantic_cards/cases/claude-interleaved-subagents.json +index e5b8a6a57913d153fb81247c17566effce023b80..40b29f8dd73ab7874587ad2bced9593c20b25cfa 100644 +--- a/tests/data/semantic_cards/cases/claude-interleaved-subagents.json ++++ b/tests/data/semantic_cards/cases/claude-interleaved-subagents.json +@@ -79,7 +79,7 @@ + { + "schema_version": "semantic-card.v1", + "kind": "task", +- "title": "Task / subagent", ++ "title": "Task / delegation", + "source": { + "session_id": "fixture:claude-interleaved-subagents", + "provider_family": "claude-code", +@@ -88,15 +88,25 @@ + "block_index": 0, + "tool_name": "Task", + "tool_id": "t1", ++ "material_origin": "unknown", + "result_message_id": "m-result-1", + "result_block_id": "b-result-1", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "Task" + }, ++ { ++ "label": "semantic family", ++ "value": "subagent" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "agent", + "value": "Explore" +@@ -144,7 +154,7 @@ + { + "schema_version": "semantic-card.v1", + "kind": "task", +- "title": "Task / subagent", ++ "title": "Task / delegation", + "source": { + "session_id": "fixture:claude-interleaved-subagents", + "provider_family": "claude-code", +@@ -153,15 +163,25 @@ + "block_index": 1, + "tool_name": "Task", + "tool_id": "t2", ++ "material_origin": "unknown", + "result_message_id": "m-result-2", + "result_block_id": "b-result-2", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "Task" + }, ++ { ++ "label": "semantic family", ++ "value": "subagent" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "agent", + "value": "Test" +diff --git a/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json b/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json +index cba6e0b385b81438245cbff89f6673cf00b98ef8..ed5fc1040b8565d2a4eae975cd219c9beecd8134 100644 +--- a/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json ++++ b/tests/data/semantic_cards/cases/codex-apply-patch-missing-result.json +@@ -45,13 +45,22 @@ + "block_id": "b-use", + "block_index": 0, + "tool_name": "apply_patch", +- "tool_id": "call-1" ++ "tool_id": "call-1", ++ "material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "apply_patch" + }, ++ { ++ "label": "semantic family", ++ "value": "file_edit" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "path", + "value": "src/core.py" +diff --git a/tests/data/semantic_cards/cases/codex-conflicting-outcome.json b/tests/data/semantic_cards/cases/codex-conflicting-outcome.json +index d53334708b99e6a32dde79373bfe69ed10b6eb19..3c263ec9daf3dbb4f2b3dd10a3e01707ec1fab33 100644 +--- a/tests/data/semantic_cards/cases/codex-conflicting-outcome.json ++++ b/tests/data/semantic_cards/cases/codex-conflicting-outcome.json +@@ -61,15 +61,25 @@ + "block_index": 0, + "tool_name": "exec_command", + "tool_id": "call-1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "exec_command" + }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "command", + "value": "python check.py" +diff --git a/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json b/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json +index 5c1a485f8f5e5e16883bd0e78c6d86e531bc6b80..d9d5fbcc212f9f45c5e01a7466d5836f84822499 100644 +--- a/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json ++++ b/tests/data/semantic_cards/cases/codex-exec-failure-ten-thousand-lines.json +@@ -63,15 +63,25 @@ + "block_index": 0, + "tool_name": "exec_command", + "tool_id": "call-1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "exec_command" + }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "command", + "value": "pytest tests/huge.py -q" +diff --git a/tests/data/semantic_cards/cases/codex-search.json b/tests/data/semantic_cards/cases/codex-search.json +new file mode 100644 +index 0000000000000000000000000000000000000000..011bb1503793435853a96294fabfa70015cf0947 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/codex-search.json +@@ -0,0 +1,127 @@ ++{ ++ "session_id": "fixture:codex-search", ++ "provider_family": "codex", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "origin": "codex-session", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "search", ++ "tool_id": "search-1", ++ "semantic_type": "search", ++ "tool_input": { ++ "query": "semantic_entries", ++ "path": "polylogue" ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "origin": "codex-session", ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "search-1", ++ "text": "polylogue/daemon/http.py: semantic_entries", ++ "tool_result_exit_code": 0 ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "search" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "search", ++ "title": "Search", ++ "source": { ++ "session_id": "fixture:codex-search", ++ "provider_family": "codex", ++ "origin": "codex-session", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "tool_name": "search", ++ "tool_id": "search-1", ++ "material_origin": "unknown", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "search" ++ }, ++ { ++ "label": "semantic family", ++ "value": "search" ++ }, ++ { ++ "label": "classification", ++ "value": "persisted_semantic_type" ++ }, ++ { ++ "label": "query", ++ "value": "semantic_entries" ++ }, ++ { ++ "label": "scope", ++ "value": "polylogue" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "matches", ++ "text": "polylogue/daemon/http.py: semantic_entries", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "semantic_entries", ++ "outcome": { ++ "state": "succeeded", ++ "exit_code": 0 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "query": "semantic_entries", ++ "path": "polylogue" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "polylogue/daemon/http.py: semantic_entries", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/empty-thinking-run.json b/tests/data/semantic_cards/cases/empty-thinking-run.json +new file mode 100644 +index 0000000000000000000000000000000000000000..24716eda2156d7e1a4e92ed30f1e6d1959689dff +--- /dev/null ++++ b/tests/data/semantic_cards/cases/empty-thinking-run.json +@@ -0,0 +1,188 @@ ++{ ++ "session_id": "fixture:empty-thinking-run", ++ "provider_family": "chatgpt", ++ "freeze_entries": true, ++ "messages": [ ++ { ++ "id": "m-empty-a", ++ "role": "assistant", ++ "message_type": "message", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "occurred_at": "2026-07-17T08:00:00+00:00", ++ "duration_ms": 3, ++ "parent_id": "m-user", ++ "variant_index": 1, ++ "is_active_path": true, ++ "is_active_leaf": false, ++ "inherited_prefix": true, ++ "blocks": [ ++ { ++ "id": "think-a", ++ "type": "thinking", ++ "text": "" ++ }, ++ { ++ "id": "reason-a", ++ "type": "reasoning", ++ "text": " " ++ } ++ ] ++ }, ++ { ++ "id": "m-separator", ++ "role": "assistant", ++ "message_type": "message", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "blocks": [ ++ { ++ "id": "text-separator", ++ "type": "text", ++ "text": "Visible answer." ++ } ++ ] ++ }, ++ { ++ "id": "m-empty-b", ++ "role": "assistant", ++ "message_type": "message", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "blocks": [ ++ { ++ "id": "reason-b", ++ "type": "reasoning", ++ "text": null ++ } ++ ] ++ }, ++ { ++ "id": "m-thinking", ++ "role": "assistant", ++ "message_type": "message", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "blocks": [ ++ { ++ "id": "think-b", ++ "type": "thinking", ++ "text": "A retained typed thought." ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 0, ++ "kinds": [], ++ "outcomes": [], ++ "entry_types": [ ++ "notice", ++ "prose", ++ "notice", ++ "prose" ++ ], ++ "notice_counts": [ ++ 2, ++ 1 ++ ] ++ }, ++ "expected_cards": [], ++ "expected_entries": [ ++ { ++ "entry_type": "notice", ++ "notice": { ++ "kind": "empty_thinking", ++ "count": 2, ++ "sources": [ ++ { ++ "message_id": "m-empty-a", ++ "block_index": 0, ++ "block_type": "thinking", ++ "block_id": "think-a", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "chatgpt", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "occurred_at": "2026-07-17T08:00:00+00:00", ++ "duration_ms": 3, ++ "parent_message_id": "m-user", ++ "variant_index": 1, ++ "is_active_path": true, ++ "is_active_leaf": false, ++ "inherited_prefix": true ++ }, ++ { ++ "message_id": "m-empty-a", ++ "block_index": 1, ++ "block_type": "reasoning", ++ "block_id": "reason-a", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "chatgpt", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "occurred_at": "2026-07-17T08:00:00+00:00", ++ "duration_ms": 3, ++ "parent_message_id": "m-user", ++ "variant_index": 1, ++ "is_active_path": true, ++ "is_active_leaf": false, ++ "inherited_prefix": true ++ } ++ ] ++ } ++ }, ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-separator", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "chatgpt", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "text": "Visible answer.", ++ "block_id": "text-separator", ++ "block_index": 0, ++ "block_type": "text" ++ } ++ }, ++ { ++ "entry_type": "notice", ++ "notice": { ++ "kind": "empty_thinking", ++ "count": 1, ++ "sources": [ ++ { ++ "message_id": "m-empty-b", ++ "block_index": 0, ++ "block_type": "reasoning", ++ "block_id": "reason-b", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "chatgpt", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant" ++ } ++ ] ++ } ++ }, ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-thinking", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "chatgpt", ++ "origin": "chatgpt-export", ++ "material_origin": "assistant", ++ "text": "A retained typed thought.", ++ "block_id": "think-b", ++ "block_index": 0, ++ "block_type": "thinking" ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/gemini-file-read.json b/tests/data/semantic_cards/cases/gemini-file-read.json +new file mode 100644 +index 0000000000000000000000000000000000000000..5c29cbb43e93b19938838fde8d3783f4fdcbd5dd +--- /dev/null ++++ b/tests/data/semantic_cards/cases/gemini-file-read.json +@@ -0,0 +1,140 @@ ++{ ++ "session_id": "fixture:gemini-file-read", ++ "provider_family": "gemini-cli", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "origin": "gemini-cli-session", ++ "duration_ms": 7, ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "read_file", ++ "tool_id": "read-1", ++ "tool_input": { ++ "file_path": "src/main.py", ++ "offset": 10, ++ "limit": 20 ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "origin": "gemini-cli-session", ++ "duration_ms": 11, ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "read-1", ++ "text": "def main():\n pass\n", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "file_read" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "file_read", ++ "title": "File read", ++ "source": { ++ "session_id": "fixture:gemini-file-read", ++ "provider_family": "gemini-cli", ++ "origin": "gemini-cli-session", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "tool_name": "read_file", ++ "tool_id": "read-1", ++ "material_origin": "unknown", ++ "duration_ms": 7, ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_duration_ms": 11, ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "read_file" ++ }, ++ { ++ "label": "semantic family", ++ "value": "file_read" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, ++ { ++ "label": "duration", ++ "value": "11 ms" ++ }, ++ { ++ "label": "path", ++ "value": "src/main.py" ++ }, ++ { ++ "label": "offset", ++ "value": "10" ++ }, ++ { ++ "label": "limit", ++ "value": "20" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "content", ++ "text": "def main():\n pass\n", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "src/main.py", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "file_path": "src/main.py", ++ "offset": 10, ++ "limit": 20 ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "def main():\n pass\n", ++ "line_count": 2, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json b/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json +index fda30ebabfd17a8f5131d697c21b3d55c71fbc29..9013a4b658247e34f12a27ed665fc08b0bfb6207 100644 +--- a/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json ++++ b/tests/data/semantic_cards/cases/gemini-shell-null-outcome.json +@@ -60,15 +60,25 @@ + "block_index": 0, + "tool_name": "run_shell_command", + "tool_id": "g1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "run_shell_command" + }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "persisted_semantic_type" ++ }, + { + "label": "command", + "value": "make verify" +diff --git a/tests/data/semantic_cards/cases/gemini-write-file.json b/tests/data/semantic_cards/cases/gemini-write-file.json +index 131ce87c2e84b88839f55e69af33ffbd054dfdb0..e994e95d97c0c0451aea9efcd4c4eeaaa83a29bb 100644 +--- a/tests/data/semantic_cards/cases/gemini-write-file.json ++++ b/tests/data/semantic_cards/cases/gemini-write-file.json +@@ -53,7 +53,7 @@ + { + "schema_version": "semantic-card.v1", + "kind": "file_edit", +- "title": "File edit", ++ "title": "File write", + "source": { + "session_id": "fixture:gemini-write-file", + "provider_family": "gemini-cli", +@@ -62,15 +62,25 @@ + "block_index": 0, + "tool_name": "write_file", + "tool_id": "g1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "write_file" + }, ++ { ++ "label": "semantic family", ++ "value": "file_write" ++ }, ++ { ++ "label": "classification", ++ "value": "persisted_semantic_type" ++ }, + { + "label": "path", + "value": "notes.txt" +diff --git a/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json b/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json +index 9554605347a459f107ece3e10272b24dedf1ab68..ad25f22121ab5bc5cc79cd579494775d8174aec8 100644 +--- a/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json ++++ b/tests/data/semantic_cards/cases/hermes-shell-non-utf8.json +@@ -63,15 +63,25 @@ + "block_index": 0, + "tool_name": "shell", + "tool_id": "h1", ++ "material_origin": "unknown", + "result_message_id": "m-result", + "result_block_id": "b-result", +- "result_block_index": 0 ++ "result_block_index": 0, ++ "result_material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "shell" + }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, + { + "label": "command", + "value": "cat binary.log" +diff --git a/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json b/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json +index 19f0ab6bcd430c2a3ae64e54ffba6a9a349dc426..340e3d5dbd35f57339152e614285ee4cd383d85b 100644 +--- a/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json ++++ b/tests/data/semantic_cards/cases/hermes-truncated-unknown-input.json +@@ -42,12 +42,21 @@ + "block_id": "b-use", + "block_index": 0, + "tool_name": "mystery_tool", +- "tool_id": "h1" ++ "tool_id": "h1", ++ "material_origin": "unknown" + }, + "fields": [ + { + "label": "tool", + "value": "mystery_tool" ++ }, ++ { ++ "label": "semantic family", ++ "value": "other" ++ }, ++ { ++ "label": "classification", ++ "value": "generic_fallback" + } + ], + "previews": [ +@@ -63,7 +72,8 @@ + } + ], + "caveats": [ +- "tool classification is unknown; raw evidence is shown without a guessed semantic card type", ++ "generic fallback policy: no persisted semantic_type, MCP identity, or grounded exact alias is present", ++ "raw evidence is shown without a guessed specialized card type", + "tool input was retained as raw text because it was not valid JSON", + "no structurally paired tool result is present" + ], +diff --git a/tests/data/semantic_cards/cases/lineage-unknown-boundary.json b/tests/data/semantic_cards/cases/lineage-unknown-boundary.json +index 8ac1d8da85a19a1b11ffeded551a1b2f60132639..b4326ac753246342d97f75b244525d9fea57063c 100644 +--- a/tests/data/semantic_cards/cases/lineage-unknown-boundary.json ++++ b/tests/data/semantic_cards/cases/lineage-unknown-boundary.json +@@ -51,13 +51,21 @@ + "value": "session:fixture:lineage-unknown-boundary" + }, + { +- "label": "root", +- "value": "session:fixture:root" ++ "label": "authority", ++ "value": "session_row" ++ }, ++ { ++ "label": "availability", ++ "value": "partial" + }, + { + "label": "relation", + "value": "unknown" + }, ++ { ++ "label": "root", ++ "value": "session:fixture:root" ++ }, + { + "label": "parent", + "value": "session:fixture:parent" +@@ -65,7 +73,8 @@ + ], + "previews": [], + "caveats": [ +- "the parent edge is structural but its narrower lineage relation is unknown" ++ "transcript-composition completeness is unknown from this bounded lineage authority", ++ "the structural lineage relation is unknown" + ], + "summary": "session:fixture:lineage-unknown-boundary" + } +diff --git a/tests/data/semantic_cards/cases/mcp-tool.json b/tests/data/semantic_cards/cases/mcp-tool.json +new file mode 100644 +index 0000000000000000000000000000000000000000..7b03f4a76915f886d85afdea275aa2e0cd521c34 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/mcp-tool.json +@@ -0,0 +1,140 @@ ++{ ++ "session_id": "fixture:mcp-tool", ++ "provider_family": "antigravity", ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "origin": "antigravity-session", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "mcp__github__get_issue", ++ "tool_id": "mcp-1", ++ "tool_input": { ++ "name": "polylogue", ++ "id": 395 ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "origin": "antigravity-session", ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "mcp-1", ++ "text": "issue loaded", ++ "tool_result_is_error": false ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "mcp" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "mcp", ++ "title": "MCP tool call", ++ "source": { ++ "session_id": "fixture:mcp-tool", ++ "provider_family": "antigravity", ++ "origin": "antigravity-session", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "tool_name": "mcp__github__get_issue", ++ "tool_id": "mcp-1", ++ "material_origin": "unknown", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_material_origin": "unknown" ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "mcp__github__get_issue" ++ }, ++ { ++ "label": "semantic family", ++ "value": "other" ++ }, ++ { ++ "label": "classification", ++ "value": "mcp_tool_identity" ++ }, ++ { ++ "label": "MCP server", ++ "value": "github" ++ }, ++ { ++ "label": "MCP tool", ++ "value": "get_issue" ++ }, ++ { ++ "label": "target", ++ "value": "polylogue" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "input", ++ "text": "{\n \"id\": 395,\n \"name\": \"polylogue\"\n}", ++ "line_count": 4, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ }, ++ { ++ "kind": "result", ++ "text": "issue loaded", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "github/get_issue", ++ "outcome": { ++ "state": "succeeded", ++ "is_error": false ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "name": "polylogue", ++ "id": 395 ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": "issue loaded", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/message-attachment-envelope.json b/tests/data/semantic_cards/cases/message-attachment-envelope.json +new file mode 100644 +index 0000000000000000000000000000000000000000..06806d264807088d8e4be54bb584727837bdf319 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/message-attachment-envelope.json +@@ -0,0 +1,155 @@ ++{ ++ "session_id": "fixture:message-attachment-envelope", ++ "provider_family": "claude-ai", ++ "freeze_entries": true, ++ "messages": [ ++ { ++ "id": "m-attachment", ++ "role": "user", ++ "message_type": "message", ++ "origin": "claude-ai-export", ++ "material_origin": "human", ++ "text": "Review the attached report.", ++ "attachments": [ ++ { ++ "id": "att-7", ++ "name": "report.pdf", ++ "mime_type": "application/pdf", ++ "size_bytes": 2048, ++ "path": "attachments/report.pdf", ++ "source_url": "blob:sha256:def", ++ "caption": "Q2 report", ++ "upload_origin": "user_upload" ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "attachment" ++ ], ++ "outcomes": [ ++ null ++ ], ++ "entry_types": [ ++ "prose", ++ "card" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:message-attachment-envelope", ++ "provider_family": "claude-ai", ++ "origin": "claude-ai-export", ++ "message_id": "m-attachment", ++ "attachment_id": "att-7", ++ "material_origin": "human" ++ }, ++ "fields": [ ++ { ++ "label": "name", ++ "value": "report.pdf" ++ }, ++ { ++ "label": "media type", ++ "value": "application/pdf" ++ }, ++ { ++ "label": "size", ++ "value": "2048 bytes" ++ }, ++ { ++ "label": "path", ++ "value": "attachments/report.pdf" ++ }, ++ { ++ "label": "source URL", ++ "value": "blob:sha256:def" ++ }, ++ { ++ "label": "upload origin", ++ "value": "user_upload" ++ }, ++ { ++ "label": "caption", ++ "value": "Q2 report" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript document" ++ ], ++ "summary": "report.pdf" ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-attachment", ++ "role": "user", ++ "message_type": "message", ++ "provider_family": "claude-ai", ++ "origin": "claude-ai-export", ++ "material_origin": "human", ++ "text": "Review the attached report." ++ } ++ }, ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "attachment", ++ "title": "Attachment", ++ "source": { ++ "session_id": "fixture:message-attachment-envelope", ++ "provider_family": "claude-ai", ++ "origin": "claude-ai-export", ++ "message_id": "m-attachment", ++ "attachment_id": "att-7", ++ "material_origin": "human" ++ }, ++ "fields": [ ++ { ++ "label": "name", ++ "value": "report.pdf" ++ }, ++ { ++ "label": "media type", ++ "value": "application/pdf" ++ }, ++ { ++ "label": "size", ++ "value": "2048 bytes" ++ }, ++ { ++ "label": "path", ++ "value": "attachments/report.pdf" ++ }, ++ { ++ "label": "source URL", ++ "value": "blob:sha256:def" ++ }, ++ { ++ "label": "upload origin", ++ "value": "user_upload" ++ }, ++ { ++ "label": "caption", ++ "value": "Q2 report" ++ } ++ ], ++ "previews": [], ++ "caveats": [ ++ "attachment bytes are not embedded in the transcript document" ++ ], ++ "summary": "report.pdf" ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/mixed-tool-result-context.json b/tests/data/semantic_cards/cases/mixed-tool-result-context.json +new file mode 100644 +index 0000000000000000000000000000000000000000..d4755abe0d47f5c6e2ed5a5d58facf8178dfc494 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/mixed-tool-result-context.json +@@ -0,0 +1,253 @@ ++{ ++ "session_id": "fixture:mixed-tool-result-context", ++ "provider_family": "claude-code", ++ "freeze_entries": true, ++ "messages": [ ++ { ++ "id": "m-use", ++ "role": "assistant", ++ "message_type": "tool_use", ++ "origin": "claude-code-session", ++ "blocks": [ ++ { ++ "id": "b-use", ++ "type": "tool_use", ++ "tool_name": "Bash", ++ "tool_id": "mixed-1", ++ "tool_input": { ++ "command": "git status --short" ++ } ++ } ++ ] ++ }, ++ { ++ "id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "origin": "claude-code-session", ++ "material_origin": "tool_result", ++ "text": "Independent envelope context: cached branch state retained.", ++ "parent_id": "m-use", ++ "variant_index": 2, ++ "is_active_path": true, ++ "is_active_leaf": true, ++ "inherited_prefix": false, ++ "blocks": [ ++ { ++ "id": "b-result", ++ "type": "tool_result", ++ "tool_id": "mixed-1", ++ "text": " M tracked.py", ++ "tool_result_exit_code": 0 ++ }, ++ { ++ "id": "b-context", ++ "type": "text", ++ "text": "Operator context beside the protocol row." ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 1, ++ "kinds": [ ++ "shell" ++ ], ++ "outcomes": [ ++ "succeeded" ++ ], ++ "entry_types": [ ++ "card", ++ "prose", ++ "prose" ++ ] ++ }, ++ "expected_cards": [ ++ { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:mixed-tool-result-context", ++ "provider_family": "claude-code", ++ "origin": "claude-code-session", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "tool_name": "Bash", ++ "tool_id": "mixed-1", ++ "material_origin": "unknown", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_material_origin": "tool_result", ++ "result_inherited_prefix": false ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Bash" ++ }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, ++ { ++ "label": "command", ++ "value": "git status --short" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": " M tracked.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "git status --short", ++ "outcome": { ++ "state": "succeeded", ++ "exit_code": 0 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "git status --short" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": " M tracked.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ ], ++ "expected_entries": [ ++ { ++ "entry_type": "card", ++ "card": { ++ "schema_version": "semantic-card.v1", ++ "kind": "shell", ++ "title": "Shell command", ++ "source": { ++ "session_id": "fixture:mixed-tool-result-context", ++ "provider_family": "claude-code", ++ "origin": "claude-code-session", ++ "message_id": "m-use", ++ "block_id": "b-use", ++ "block_index": 0, ++ "tool_name": "Bash", ++ "tool_id": "mixed-1", ++ "material_origin": "unknown", ++ "result_message_id": "m-result", ++ "result_block_id": "b-result", ++ "result_block_index": 0, ++ "result_material_origin": "tool_result", ++ "result_inherited_prefix": false ++ }, ++ "fields": [ ++ { ++ "label": "tool", ++ "value": "Bash" ++ }, ++ { ++ "label": "semantic family", ++ "value": "shell" ++ }, ++ { ++ "label": "classification", ++ "value": "exact_alias" ++ }, ++ { ++ "label": "command", ++ "value": "git status --short" ++ } ++ ], ++ "previews": [ ++ { ++ "kind": "output", ++ "text": " M tracked.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ ], ++ "caveats": [], ++ "summary": "git status --short", ++ "outcome": { ++ "state": "succeeded", ++ "exit_code": 0 ++ }, ++ "raw_evidence": { ++ "tool_input": { ++ "command": "git status --short" ++ }, ++ "result_preview": { ++ "kind": "raw_result", ++ "text": " M tracked.py", ++ "line_count": 1, ++ "omitted_lines": 0, ++ "omitted_characters": 0, ++ "truncated": false, ++ "strategy": "full", ++ "encoding_replacements": 0 ++ } ++ } ++ } ++ }, ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "provider_family": "claude-code", ++ "origin": "claude-code-session", ++ "material_origin": "tool_result", ++ "text": "Operator context beside the protocol row.", ++ "block_id": "b-context", ++ "block_index": 1, ++ "block_type": "text", ++ "parent_message_id": "m-use", ++ "variant_index": 2, ++ "is_active_path": true, ++ "is_active_leaf": true, ++ "inherited_prefix": false ++ } ++ }, ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-result", ++ "role": "tool", ++ "message_type": "tool_result", ++ "provider_family": "claude-code", ++ "origin": "claude-code-session", ++ "material_origin": "tool_result", ++ "text": "Independent envelope context: cached branch state retained.", ++ "parent_message_id": "m-use", ++ "variant_index": 2, ++ "is_active_path": true, ++ "is_active_leaf": true, ++ "inherited_prefix": false ++ } ++ } ++ ] ++} +diff --git a/tests/data/semantic_cards/cases/orphan-result.json b/tests/data/semantic_cards/cases/orphan-result.json +index a993e2ee24a2bb69f3e1cb7def4a7d66dcdce042..30feeb02d2c32156fc8b32697ad79532f6773ac4 100644 +--- a/tests/data/semantic_cards/cases/orphan-result.json ++++ b/tests/data/semantic_cards/cases/orphan-result.json +@@ -39,12 +39,18 @@ + "source": { + "session_id": "fixture:orphan-result", + "provider_family": "gemini-cli", ++ "message_id": "m-result", ++ "block_id": "b-result", ++ "block_index": 0, + "tool_id": "missing-call", +- "result_message_id": "m-result", +- "result_block_id": "b-result", +- "result_block_index": 0 ++ "material_origin": "unknown" + }, +- "fields": [], ++ "fields": [ ++ { ++ "label": "tool id", ++ "value": "missing-call" ++ } ++ ], + "previews": [ + { + "kind": "result", +diff --git a/tests/data/semantic_cards/cases/typed-prose-metadata.json b/tests/data/semantic_cards/cases/typed-prose-metadata.json +new file mode 100644 +index 0000000000000000000000000000000000000000..f029633692642bad0c69894fc1523383ad0cc6c1 +--- /dev/null ++++ b/tests/data/semantic_cards/cases/typed-prose-metadata.json +@@ -0,0 +1,91 @@ ++{ ++ "session_id": "fixture:typed-prose-metadata", ++ "provider_family": "grok", ++ "freeze_entries": true, ++ "messages": [ ++ { ++ "id": "m-prose", ++ "role": "assistant", ++ "message_type": "message", ++ "origin": "grok-export", ++ "material_origin": "assistant", ++ "occurred_at": "2026-07-17T09:30:00+00:00", ++ "duration_ms": 42, ++ "parent_id": "m-parent", ++ "variant_index": 3, ++ "is_active_path": false, ++ "is_active_leaf": false, ++ "inherited_prefix": true, ++ "blocks": [ ++ { ++ "id": "b-reason", ++ "type": "reasoning", ++ "text": "Typed reasoning with source identity." ++ }, ++ { ++ "id": "b-code", ++ "type": "code", ++ "language": "python", ++ "text": "print(\"semantic\")" ++ } ++ ] ++ } ++ ], ++ "expected_contract": { ++ "card_count": 0, ++ "kinds": [], ++ "outcomes": [], ++ "entry_types": [ ++ "prose", ++ "prose" ++ ] ++ }, ++ "expected_cards": [], ++ "expected_entries": [ ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-prose", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "grok", ++ "origin": "grok-export", ++ "material_origin": "assistant", ++ "text": "Typed reasoning with source identity.", ++ "block_id": "b-reason", ++ "block_index": 0, ++ "block_type": "reasoning", ++ "occurred_at": "2026-07-17T09:30:00+00:00", ++ "duration_ms": 42, ++ "parent_message_id": "m-parent", ++ "variant_index": 3, ++ "is_active_path": false, ++ "is_active_leaf": false, ++ "inherited_prefix": true ++ } ++ }, ++ { ++ "entry_type": "prose", ++ "prose": { ++ "message_id": "m-prose", ++ "role": "assistant", ++ "message_type": "message", ++ "provider_family": "grok", ++ "origin": "grok-export", ++ "material_origin": "assistant", ++ "text": "print(\"semantic\")", ++ "block_id": "b-code", ++ "block_index": 1, ++ "block_type": "code", ++ "language": "python", ++ "occurred_at": "2026-07-17T09:30:00+00:00", ++ "duration_ms": 42, ++ "parent_message_id": "m-parent", ++ "variant_index": 3, ++ "is_active_path": false, ++ "is_active_leaf": false, ++ "inherited_prefix": true ++ } ++ } ++ ] ++} +diff --git a/tests/unit/cli/test_messages.py b/tests/unit/cli/test_messages.py +index efe0381853ba158fa39a0b20c8cbcf0d46a9e1d2..b08a33085540276bc667ad3f3d817920972e0498 100644 +--- a/tests/unit/cli/test_messages.py ++++ b/tests/unit/cli/test_messages.py +@@ -14,7 +14,6 @@ from polylogue.cli.read_views.messages import _write_messages_file + from polylogue.cli.root_request import RootModeRequest + from polylogue.cli.shared.types import AppEnv + from polylogue.config import Config +-from polylogue.insights.topology import SessionTopology + + SCHEMAS_DIR = Path("docs/schemas/cli-output") + +@@ -33,7 +32,7 @@ class _FakeApi: + raw_result: tuple[list[dict[str, object]], int] = ([], 0), + paginate_messages: bool = False, + session_origin: str = "codex-session", +- topology: SessionTopology | None = None, ++ topology: object | None = None, + ) -> None: + self.messages_result = messages_result + self.raw_result = raw_result +@@ -125,10 +124,18 @@ class _FakeApi: + return self.raw_result + + async def get_session(self, session_id: str) -> object: +- del session_id +- return type("_FakeSession", (), {"origin": self.session_origin})() +- +- async def get_session_topology(self, session_id: str) -> SessionTopology | None: ++ return type( ++ "_FakeSession", ++ (), ++ { ++ "id": session_id, ++ "origin": self.session_origin, ++ "parent_id": None, ++ "branch_type": None, ++ }, ++ )() ++ ++ async def get_session_topology(self, session_id: str) -> object | None: + del session_id + return self.topology + +diff --git a/tests/unit/core/test_tool_identity.py b/tests/unit/core/test_tool_identity.py +new file mode 100644 +index 0000000000000000000000000000000000000000..aa6cbfd22080f62ffd5dc69bf35e69819a3b9f52 +--- /dev/null ++++ b/tests/unit/core/test_tool_identity.py +@@ -0,0 +1,19 @@ ++from __future__ import annotations ++ ++from polylogue.core.tool_identity import extract_mcp_server, parse_mcp_tool_name ++ ++ ++def test_mcp_identity_preserves_server_and_server_local_tool_name() -> None: ++ identity = parse_mcp_tool_name("mcp__github__search__issues") ++ ++ assert identity is not None ++ assert identity.server == "github" ++ assert identity.tool == "search__issues" ++ assert extract_mcp_server(identity.raw_name) == "github" ++ ++ ++def test_mcp_identity_rejects_names_without_both_structural_segments() -> None: ++ assert parse_mcp_tool_name("mcp__github") is None ++ assert parse_mcp_tool_name("mcp____tool") is None ++ assert parse_mcp_tool_name("mcp__github__") is None ++ assert parse_mcp_tool_name("search") is None +diff --git a/tests/unit/rendering/test_semantic_cards.py b/tests/unit/rendering/test_semantic_cards.py +index 7801bed04c63d923bd92f01dd89101e9b579f70f..0c59f223bd3224e1690ca139496399a028f3e8da 100644 +--- a/tests/unit/rendering/test_semantic_cards.py ++++ b/tests/unit/rendering/test_semantic_cards.py +@@ -4,31 +4,44 @@ import ast + import copy + import json + from pathlib import Path ++from types import SimpleNamespace + from typing import cast + + import jsonschema + import pytest + +-from devtools.render_semantic_card_fixtures import _load_case, render_case ++from devtools.render_semantic_card_fixtures import ( ++ _load_case, ++ build_case_transcript, ++ render_case, ++ render_case_entries, ++) + from devtools.render_semantic_card_fixtures import main as fixture_main ++from polylogue.core.enums import Origin + from polylogue.core.json import JSONDocument, require_json_document ++from polylogue.rendering.block_models import coerce_renderable_block + from polylogue.rendering.semantic_card_models import ( + SemanticCard, + SemanticCardField, + SemanticCardKind, + SemanticCardSource, + ) ++from polylogue.rendering.semantic_card_placement import semantic_card_placement_for_messages + from polylogue.rendering.semantic_card_registry import ( + card_kind_for_tool, + provider_namespace_documents, + semantic_type_policy_documents, + tool_mapping_rows, + ) +-from polylogue.rendering.semantic_cards import build_semantic_transcript ++from polylogue.rendering.semantic_cards import ( ++ build_semantic_transcript, ++ lineage_descriptor_from_archive_envelope, ++) + from polylogue.rendering.semantic_markdown import render_semantic_card_markdown + + CASES_ROOT = Path("tests/data/semantic_cards/cases") + SCHEMA_PATH = Path("docs/schemas/semantic-card-v1.schema.json") ++TRANSCRIPT_SCHEMA_PATH = Path("docs/schemas/semantic-transcript-v1.schema.json") + CASE_PATHS = sorted(CASES_ROOT.glob("*.json")) + + +@@ -43,6 +56,29 @@ def _expected(case: dict[str, object]) -> list[JSONDocument]: + return [require_json_document(item, context="expected semantic card") for item in value] + + ++def test_attribute_block_metadata_retains_persisted_display_fields() -> None: ++ block = SimpleNamespace( ++ type=SimpleNamespace(value="image"), ++ text=None, ++ metadata=json.dumps( ++ { ++ "language": "python", ++ "url": "https://example.test/plot.png", ++ "name": "plot", ++ "media_type": "image/png", ++ } ++ ), ++ ) ++ ++ rendered = coerce_renderable_block(block) ++ ++ assert rendered is not None ++ assert rendered.language == "python" ++ assert rendered.url == "https://example.test/plot.png" ++ assert rendered.name == "plot" ++ assert rendered.mime_type == "image/png" ++ ++ + @pytest.mark.parametrize("case_path", CASE_PATHS, ids=lambda path: path.stem) + def test_semantic_card_golden_cases(case_path: Path) -> None: + case = _case(case_path) +@@ -65,8 +101,28 @@ def test_golden_corpus_covers_every_card_kind_and_provider_family() -> None: + providers.add(case.get("provider_family")) + kinds.update(card.get("kind") for card in _expected(case)) + +- assert providers == {"claude-code", "codex", "gemini-cli", "chatgpt", "hermes"} +- assert kinds == {"shell", "file_edit", "task", "lineage", "attachment", "fallback"} ++ assert providers == { ++ "antigravity", ++ "chatgpt", ++ "claude-ai", ++ "claude-code", ++ "codex", ++ "gemini-cli", ++ "grok", ++ "hermes", ++ } ++ assert kinds == { ++ "attachment", ++ "fallback", ++ "file_edit", ++ "file_read", ++ "lineage", ++ "mcp", ++ "search", ++ "shell", ++ "task", ++ "web", ++ } + + + def test_registry_is_explicit_unique_and_evidence_backed() -> None: +@@ -111,20 +167,32 @@ def test_semantic_type_policy_is_exhaustive_over_persisted_vocabulary() -> None: + assert by_type["shell"]["card_kind"] == "shell" + assert by_type["file_edit"]["card_kind"] == "file_edit" + assert by_type["subagent"]["rendering_status"] == "model_only" +- assert by_type["search"]["card_kind"] == "fallback" ++ assert by_type["search"]["card_kind"] == "search" ++ assert by_type["web"]["card_kind"] == "web" ++ assert by_type["file_read"]["card_kind"] == "file_read" + + + def test_provider_namespace_policy_discloses_open_world_fallback() -> None: + policies = provider_namespace_documents() ++ assert {item["origin"] for item in policies} == {origin.value for origin in Origin} + assert {item["provider_family"] for item in policies} == { ++ "antigravity", ++ "beads", ++ "chatgpt", ++ "claude-ai", + "claude-code", + "codex", + "gemini-cli", +- "chatgpt", ++ "grok", + "hermes", ++ "unknown", + } + assert all(item["namespace"] == "open" for item in policies) +- assert all(item["unlisted_behavior"] == "persisted_semantic_type_then_fallback_raw_evidence" for item in policies) ++ assert all( ++ item["unlisted_behavior"] ++ == "structural_mcp_then_persisted_semantic_type_then_fallback_raw_evidence" ++ for item in policies ++ ) + + + def test_unknown_tool_remains_fallback_even_when_input_looks_like_shell() -> None: +@@ -253,12 +321,14 @@ def test_fixture_checker_rejects_corrupted_frozen_card( + + def test_pure_renderer_modules_do_not_import_storage_api_or_daemon() -> None: + modules = ( ++ Path("polylogue/core/tool_identity.py"), ++ Path("polylogue/rendering/block_models.py"), + Path("polylogue/rendering/semantic_card_models.py"), + Path("polylogue/rendering/semantic_card_registry.py"), + Path("polylogue/rendering/semantic_cards.py"), + Path("polylogue/rendering/semantic_markdown.py"), + ) +- forbidden = ("polylogue.storage", "polylogue.api", "polylogue.daemon") ++ forbidden = ("polylogue.storage", "polylogue.api", "polylogue.daemon", "polylogue.insights") + violations: list[str] = [] + for path in modules: + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) +@@ -339,3 +409,182 @@ def test_markdown_inline_code_preserves_backticks() -> None: + rendered = render_semantic_card_markdown(card) + assert "`` src/`odd`.py ``" in rendered + assert card_document["kind"] == "file_edit" ++ ++ ++@pytest.mark.parametrize("case_path", CASE_PATHS, ids=lambda path: path.stem) ++def test_semantic_transcript_golden_cases_satisfy_public_schema(case_path: Path) -> None: ++ """Production dependency: SemanticTranscript.to_document and transcript schema. ++ ++ Mutation control: removing an entry discriminator or source coordinate makes ++ jsonschema validation fail instead of allowing a card-only compatibility ++ projection to stand in for the full transcript. ++ """ ++ ++ schema = json.loads(TRANSCRIPT_SCHEMA_PATH.read_text(encoding="utf-8")) ++ document = build_case_transcript(_case(case_path), case_path=case_path).to_document() ++ jsonschema.validate(instance=document, schema=schema) ++ ++ ++def test_result_serialized_before_use_is_paired_once() -> None: ++ """Production dependency: semantic_cards._pair_tool_results whole-document FIFO. ++ ++ Mutation control: restoring one-pass pairing emits an orphan fallback before ++ the shell card and this exact one-entry assertion fails. ++ """ ++ ++ case_path = CASES_ROOT / "result-before-use.json" ++ entries = render_case_entries(_case(case_path), case_path=case_path) ++ assert [entry["entry_type"] for entry in entries] == ["card"] ++ card = cast(dict[str, object], entries[0]["card"]) ++ assert card["kind"] == "shell" ++ assert cast(dict[str, object], card["source"])["result_message_id"] == "m-result" ++ ++ ++def test_empty_typed_thinking_compacts_by_contiguous_typed_run() -> None: ++ """Production dependency: typed THINKING/REASONING run compaction. ++ ++ Mutation control: treating whitespace as prose, grouping across the text ++ separator, or dropping empty blocks changes entry types/counts/source ids. ++ """ ++ ++ case_path = CASES_ROOT / "empty-thinking-run.json" ++ entries = render_case_entries(_case(case_path), case_path=case_path) ++ notices = [cast(dict[str, object], entry["notice"]) for entry in entries if entry["entry_type"] == "notice"] ++ assert [notice["count"] for notice in notices] == [2, 1] ++ first_sources = cast(list[dict[str, object]], notices[0]["sources"]) ++ assert [(source["block_type"], source["block_id"]) for source in first_sources] == [ ++ ("thinking", "think-a"), ++ ("reasoning", "reason-a"), ++ ] ++ assert first_sources[0]["inherited_prefix"] is True ++ assert first_sources[0]["variant_index"] == 1 ++ ++ ++def test_mixed_result_context_prevents_protocol_row_suppression() -> None: ++ """Production dependency: shared placement over all semantic entry kinds. ++ ++ Mutation control: suppressing every paired result message loses both context ++ prose entries and makes ``is_suppressed('m-result')`` true. ++ """ ++ ++ case_path = CASES_ROOT / "mixed-tool-result-context.json" ++ case = _case(case_path) ++ placement = semantic_card_placement_for_messages( ++ cast(list[dict[str, object]], case["messages"]), ++ session_id=str(case["session_id"]), ++ provider_family=str(case["provider_family"]), ++ ) ++ result_entries = placement.entries_for("m-result") ++ assert placement.is_suppressed("m-result") is False ++ assert [entry["entry_type"] for entry in result_entries] == ["prose", "prose"] ++ assert [cast(dict[str, object], entry["prose"])["text"] for entry in result_entries] == [ ++ "Operator context beside the protocol row.", ++ "Independent envelope context: cached branch state retained.", ++ ] ++ ++ ++def test_pure_paired_result_row_is_suppressed() -> None: ++ """Production dependency: placement suppression is conditional on no independent entry.""" ++ ++ case_path = CASES_ROOT / "result-before-use.json" ++ case = _case(case_path) ++ placement = semantic_card_placement_for_messages( ++ cast(list[dict[str, object]], case["messages"]), ++ session_id=str(case["session_id"]), ++ provider_family=str(case["provider_family"]), ++ ) ++ assert placement.is_suppressed("m-result") is True ++ assert placement.entries_for("m-result") == [] ++ ++ ++def test_chatgpt_web_recipient_extracts_structured_query_without_raw_json_title() -> None: ++ """Production dependency: explicit ChatGPT web input structure and specialized web builder.""" ++ ++ case_path = CASES_ROOT / "chatgpt-web.json" ++ card = render_case(_case(case_path), case_path=case_path)[0] ++ assert card["kind"] == "web" ++ assert card["summary"] == "semantic transcript renderer" ++ assert any( ++ field["label"] == "query" and field["value"] == "semantic transcript renderer" ++ for field in cast(list[dict[str, object]], card["fields"]) ++ ) ++ ++ ++def test_mcp_identity_precedes_generic_persisted_family() -> None: ++ """Production dependency: structural MCP identity has highest registry precedence.""" ++ ++ kind = card_kind_for_tool( ++ provider_family="unknown", ++ tool_name="mcp__github__get_issue", ++ semantic_type="other", ++ ) ++ assert kind is SemanticCardKind.MCP ++ ++ ++def test_message_attachment_envelope_retains_attachment_identity() -> None: ++ case_path = CASES_ROOT / "message-attachment-envelope.json" ++ entries = render_case_entries(_case(case_path), case_path=case_path) ++ card = cast(dict[str, object], entries[1]["card"]) ++ source = cast(dict[str, object], card["source"]) ++ assert source["attachment_id"] == "att-7" ++ assert source["origin"] == "claude-ai-export" ++ assert _field_value(card, "size") == "2048 bytes" ++ ++ ++def _field_value(card: dict[str, object], label: str) -> str | None: ++ for field in cast(list[dict[str, object]], card["fields"]): ++ if field["label"] == label: ++ return cast(str, field["value"]) ++ return None ++ ++ ++ ++def test_archive_lineage_descriptor_preserves_exact_composition_authority() -> None: ++ """Production dependency: archive envelope facts, not inferred ancestry. ++ ++ Mutation control: forcing resolved/root/completeness certainty or dropping ++ the branch point changes this exact bounded descriptor. ++ """ ++ ++ descriptor = lineage_descriptor_from_archive_envelope( ++ SimpleNamespace( ++ session_id="child", ++ origin="codex-session", ++ parent_session_id="parent", ++ root_session_id="root", ++ branch_type="fork", ++ lineage_complete=True, ++ lineage_truncation_reason=None, ++ lineage_inheritance="prefix-sharing", ++ lineage_branch_point_message_id="parent:p1", ++ active_leaf_message_id="child:c2", ++ ) ++ ) ++ assert descriptor.root_session_id == "root" ++ assert descriptor.parent_session_id == "parent" ++ assert descriptor.resolved is True ++ assert descriptor.lineage_complete is True ++ assert descriptor.inherited_prefix is True ++ assert descriptor.branch_point_message_id == "parent:p1" ++ assert descriptor.availability.value == "complete" ++ ++ ++def test_archive_lineage_descriptor_does_not_invent_resolution_when_truncated() -> None: ++ descriptor = lineage_descriptor_from_archive_envelope( ++ SimpleNamespace( ++ session_id="child", ++ origin="codex-session", ++ parent_session_id="parent", ++ root_session_id=None, ++ branch_type="fork", ++ lineage_complete=False, ++ lineage_truncation_reason="depth_limit", ++ lineage_inheritance="prefix-sharing", ++ lineage_branch_point_message_id="parent:p1", ++ active_leaf_message_id=None, ++ ) ++ ) ++ assert descriptor.root_session_id is None ++ assert descriptor.resolved is None ++ assert descriptor.availability.value == "partial" ++ assert descriptor.lineage_truncation_reason == "depth_limit" +diff --git a/tests/unit/storage/test_lineage_normalization.py b/tests/unit/storage/test_lineage_normalization.py +index 04c6009a1007c7bb1b6922c7020e86137a9bfabf..f58b26e9566075a752e571edf5e15eca266c2f9e 100644 +--- a/tests/unit/storage/test_lineage_normalization.py ++++ b/tests/unit/storage/test_lineage_normalization.py +@@ -145,6 +145,18 @@ def test_prefix_sharing_child_stores_only_tail_and_composes(tmp_path: Path) -> N + "child diverges here", + "child reply", + ] ++ # Production dependency: read_archive_session_envelope records exact ++ # composition provenance instead of making renderers infer a prefix from ++ # message position. Removing source_session_id or the bounded edge facts ++ # makes these assertions fail. ++ assert envelope.lineage_inheritance == "prefix-sharing" ++ assert envelope.lineage_branch_point_message_id == link["branch_point_message_id"] ++ assert [message.source_session_id for message in envelope.messages] == [ ++ parent_id, ++ parent_id, ++ child_id, ++ child_id, ++ ] + + conn.close() + +diff --git a/tests/visual/test_reader_semantic_cards.py b/tests/visual/test_reader_semantic_cards.py +index 6f7d6cb26d749d243a6500d685e5add062bbb2bb..4dfaf0c0d9793ffae5918f0976d0ac534aae45d4 100644 +--- a/tests/visual/test_reader_semantic_cards.py ++++ b/tests/visual/test_reader_semantic_cards.py +@@ -2,17 +2,16 @@ + + Pins the daemon web shell's contract for the shared CLI/web renderer + registry: the daemon session-detail routes (``daemon/http.py``) attach +-``semantic_cards``/``semantic_card_suppressed`` to every message from the +-exact ``SemanticCard`` registry the CLI renders to Markdown ++``semantic_entries`` plus compatibility ``semantic_cards`` to every message ++from the exact ``SemanticTranscript`` registry the CLI renders to Markdown + (``rendering/semantic_cards.py``, exercised by + ``tests/unit/rendering/test_semantic_cards.py``), and the web reader +-(``daemon/web_shell_semantic_cards.py``) renders that same card JSON to DOM. ++(``daemon/web_shell_semantic_cards.py``) renders that same ordered entry JSON ++to DOM without reclassifying raw roles or prose. + +-Structure parity between the two backends is proven directly: every card the +-web route emits is validated against the public +-``docs/schemas/semantic-card-v1.schema.json`` contract — the same schema the +-CLI-side golden-case corpus validates against — rather than asserted by +-inspection. ++Structure parity is proven directly against both public schemas on the actual ++paginated message route used by the browser, rather than inferred from the ++full-detail compatibility route. + """ + + from __future__ import annotations +@@ -41,6 +40,7 @@ from tests.visual.conftest import ( + ) + + SEMANTIC_CARD_SCHEMA_PATH = Path("docs/schemas/semantic-card-v1.schema.json") ++SEMANTIC_TRANSCRIPT_SCHEMA_PATH = Path("docs/schemas/semantic-transcript-v1.schema.json") + + + def _message(messages: list[dict[str, object]], message_id: str) -> dict[str, object]: +@@ -73,7 +73,7 @@ def _outcome(card: dict[str, object]) -> dict[str, object]: + def _semantic_card_session(reader_workspace: ReaderWorkspace) -> tuple[str, dict[str, object]]: + with running_reader_server(reader_workspace) as (_, base_url): + seed_reader_semantic_cards(reader_workspace) +- payload = get_json(base_url, f"/api/sessions/{READER_SEM1}") ++ payload = get_json(base_url, f"/api/sessions/{READER_SEM1}/messages?limit=100&offset=0") + assert isinstance(payload, dict) + return base_url, payload + +@@ -83,6 +83,20 @@ def test_semantic_card_web_json_contract(_semantic_card_session: tuple[str, dict + messages = cast("list[dict[str, object]]", payload["messages"]) + + schema = json.loads(SEMANTIC_CARD_SCHEMA_PATH.read_text(encoding="utf-8")) ++ transcript_schema = json.loads(SEMANTIC_TRANSCRIPT_SCHEMA_PATH.read_text(encoding="utf-8")) ++ ordered_entries = cast("list[dict[str, object]]", payload.get("semantic_entries", [])) + [ ++ entry ++ for message in messages ++ for entry in cast("list[dict[str, object]]", message["semantic_entries"]) ++ ] ++ jsonschema.validate( ++ instance={ ++ "schema_version": "semantic-transcript.v1", ++ "session_id": READER_SEM1, ++ "entries": ordered_entries, ++ }, ++ schema=transcript_schema, ++ ) + all_cards: list[dict[str, object]] = [] + for message in messages: + cards = cast("list[dict[str, object]]", message["semantic_cards"]) +@@ -155,9 +169,10 @@ def test_semantic_card_web_dom_shape_contract(reader_workspace: ReaderWorkspace, + # message fully absorbed into another message's card, rather than + # re-deriving tool semantics from raw ``has_tool_use`` flags alone. + for phrase in ( +- "_polySemanticCardsForMessage", ++ "_polySemanticEntriesForMessage", + "semantic_card_suppressed", +- "_polySemanticCardsHtml", ++ "_polySemanticEntriesHtml", ++ "_polySemanticSessionEntriesHtml", + 'class="sem-card sem-card-', + "sem-card-header", + "sem-card-fields", +@@ -176,7 +191,7 @@ def test_semantic_card_web_dom_shape_contract(reader_workspace: ReaderWorkspace, + checks={ + "status": status, + "content_type": content_type, +- "semantic_card_renderer_wired": True, ++ "semantic_transcript_renderer_wired": True, + "card_kinds_covered": ["shell", "file_edit", "task"], + "suppression_wired": True, + "private_path_safe": True, diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/TESTS.md new file mode 100644 index 000000000..65db16f9e --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/extracted/TESTS.md @@ -0,0 +1,196 @@ +# Test design and execution record + +## Test strategy + +The tests are designed around production dependencies rather than presentation snapshots alone. Each behavior names the implementation seam that establishes it and a representative mutation or removal that makes the test fail. + +| Behavior | Production dependency exercised | Anti-vacuity mutation/removal | +|---|---|---| +| Complete persisted-family policy | `semantic_type_policy_documents()` over `SemanticBlockType` and `_SEMANTIC_CARD_KIND`/fallback policy | Remove one enum row or one policy mapping; exhaustive set/card-kind assertions fail. | +| Complete executable-origin policy | `origin_policy_documents()` over `Origin` | Remove one `OriginPolicy`; the test comparing policy origins with `set(Origin)` fails. | +| Exact alias evidence | `_TOOL_MAPPINGS` and committed evidence paths | Delete/rename an alias or point evidence to a file that does not contain the tool name; registry evidence test fails. | +| Structural MCP classification | `parse_mcp_tool_name()` and `classify_tool()` precedence | Parse by substring, omit the server/tool split, or move MCP below provider aliases; MCP helper/fixture tests fail. | +| All semantic card kinds | builders in `semantic_cards.py` and the 23-case corpus | Remove a builder or redirect one kind to fallback; golden equality and all-kinds coverage fail. | +| Structural outcomes | `_outcome_from_result()` using `is_error`/`exit_code` | Infer failure from output prose or default missing fields to success; hostile success/failure/unknown fixtures fail. | +| Result serialized before use | `_pair_tool_results()` pre-indexes uses/results and records paired result coordinates | Return to a one-pass “seen use” map or fail to mark the earlier result as paired; `result-before-use` emits a duplicate orphan and fails. | +| Mixed result/context message | placement computes `paired_result_ids - independent_message_ids` | Suppress every paired result message regardless of independent prose/context; mixed-row fixture and placement assertions fail. | +| Empty-thinking compaction | typed `_THINKING_BLOCK_TYPES`, `.strip()`, pending run, explicit flush boundaries | Treat whitespace as content, compact by provider/role, or fail to flush on a typed non-thinking row; ordered notice tests fail. | +| Non-empty thinking retained | `_prose_for_block()` path for typed thinking/reasoning with content | Drop all thinking blocks or convert them to absence notices; fixture entry sequence fails. | +| Exact authoredness/variant metadata | `RenderableMessage`, `TranscriptProse`, notice sources, `SemanticCardSource` | Remove material origin, parent, variant, active-path/leaf, duration, or inherited-prefix projection; `typed-prose-metadata` and source assertions fail. | +| Metadata-backed runtime blocks | `_block_attribute_mapping()` plus metadata fallbacks in `_coerce_mapping_block()` | Remove metadata fallback for language/URL/name/media type; `test_attribute_block_metadata_retains_persisted_display_fields` fails. | +| Message-envelope attachments | `_coerce_renderable_attachments()` and `_build_attachment_envelope_card()` | Render only typed attachment blocks or remove envelope projection; attachment-envelope fixture fails. | +| Exact archive inherited-prefix evidence | `ArchiveMessageRow.source_session_id` and envelope inheritance/branch point | Remove source-session propagation or edge facts; added lineage normalization assertions fail. | +| Honest bounded lineage | session/archive descriptor builders and `_build_lineage_card()` | Invent a root/completeness value from a session row or drop archive truncation reason; lineage tests fail. | +| CLI/web structural parity | both surfaces consume `SemanticTranscript`/entry documents | Reintroduce a web-side tool classifier or attach entries only to the full-detail route; visual contract source and actual-route test fail. | +| Browser authoritative empty array | `_polySemanticEntriesForMessage()` checks `Array.isArray`, not length | Treat `[]` as “missing” and fall back to raw role/text; DOM source-contract assertion fails and paired rows regain duplicate rendering. | +| Public contract validity | both JSON Schemas plus fixture/route validation | Add an undeclared field, omit required entry discriminator, or emit an unknown kind; JSON Schema validation fails. | + +## Focused automated tests executed + +### Shared renderer and contract suite + +Command: + +```text +/opt/pyvenv/bin/python -m pytest -q -o addopts='' \ + tests/unit/rendering/test_semantic_cards.py \ + --confcutdir=tests/unit/rendering +``` + +Result: + +```text +96 passed, 2 warnings in 2.29s +``` + +The warnings are `PytestConfigWarning` reports for unavailable `timeout` and `timeout_method` plugin options in this reduced runtime. They are not test failures. + +This suite covers golden cards, ordered transcript entries, public schemas, exhaustive origin/family policy, structural outcomes, result-before-use pairing, mixed rows, empty-thinking runs, typed prose metadata, attachments, MCP, lineage descriptors, placement/suppression, Markdown output, and generator idempotence. + +### Frozen fixture generator + +Command: + +```text +/opt/pyvenv/bin/python -m devtools.render_semantic_card_fixtures --check +``` + +Result: + +```text +semantic-card fixtures: verified 23 case(s) +``` + +### Generated registry + +Command: + +```text +/opt/pyvenv/bin/python -m devtools.render_semantic_card_registry --check +``` + +Result: + +```text +semantic-card registry: verified 2 surface(s) +``` + +### Topology inventories + +Commands: + +```text +/opt/pyvenv/bin/python -m devtools render topology-projection --check +/opt/pyvenv/bin/python -m devtools render topology-status --check +``` + +Result: both commands exited successfully. The projection reports 1,008 rows and 9 existing TBD ownership rows. + +### Independent schema validation + +A direct Python check used `jsonschema.Draft202012Validator.check_schema` on both public schemas, rebuilt each fixture transcript through the production renderer, validated every transcript, and separately validated every nested card against `semantic-card.v1`. + +Result: + +```text +validated 23 transcript fixtures and 22 card entries +``` + +### Shared MCP identity tests + +The two fixture-free functions in `tests/unit/core/test_tool_identity.py` were executed directly with `runpy`, avoiding unrelated repository conftest imports. + +Result: + +```text +executed 2 standalone core identity tests +``` + +### Python compilation and diff hygiene + +All changed Python paths, including new files, were compiled with `py_compile`. + +Result: + +```text +compiled 21 changed Python files +``` + +`git diff --check` exited successfully. + +### Browser JavaScript syntax + +The literal `READER_JS`, `SEMANTIC_CARD_JS`, and the script block from the assembled `WEB_SHELL_HTML` template were extracted with Python AST parsing and passed individually to Node 22.16.0: + +```text +node --check /tmp/beads05-reader_js.js +node --check /tmp/beads05-semantic_card_js.js +node --check /tmp/beads05-web-shell-0.js +``` + +Result: all three scripts reported syntax OK. + +### Patch certification + +`PATCH.diff` was generated from a temporary Git index rooted at the named snapshot so tracked edits, new files, deletion/rename state, and generated text are all represented without altering the repository index. + +Checks performed: + +```text +git -C /mnt/data/beads05-work/base-repo apply --check PATCH.diff +git clone --no-hardlinks /mnt/data/beads05-work/base-repo +git -C checkout f654480cadb7cc4c194704e24dfd483199547b35 +git -C apply PATCH.diff +git -C diff --check +``` + +Every changed path in the applied clone was then compared byte-for-byte with the implementation workspace. + +Result: + +```text +git apply --check: OK +byte-for-byte apply comparison OK for 50 changed paths +actual patch apply: OK +``` + +Patch properties: + +```text +275,889 bytes +7,265 lines +49 diff records / 50 paths (one rename) +4,119 insertions / 536 deletions +``` + +The patch contains no `GIT binary patch`/`Binary files` marker, no supplied archive path, and no implementation placeholder marker. + +## Tests added or strengthened but not executed in this runtime + +`tests/unit/cli/test_messages.py` now supplies a realistic bounded session row to the CLI renderer. The existing structural shell test exercises the same shared renderer, but normal module import is blocked here by missing `dateparser` in the repository dependency chain. + +`tests/unit/storage/test_lineage_normalization.py::test_prefix_sharing_child_stores_only_tail_and_composes` now asserts exact `lineage_inheritance`, branch-point identity, and per-message `source_session_id`. Collection reaches unrelated source-package imports and is blocked by missing dependencies (`ijson`, then `tenacity`; the normal suite also requires `aiosqlite`). + +`tests/visual/test_reader_semantic_cards.py` now targets the browser’s real paginated `/api/sessions/{id}/messages` route, validates the reconstructed ordered transcript against `semantic-transcript.v1`, validates nested cards against `semantic-card.v1`, and pins the authoritative DOM hook. The daemon visual harness was not executable because the complete locked runtime dependencies are absent. + +The existing ChatGPT parser regression from PR #2629, which reparses recipient-addressed tool calls as `TOOL_USE`, was inspected but not re-executed for the same dependency reason. + +## Repository-wide gates attempted or unavailable + +`/opt/pyvenv/bin/python -m devtools render all --check` was attempted. It stopped before running surfaces because importing the generated-surface catalog reached `polylogue/sources/decoders.py`, where `ijson` is unavailable. This is an environment dependency failure, not a reported generated drift result. + +The normal root/unit pytest harness is also incomplete in this runtime: collection encounters missing packages including `hypothesis`, `ijson`, `aiosqlite`, `tenacity`, and `dateparser` depending on the selected path. + +Ruff 0.15.20 and mypy are declared by the repository lock/config but no executable or cached wheel is available offline here. No Ruff or mypy pass is claimed. + +## Manual and deployment checks remaining + +The following checks require the operator environment and remain unverified: + +- locked `devtools verify` or at minimum `devtools verify --quick` plus affected testmon selection; +- Ruff format/lint and strict mypy on all changed production/test files; +- added CLI, storage, and visual tests through the normal managed harness; +- browser inspection of a real ChatGPT export containing multiple unavailable reasoning/thinking blocks and at least one non-empty typed thinking block; +- daemon DB-backed and archive-backed route comparison on a prefix-sharing session; +- performance measurement for the paginated DB route’s whole-session placement; +- live Nix/deployment, secrets, private archive, and browser-capture behavior. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/raw.zip b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/raw.zip new file mode 100644 index 000000000..8fd954f75 Binary files /dev/null and b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/beads-05/r02/raw.zip differ diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/index.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/index.json index 4fee5b7cb..5dcff9e22 100644 --- a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/index.json +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/beads/results/index.json @@ -89,7 +89,7 @@ { "job": "beads-06", "workload": "beads", - "state": "triaged_snapshot_relative", + "state": "triaged", "attempt_id": "a01", "package_revision": "r04", "artifact": "beads-06/a01/raw/beads-06-agent-manual-install-r04.zip", diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/08-agent-guidance-tool-discovery.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/08-agent-guidance-tool-discovery.md new file mode 100644 index 000000000..6f581906d --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/08-agent-guidance-tool-discovery.md @@ -0,0 +1,15 @@ +Title: "[research 08] Standing agent guidance and tool-discovery effectiveness" + +Job ID: `deep-research-08` + +Research evidence on making coding agents reliably use large local tool +ecosystems: standing versus retrieved instruction, tool/resource discovery, +capability catalogs, executable examples, error recovery, prompt caching, and +evaluation of spontaneous appropriate use. Seek counterevidence to both a huge +static manual and pointer-only guidance. + +Map conclusions to `polylogue-3gd.2`, `z9gh.3`, and `t46.8`. Evaluate the +proposed comprehensive project-generated SessionStart manual plus queryable +live catalog: what belongs in standing context, what should be discovered, how +examples remain executable, and how usefulness is measured. Do not substitute +arbitrary token caps for effectiveness evidence. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/09-archive-freshness-observability.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/09-archive-freshness-observability.md new file mode 100644 index 000000000..86ec83713 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/09-archive-freshness-observability.md @@ -0,0 +1,13 @@ +Title: "[research 09] Archive freshness, lag, and negative-result observability" + +Job ID: `deep-research-09` + +Research end-to-end freshness patterns where source bytes, acquisition, parse, +index, and derived views advance independently: watermarks, high-water marks, +lag, exclusions/dead letters, exact-key diagnostics, and user-facing negative +result semantics. Prefer primary sources and production evidence. + +Apply findings to the current raw ChatGPT capture that a recent archive query +cannot see. Map recommendations to `1xc.13`, `lkrc`, `yla8`, and `z9gh.3`. +Specify needed states and the exact source identity a model must receive; reject +global scans, aggregate-count “freshness,” and normal-operation repair rituals. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/10-agent-workflow-lineage.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/10-agent-workflow-lineage.md new file mode 100644 index 000000000..a46e93126 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/10-agent-workflow-lineage.md @@ -0,0 +1,14 @@ +Title: "[research 10] AI-agent workflow, task, and lineage evidence models" + +Job ID: `deep-research-10` + +Research interoperable models of multi-agent coding work without collapsing a +workflow run, lane/task, agent session, attempt, model invocation, artifact, +commit, PR, and Bead effect into one identity. Examine current telemetry, +provenance, workflow standards, and agent traces; cover forks, resumes, +subagents, retries, partial outputs, conflicting claims, and late evidence. + +Map conclusions to `1vpm.6`, `2qx.2`, `4ts`, `t8t`, and `z9gh.7`. Assess the +archive unit `workflow run → lane/task → sessions → commits/PRs/Beads effects` +and deliver a minimal evidence/edge taxonomy with uncertainty and falsifying +fixtures—not a proposal to replace Polylogue with an external standard. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/11-privacy-safe-structural-inference.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/11-privacy-safe-structural-inference.md new file mode 100644 index 000000000..fe40c70f7 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/deep-research/briefs/11-privacy-safe-structural-inference.md @@ -0,0 +1,15 @@ +Title: "[research 11] Privacy-safe structural inference from conversational archives" + +Job ID: `deep-research-11` + +Research privacy-preserving derivation of shareable structural schemas, +workload profiles, and synthetic data from private conversation archives. Cover +leakage through keys, rare strings, quasi-identifiers, nested payloads and +metadata; distinguish useful DP from practical allowlists, generalization, +thresholds, and release review. Seek failures where schema inference turns +observed text into public property names. + +Map results to `1xc.14.1.2`, `.1.3`, and `.1`. Recommend an enforceable +classification/release contract that distinguishes harmless operational +metadata from sensitive content, plus operator-review surfaces and a local +red-team corpus that could falsify it. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/index.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/index.json index c7ee3aa20..ba70a78d0 100644 --- a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/index.json +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/index.json @@ -59,6 +59,28 @@ "sha256": "c3843babc8fc0126401e471c9eaee1db26ad5ccdcabf0e1697d01157e591e47e", "bytes": 26025 }, + { + "job": "testdiet-03", + "workload": "testdiet", + "state": "triaged", + "attempt_id": "a02", + "package_revision": "r01", + "artifact": "testdiet-03/a02/raw/testdiet-03-rebuild-equivalence-r01(1).zip", + "sha256": "cf01225f0af536dd5142f9f374d4eb174a0abf6e1800fbc2aebe262351453258", + "bytes": 25098, + "integration": "Raw and extracted evidence retained. Current-master apply check fails; reconcile with the already-merged testdiet-03 delivery before source admission." + }, + { + "job": "testdiet-04", + "workload": "testdiet", + "state": "triaged", + "attempt_id": "a02", + "package_revision": "r01", + "artifact": "testdiet-04/a02/raw/testdiet-04-derived-freshness-r01.zip", + "sha256": "226be0e25fae6de8cfd087d84eefcaf8c9ca2b7797d341c66b26d8785e66ce8e", + "bytes": 61199, + "integration": "Clipboard corroborates the provider report. Current-master apply check fails; reconcile the proposed freshness protocol with current derived tiers before source admission." + }, { "job": "testdiet-07", "workload": "testdiet", @@ -129,32 +151,10 @@ "sha256": "34cdf6d724bd4d1591fad051dbea14016181309e3166dc3fe83ff7f086395c44", "bytes": 91343599 }, - { - "job": "testdiet-03", - "workload": "testdiet", - "state": "triaged_snapshot_relative", - "attempt_id": "a02", - "package_revision": "r01", - "artifact": "testdiet-03/a02/raw/testdiet-03-rebuild-equivalence-r01(1).zip", - "sha256": "cf01225f0af536dd5142f9f374d4eb174a0abf6e1800fbc2aebe262351453258", - "bytes": 25098, - "integration": "Raw and extracted evidence retained. Current-master apply check fails; reconcile with the already-merged testdiet-03 delivery before source admission." - }, - { - "job": "testdiet-04", - "workload": "testdiet", - "state": "triaged_snapshot_relative", - "attempt_id": "a02", - "package_revision": "r01", - "artifact": "testdiet-04/a02/raw/testdiet-04-derived-freshness-r01.zip", - "sha256": "226be0e25fae6de8cfd087d84eefcaf8c9ca2b7797d341c66b26d8785e66ce8e", - "bytes": 61199, - "integration": "Clipboard corroborates the provider report. Current-master apply check fails; reconcile the proposed freshness protocol with current derived tiers before source admission." - }, { "job": "testdiet-14", "workload": "testdiet", - "state": "rejected_validation_failed", + "state": "rejected", "attempt_id": "a01", "package_revision": "r01", "artifact": "testdiet-14/a01/raw/testdiet-14-config-path-coherence-r01.zip", diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..845f6ff7e --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/EVIDENCE.md @@ -0,0 +1,129 @@ +# Source, Bead, test, and history evidence + +## Snapshot authority + +The supplied project-state manifest identifies `/realm/project/polylogue`, branch `master`, commit `f654480cadb7cc4c194704e24dfd483199547b35`, generated at `2026-07-17T043202Z`, with `dirty: true`. + +The supplied branch-delta report identifies `origin/master` with merge base `f654480...`, no commits, no changed files, and an empty patch. Reconstructing a detached worktree at the commit and overlaying the captured `polylogue-working-tree.tar.gz` produced no Git status changes. Thus the exact content authority is the named commit/captured tree, while the dirty flag remains an unexplained snapshot-metadata discrepancy. + +The attached Test Diet exact-query dossier identifies the older head `21f78b4db2ba62ff44b5f16dfab96067bc249b4c` and says “Realized artifacts: 0.” Its proposed filenames remain useful, but its paths, readiness assumptions, and API descriptions cannot override the newer source snapshot. + +## Repository architecture findings + +`AGENTS.md` resolves to `CLAUDE.md`. The repository instructions state that substrate owns meaning and surfaces are leaf adapters; new semantics belong in storage/product before surfaces. They also identify `polylogue/archive/query/expression.py` as the real Lark query DSL and require focused `devtools test` use rather than blanket directory runs. `CONTRIBUTING.md` and `pyproject.toml` establish strict mypy and Ruff obligations. + +The current query path is not one function. The inspected chain is: + +```text +Lark expression + -> parsed AST / compiled query spec + -> archive execution or terminal-unit source + -> SQLite session/action relation + -> repository reader/count executor + -> Polylogue facade and public envelopes + -> root CLI read/action renderer +``` + +The daemon session-page fast-path support check rejects a compiled Boolean predicate. Therefore the Boolean root CLI law reaches the local repository path repaired by this patch rather than obtaining a false pass from a daemon adapter. + +## Production defect evidence + +In `_execute_archive_query_stdout`, the code constructs `_ArchiveFilterKwargs` from all compiled filters and conditionally adds `boolean_predicate`. Search, stats, count, mutation, and other branches consume that map or explicitly forward the predicate. The final bare-list branch instead repeated the individual keyword list and omitted `boolean_predicate`. + +Commit `5d5edaf496d70b9372c7c2123c2e70a6ed4d34e6` repaired two count call sites by forwarding `boolean_predicate`; it did not repair the bare `list_summaries` call. This explains the current contradiction with closed Bead `polylogue-70qb`: the Bead title and acceptance criteria require bare explicit Boolean list parity, while its close reason describes only the count-call fix. The new survivor exposed that residual implementation gap directly. + +The final patch uses the already-typed map: + +```python +summaries = archive.list_summaries( + limit=fetch_limit, + offset=page_offset, + sample=sample_count is not None, + sort=sort, + reverse=reverse, + **filter_kwargs, +) +``` + +This preserves all previous filter keys, includes `boolean_predicate` only when compiled, and removes a second manual forwarding inventory. + +## Relational cardinality evidence + +The canonical action relation ranks tool-use and tool-result blocks by `(session_id, tool_id)` and transcript ordering, then pairs equal ranks. It left-preserves tool uses without results. That implementation reflects the historical defect and repair recorded in: + +- `7b5a5aa0589772ed9d7eb9632af1591cf22cdd33` — pair actions by transcript rank rather than plain tool-ID equality; +- `9f0e77116280571c14430b47986e7bd5b0e8be1a` — closure evidence for `polylogue-xnkf`. + +The planted manifest reproduces the discriminating shape without copying a production answer: two uses plus two results share one tool ID, and one additional use is unmatched. Rank pairing yields three logical Bash rows. Equality joining yields four repeated-pair combinations plus the unmatched row, for five. The test asserts the exact wrong result. + +The current action query implementation also supports a bounded exact-session relation that pushes selected session IDs into both ranked branches. Existing C-03 tests prove semantic equality and a work-bound witness against a global-first mutant. The new law reuses that generated archive and receipt rather than inventing a parallel scale framework. + +## Test/helper audit + +The affected owning cluster was inspected in full. A rough AST/source census used only to understand proof form—not to authorize deletion—found: + +| File | Lines | Test functions | Mock/patch/fake signal | +| --- | ---: | ---: | ---: | +| `tests/unit/cli/test_query_expression.py` | 6,494 | 328 | 2 | +| `tests/unit/cli/test_query_exec_laws.py` | 3,552 | 66 | 46 | +| `tests/unit/cli/test_verb_cardinality.py` | 900 | 39 | 26 | +| `tests/unit/storage/test_archive_tiers_archive.py` | 1,483 | 24 | 1 | + +The expression suite preserves unique grammar, precedence, quoting, sequence, diagnostics, identity, and execution cases. The execution/cardinality suites contain valuable behavior plus many adapter-forwarding witnesses. The storage file owns the realized C-03 semantic/work-bound tests. + +Shared helpers inspected: + +- `tests/infra/query_cases.py` — a test-authored query case model; +- `tests/infra/surfaces.py` — surface adapters and `_query_case_to_spec`, a partial shadow lowering path; +- `tests/infra/semantic_facts.py` — shared semantic expected facts; +- `tests/infra/workload_artifacts.py` — content-addressed C-03 artifact, facts, manifest/receipt, and clone support. + +The new oracle does not replace those helpers globally. It is deliberately small and local to this responsibility. + +## Beads evidence + +| Bead | Status | Constraint or intent used | +| --- | --- | --- | +| `polylogue-1xc.14.1` | in progress | Real provider-native generated workloads, selective C-03 query, shared identities/receipts; do not invent another corpus framework. | +| `polylogue-b054.1.1.4` | open | A trustworthy affected gate ultimately needs a real production mutation and actual testmon selection. This package proves the direct mutation but does not claim testmon closure. | +| `polylogue-xnkf` | closed | Plain action joins fan out duplicate provider-emitted tool IDs; one logical use must remain one row. | +| `polylogue-yeq.3` | open | Cross-surface query laws require identity, completeness, partitions, page concatenation, duplicate/missing/late shapes, and mutation sensitivity. | +| `polylogue-z9gh.2` | open | Selective action/delegation relations must avoid archive-wide materialization while preserving Nth-use/Nth-result and missing-result semantics. | +| `polylogue-fnm` | open | One Lark grammar owns query semantics; no parallel Test Diet query language or verb was added. | +| `polylogue-z9gh.9.1` | open | Full bounded shared transactions and lossless continuation across every read surface remain broader work. | +| `polylogue-70qb` | closed | Acceptance says bare explicit Boolean list must agree; close evidence only repaired count calls. Current source retained the bare-list omission, now fixed here. | + +Later/current Bead notes and source were treated as stronger than the stale Test Diet prose wherever they conflicted. + +## Historical findings + +| Commit | Finding relevant to this patch | +| --- | --- | +| `5d5edaf496d70b9372c7c2123c2e70a6ed4d34e6` | Fixed Boolean forwarding for count paths only, leaving the list-path gap. | +| `7b5a5aa0589772ed9d7eb9632af1591cf22cdd33` | Established transcript-rank action pairing. | +| `9f0e77116280571c14430b47986e7bd5b0e8be1a` | Closed the duplicate action fanout issue after the rank-pair repair. | +| `89166362b9aee8c304b27a69f68ec1b74606f634` | Landed the production query evaluator and public query route. | +| `478d6a77cf1cea1d63c180701d49099ad2cfacc3` | Added exact-session selective temporal bounding. | +| `c6495ea29c92d54dae00b302cb17336d80b5162d` | Established the shared receipt-backed query canary. | +| `c20286459cf2c3d1e4c968a8584f13e7cd382ff2` | Added workload profile/seeded artifact infrastructure used by the fixture. | + +## Dominated candidates for later certification + +No deletion is included. The following are candidates only after independent local route/oracle/sensitivity certification proves diagnostic and compatibility dominance: + +- mock-forwarding permutations in `tests/unit/cli/test_query_exec_laws.py` that only restate collaborator keyword plumbing; +- repetitive single-field lowerer examples in `TestLowererFieldMapping`, potentially consolidated into a reviewed decision table while retaining unique diagnostics; +- source-spelling and call-path tests in `TestSharedCardinalityPath` once behavior and removal mutations dominate them; +- shadow query maps/translators in `tests/infra/query_cases.py` and `tests/infra/surfaces.py` after all their represented fields and surfaces migrate to production-expression laws; +- overlapping parts of `tests/infra/semantic_facts.py` only after a broader independent manifest covers the same obligations. + +Required certification before any deletion: run the proposed survivor and the candidate locally; remove or mutate the production dependency; prove the survivor fails for the historical defect; confirm the candidate adds no unique parser diagnostic, security, compatibility, payload, or branch evidence. Until that proof exists, retain the tests and helpers. + +## Contradictions and unresolved boundaries + +1. The snapshot manifest says dirty, but the captured tree equals the named commit. +2. The stale query dossier reports no realized artifact and an older head, while current source contains C-03 workload/receipt support. +3. Closed Bead `polylogue-70qb` names bare-list parity, but its implemented/recorded fix only forwarded Boolean predicates to count paths. +4. The local host's default `pytest-randomly` plugin fails before test setup; managed runs explicitly disable it. +5. Full quick verification did not complete at full-repository mypy; only completed checks are reported as passes. +6. Daemon/HTTP/MCP/browser and live deployment are not represented by this package and remain explicitly unverified. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/HANDOFF.md new file mode 100644 index 000000000..361c89d16 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/HANDOFF.md @@ -0,0 +1,183 @@ +# Test Diet 01 handoff — query algebra and cardinality survivor + +## Result + +This package implements the strongest coherent local production-route survivor supported by the supplied snapshot. It adds an independent planted-fact oracle, four real-route laws, and one production correction discovered by those laws. + +The correction is in the root CLI's non-search session-list branch. The branch compiled explicit Boolean expressions into `filter_kwargs["boolean_predicate"]`, but then manually forwarded every other filter to `ArchiveStore.list_summaries` and omitted that predicate. Consequently, a bare expression such as `sessions where title:td01-delete-cohort AND origin:codex-session` rendered an unfiltered page even though facade list/count and terminal actions used the intended selection. The patch now sends the already-typed filter map to `list_summaries`, preserving all existing filters and forwarding the Boolean predicate only when it exists. + +The survivor begins with the repository's realized C-03 provider-native acquire/parse/index canary, clones its receipt-backed archive, and appends a separate known-fact manifest through the production archive writer. Expected membership, action rows, counts, partitions, pages, and destructive targets are computed only from that manifest; no production query path supplies its own expected answer. + +## Snapshot identity and authority + +| Field | Observed identity | +| --- | --- | +| Snapshot source | `/realm/project/polylogue` | +| Snapshot generation | `2026-07-17T043202Z` | +| Manifest branch | `master` | +| Commit / patch base | `f654480cadb7cc4c194704e24dfd483199547b35` | +| Reconstructed checkout | detached at the same commit | +| Branch delta | empty against `origin/master`; merge base is the same commit | +| Manifest dirty flag | `true` | +| Captured tree comparison | captured working-tree overlay produced no Git diff against the named commit | + +The commit plus captured working tree is therefore the patch authority. The manifest's dirty flag conflicts with the content comparison; that contradiction is retained as evidence rather than silently normalized. + +## Evidence inspected before implementation + +Repository instructions and contracts inspected: `AGENTS.md` (symlink to `CLAUDE.md`), `CLAUDE.md`, `TESTING.md`, `CONTRIBUTING.md`, and `pyproject.toml`. These establish substrate-first semantics, the single Lark query grammar, strict typing, and focused managed verification. + +Production paths followed through their dependencies: + +- `polylogue/archive/query/expression.py`, `spec.py`, `plan.py`, `archive_execution.py`, and `unit_results.py`; +- `polylogue/cli/archive_query.py`, `query.py`, `click_app.py`, `query_verbs.py`, and `verb_cardinality.py`; +- `polylogue/api/` facade entry points and public query envelopes; +- `polylogue/storage/sqlite/archive_tiers/archive.py` and `polylogue/storage/sqlite/action_relation.py`; +- source parser models, archive writing, canonical identities, and result payloads needed to plant facts through production types. + +Current tests and helpers inspected: + +- `tests/unit/cli/test_query_expression.py`; +- `tests/unit/cli/test_query_exec_laws.py`; +- `tests/unit/cli/test_verb_cardinality.py`; +- `tests/unit/storage/test_archive_tiers_archive.py`; +- query security, parity, identity, miss-diagnostic, and runtime-filter tests; +- `tests/infra/query_cases.py`, `surfaces.py`, `semantic_facts.py`, and `workload_artifacts.py`. + +The supplied Test Diet packet was also read broadly, especially `areas/query-composition.md`, `dossiers/exact-query-selection.md`, `11-test-proof-form-audit.md`, `08-capability-map.md`, and `test-suite-composition-and-scale-2026-07-16.md`. Its query dossier names commit `21f78b4db2ba62ff44b5f16dfab96067bc249b4c` and reports zero realized artifacts, so current source at `f654480...` supersedes stale path/API assumptions while preserving the dossier's behavioral obligations. + +Relevant Beads records inspected: `polylogue-1xc.14.1`, `polylogue-b054.1.1.4`, `polylogue-xnkf`, `polylogue-yeq.3`, `polylogue-z9gh.2`, `polylogue-fnm`, `polylogue-z9gh.9.1`, and `polylogue-70qb`. + +Relevant history inspected: + +- `5d5edaf496d70b9372c7c2123c2e70a6ed4d34e6` — earlier Boolean-query repair; +- `7b5a5aa0589772ed9d7eb9632af1591cf22cdd33` — rank-pair actions by transcript order; +- `9f0e77116280571c14430b47986e7bd5b0e8be1a` — closure evidence for duplicate action fanout; +- `c6495ea29c92d54dae00b302cb17336d80b5162d` — shared receipt-backed query canary; +- `c20286459cf2c3d1e4c968a8584f13e7cd382ff2` — workload profiles and seeded real-pipeline artifacts; +- `89166362b9aee8c304b27a69f68ec1b74606f634` — production query evaluator; +- `478d6a77cf1cea1d63c180701d49099ad2cfacc3` — exact-session temporal bound. + +## Mechanism + +### Independent fact model + +`tests/infra/query_manifest_oracle.py` is standard-library-only and deliberately imports no Polylogue parser, query spec, SQL relation, repository reader, or public payload type. It declares sessions and actions, then derives expected values directly: + +- canonical session IDs and exact origin/title membership; +- expected action identities and nullable result fields; +- `is_error` partitions; +- deterministic offset pages. + +The fixture plants these adversarial shapes: + +- two `Bash` uses and two results sharing one `tool_id` in transcript order; +- a third `Bash` use with no result; +- the same repeated `tool_id` in another session; +- two Codex delete candidates plus a Claude title-shadow candidate; +- identical native IDs under Codex and Claude canonical origins; +- a retained control session. + +### Real route + +`tests/unit/cli/test_query_composition_laws.py` builds the existing C-03 artifact with `build_seeded_archive`, clones it with `clone_seeded_archive`, validates the receipt identity, and plants the independent facts with `ArchiveStore.write_parsed`. The laws then traverse: + +1. Lark parsing and query lowering; +2. canonical SQLite action/session relations and repository list/count execution; +3. the asynchronous `Polylogue` facade; +4. the root CLI JSON list route; +5. CLI delete preview and apply; +6. post-action facade reads. + +The CLI daemon fast path explicitly declines compiled Boolean predicates, so the repaired law exercises the authoritative local route. It does not claim daemon, HTTP, MCP, browser, or deployed-process coverage. + +### Cardinality and mutation sensitivity + +The canonical action relation ranks uses and results within `(session_id, tool_id)` by transcript order and joins equal ranks, preserving an unmatched use through a left join. The test's representative equality-join mutant creates a temporary plain relation and replaces the production relation selector only for the mutation witness. Two uses multiplied by two results produce four rows, plus the unmatched use for five; the production relation and independent oracle require three. + +A second reversible mutation was applied in an isolated worktree to remove `boolean_predicate` immediately before the repaired `list_summaries` call. The session law failed with the default 20-row unfiltered page instead of the two oracle members. The leaked rows included the planted Claude shadow, both canonical-origin variants of `td01-shared`, C-03 irrelevant sessions, and unrelated planted controls. This is the specific wrong result the survivor detects. + +## Changed files + +| File | Change | +| --- | --- | +| `polylogue/cli/archive_query.py` | Replace a duplicated manual filter forwarding list with the existing typed `filter_kwargs`, thereby forwarding compiled Boolean predicates and reducing future call-site drift. No public signature or query-language change. | +| `tests/infra/query_manifest_oracle.py` | Add the independent known-fact manifest and pure expected-result evaluator. | +| `tests/unit/cli/test_query_composition_laws.py` | Add four real-route laws covering membership/count/identity, action rows/count/partitions/pages, preview/apply, and duplicate-join mutation sensitivity. | + +No existing test or helper was deleted. `FILES/` is intentionally omitted because `PATCH.diff` completely and unambiguously carries all three changes. + +## Acceptance matrix + +| Mission obligation | Result | Evidence | +| --- | --- | --- | +| Start from current realized canary | PASS | Fixture builds and clones the current C-03 provider-native receipt-backed artifact. | +| Independent expected fact set | PASS | Standard-library manifest computes answers without production query imports. | +| Parsing and lowering | PASS | Session AST/compiled predicate and action terminal-source parsing are asserted. | +| SQL/repository execution | PASS | `query_actions`, `query_unit_counts`, session list/count, and canonical relation execute against SQLite. | +| Stable public read routes | PASS | Python facade and root CLI JSON list agree with the oracle. | +| Membership/count agreement | PASS | Exact canonical IDs and totals agree across facade, repository, and CLI. | +| Partition agreement | PASS | `group by is_error | count` equals the oracle partition and sums to total. | +| Pagination agreement | PASS | Two facade pages concatenate exactly to the stable unpaged order and oracle slices. | +| Preview/apply agreement | PASS | CLI dry-run IDs equal the oracle; apply count matches; post-state proves only those IDs disappeared. | +| Duplicate/missing-result cardinality | PASS | Repeated IDs rank-pair to two rows, unmatched use remains a third nullable row, cross-session decoy is excluded. | +| Representative anti-vacuity mutation | PASS | Dropped-predicate production mutant fails; plain equality join produces five instead of three. | +| Preserve exact identity | PASS | Same native ID under two origins resolves only the fully canonical Codex ID. | +| Preserve existing bounded-work canary | PASS | Complete owning test file run includes both current C-03 canaries. | +| Preserve parser/security/compatibility obligations | PASS for inspected cluster | Focused parser/CLI/storage cluster plus security, parity, identity, diagnostics, and runtime filters are green. | +| Full repository suite | UNVERIFIED | Not blanket-run; repository instructions explicitly prefer focused ownership selections. | +| Locked Nix/uv environment | UNVERIFIED | External package resolution was unavailable; verification used host Python plus declared-version dependencies. | +| Live daemon/HTTP/MCP/browser/deployment | UNVERIFIED | No operator process, secrets, or deployment was available or claimed. | +| Real testmon affected-selection proof | UNVERIFIED | Direct production mutation was proved, but the separate `polylogue-b054.1.1.4` testmon-gate obligation remains open. | + +## Apply order + +Apply the patch atomically against the exact snapshot commit: + +```bash +git checkout f654480cadb7cc4c194704e24dfd483199547b35 +git apply --check PATCH.diff +git apply PATCH.diff +``` + +The patch has no migration, generated-file, or data-order dependency. The production forwarding repair and its survivor tests should land together. + +Recommended focused verification after application: + +```bash +python -m ruff format --check \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +python -m ruff check \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +python -m mypy --strict \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +python -m devtools test tests/unit/cli/test_query_composition_laws.py -p no:randomly +``` + +Then run the owning files and query-contract files listed in `TESTS.md` through the project's managed environment. + +## Patch reproducibility + +`PATCH.diff` was generated from the three named paths only, checked with `git apply --check`, and applied to a fresh detached worktree at the exact snapshot commit. The resulting files byte-match the implementation worktree. Fresh-worktree `git diff --check`, Ruff format/check, `compileall`, strict changed-file mypy, and the four managed survivor laws all pass. + +## Risks and limitations + +The production change is intentionally narrow, but it centralizes an existing call onto `filter_kwargs`. Existing owning tests cover the old forwarding behavior; nevertheless, a release environment should rerun the full affected/testmon selection under the locked toolchain. + +The new real-route laws cover SQLite, the Python facade, and local CLI read/action behavior. They do not make claims about HTTP or MCP transaction semantics, snapshot isolation across concurrent pages, cursor replay, cancellation, FTS/vector/lineage scale branches, or live archive resource ceilings. Those are separate open Bead obligations and would be unsafe to simulate as part of this local survivor. + +Raw focused pytest required `-p no:randomly` because the available host's `pytest-randomly` setup raises `ValueError: Seed must be between 0 and 2**32 - 1` before the test body. The managed command with that explicit disablement passed. The workload helper emits Python 3.13 `fork()` deprecation warnings; no semantic failure occurred. + +A full `devtools verify --quick` did not complete in this environment. Ruff stages completed after temporary command wrappers were supplied; the full-repository mypy stage was externally stopped after 240 seconds. Changed-file strict mypy completed successfully. This package does not convert that incomplete run into a pass claim. + +## Value of another iteration + +A small repair iteration would add modest confidence: rerun this patch under the exact locked Nix/uv environment, resolve the local `pytest-randomly` bootstrap problem, execute the true testmon affected gate with the reversible predicate mutation, and optionally add explicit sequence-predicate parity to the same oracle. That is verification hardening, not a redesign. + +A substantial second pass would be materially different work: extend the shared bounded query transaction across daemon HTTP and future MCP-native surfaces; prove snapshot-stable cursor/page concatenation under concurrent writes; add FTS/vector/lineage and cancellation laws; and attach selective-plan/resource receipts at p50/p95/max generated workload tiers. That work could add significant value, but it belongs with `polylogue-yeq.3`, `polylogue-z9gh.2`, and `polylogue-z9gh.9.1`, not as an ungrounded expansion of this patch. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/PATCH.diff new file mode 100644 index 000000000..8a6d4bee5 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/PATCH.diff @@ -0,0 +1,786 @@ +diff --git a/polylogue/cli/archive_query.py b/polylogue/cli/archive_query.py +index c41fcc8645a93e81d597f7d61d3f0d8df459af6f..27b84a74c765f417e635edfbd5a94d6211f67eb5 100644 +--- a/polylogue/cli/archive_query.py ++++ b/polylogue/cli/archive_query.py +@@ -729,38 +729,10 @@ def _execute_archive_query_stdout(env: AppEnv, request: RootModeRequest) -> None + summaries = archive.list_summaries( + limit=fetch_limit, + offset=page_offset, +- origin=origin, +- origins=origins, +- excluded_origins=excluded_origins, +- tags=tags, +- excluded_tags=excluded_tags, +- repo_names=repo_names, +- project_refs=project_refs, +- has_types=has_types, +- has_tool_use=has_tool_use, +- has_thinking=has_thinking, +- has_paste=has_paste, +- tool_terms=tool_terms, +- excluded_tool_terms=excluded_tool_terms, +- action_terms=action_terms, +- excluded_action_terms=excluded_action_terms, +- action_sequence=action_sequence, +- action_text_terms=action_text_terms, +- referenced_paths=referenced_paths, +- cwd_prefix=cwd_prefix, +- typed_only=typed_only, +- message_type=message_type, +- title=title_filter, +- min_messages=min_messages, +- max_messages=max_messages, +- min_words=min_words, +- max_words=max_words, +- since_ms=since_ms, +- until_ms=until_ms, +- since_session_id=since_session_id, + sample=sample_count is not None, + sort=sort, + reverse=reverse, ++ **filter_kwargs, + ) + page_summaries, next_cursor = _paginate_rows(summaries, limit=limit, offset=page_offset) + if stream: +diff --git a/tests/infra/query_manifest_oracle.py b/tests/infra/query_manifest_oracle.py +new file mode 100644 +index 0000000000000000000000000000000000000000..423ecfd817d28084eab9f36cf347f2130c1d417e +--- /dev/null ++++ b/tests/infra/query_manifest_oracle.py +@@ -0,0 +1,244 @@ ++"""Independent planted-fact oracle for real query composition laws. ++ ++The oracle deliberately knows nothing about Polylogue's parser, query specs, ++SQL relations, or public payload models. It computes expected membership and ++cardinality directly from the facts planted by the test fixture. ++""" ++ ++from __future__ import annotations ++ ++from dataclasses import dataclass ++ ++ ++@dataclass(frozen=True, slots=True) ++class QueryActionFact: ++ """One planted tool-use fact and its optional paired result.""" ++ ++ message_native_id: str ++ occurred_at_ms: int ++ tool_name: str ++ tool_id: str ++ command: str | None = None ++ path: str | None = None ++ output_text: str | None = None ++ is_error: bool | None = None ++ exit_code: int | None = None ++ ++ ++@dataclass(frozen=True, slots=True) ++class QuerySessionFact: ++ """One independently specified session planted into the archive.""" ++ ++ provider: str ++ origin: str ++ native_id: str ++ title: str ++ actions: tuple[QueryActionFact, ...] = () ++ ++ @property ++ def session_id(self) -> str: ++ return f"{self.origin}:{self.native_id}" ++ ++ ++@dataclass(frozen=True, slots=True) ++class ExpectedActionRow: ++ """Expected action row, including its hidden join key, from planted facts.""" ++ ++ session_id: str ++ message_id: str ++ tool_name: str ++ tool_id: str ++ output_text: str | None ++ is_error: bool | None ++ exit_code: int | None ++ ++ ++@dataclass(frozen=True, slots=True) ++class QueryManifest: ++ """Pure expected-result model for query membership and cardinality.""" ++ ++ sessions: tuple[QuerySessionFact, ...] ++ ++ def session_ids( ++ self, ++ *, ++ origin: str | None = None, ++ title_contains: str | None = None, ++ exact_session_id: str | None = None, ++ ) -> tuple[str, ...]: ++ selected = [] ++ for session in self.sessions: ++ if origin is not None and session.origin != origin: ++ continue ++ if title_contains is not None and title_contains.lower() not in session.title.lower(): ++ continue ++ if exact_session_id is not None and session.session_id != exact_session_id: ++ continue ++ selected.append(session.session_id) ++ return tuple(sorted(selected)) ++ ++ def action_rows( ++ self, ++ *, ++ session_id: str, ++ tool_name: str | None = None, ++ ) -> tuple[ExpectedActionRow, ...]: ++ rows = [] ++ for session in self.sessions: ++ if session.session_id != session_id: ++ continue ++ for action in session.actions: ++ if tool_name is not None and action.tool_name.casefold() != tool_name.casefold(): ++ continue ++ rows.append( ++ ExpectedActionRow( ++ session_id=session.session_id, ++ message_id=f"{session.session_id}:{action.message_native_id}", ++ tool_name=action.tool_name, ++ tool_id=action.tool_id, ++ output_text=action.output_text, ++ is_error=action.is_error, ++ exit_code=action.exit_code, ++ ) ++ ) ++ return tuple(rows) ++ ++ def action_partition( ++ self, ++ *, ++ session_id: str, ++ tool_name: str, ++ field: str, ++ ) -> tuple[tuple[str, int], ...]: ++ if field != "is_error": ++ raise ValueError(f"unsupported oracle partition field: {field}") ++ counts: dict[str, int] = {} ++ for row in self.action_rows(session_id=session_id, tool_name=tool_name): ++ key = "unknown" if row.is_error is None else str(int(row.is_error)) ++ counts[key] = counts.get(key, 0) + 1 ++ return tuple(sorted(counts.items())) ++ ++ def page_actions( ++ self, ++ *, ++ session_id: str, ++ tool_name: str, ++ limit: int, ++ offset: int, ++ ) -> tuple[ExpectedActionRow, ...]: ++ rows = self.action_rows(session_id=session_id, tool_name=tool_name) ++ return rows[offset : offset + limit] ++ ++ ++QUERY_CARDINALITY_MANIFEST = QueryManifest( ++ sessions=( ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-action", ++ title="td01 action duplicate pairing", ++ actions=( ++ QueryActionFact( ++ message_native_id="m-action-01", ++ occurred_at_ms=1_767_225_601_000, ++ tool_name="Bash", ++ tool_id="td01-repeat", ++ command="printf first", ++ output_text="first ok", ++ is_error=False, ++ exit_code=0, ++ ), ++ QueryActionFact( ++ message_native_id="m-action-02", ++ occurred_at_ms=1_767_225_602_000, ++ tool_name="Bash", ++ tool_id="td01-repeat", ++ command="printf second", ++ output_text="second failed", ++ is_error=True, ++ exit_code=7, ++ ), ++ QueryActionFact( ++ message_native_id="m-action-03", ++ occurred_at_ms=1_767_225_603_000, ++ tool_name="Bash", ++ tool_id="td01-missing", ++ command="printf missing", ++ ), ++ QueryActionFact( ++ message_native_id="m-action-04", ++ occurred_at_ms=1_767_225_604_000, ++ tool_name="Read", ++ tool_id="td01-read", ++ path="polylogue/archive/query/expression.py", ++ output_text="read ok", ++ is_error=False, ++ exit_code=0, ++ ), ++ ), ++ ), ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-action-decoy", ++ title="td01 action predicate decoy", ++ actions=( ++ QueryActionFact( ++ message_native_id="m-decoy", ++ occurred_at_ms=1_767_225_605_000, ++ tool_name="Bash", ++ tool_id="td01-repeat", ++ command="printf decoy", ++ output_text="decoy", ++ is_error=False, ++ exit_code=0, ++ ), ++ ), ++ ), ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-delete-a", ++ title="td01-delete-cohort alpha", ++ ), ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-delete-b", ++ title="td01-delete-cohort beta", ++ ), ++ QuerySessionFact( ++ provider="claude-code", ++ origin="claude-code-session", ++ native_id="td01-delete-shadow", ++ title="td01-delete-cohort provider shadow", ++ ), ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-shared", ++ title="td01 canonical identity codex", ++ ), ++ QuerySessionFact( ++ provider="claude-code", ++ origin="claude-code-session", ++ native_id="td01-shared", ++ title="td01 canonical identity claude", ++ ), ++ QuerySessionFact( ++ provider="codex", ++ origin="codex-session", ++ native_id="td01-keep", ++ title="td01 retained control", ++ ), ++ ) ++) ++ ++ ++__all__ = [ ++ "ExpectedActionRow", ++ "QUERY_CARDINALITY_MANIFEST", ++ "QueryActionFact", ++ "QueryManifest", ++ "QuerySessionFact", ++] +diff --git a/tests/unit/cli/test_query_composition_laws.py b/tests/unit/cli/test_query_composition_laws.py +new file mode 100644 +index 0000000000000000000000000000000000000000..0efbc70cba07badb075b2e8ad8c7b3fa0067a426 +--- /dev/null ++++ b/tests/unit/cli/test_query_composition_laws.py +@@ -0,0 +1,486 @@ ++"""Real-route survivor laws for query algebra and relational cardinality.""" ++ ++from __future__ import annotations ++ ++import json ++from collections.abc import Mapping ++from dataclasses import dataclass ++from pathlib import Path ++from typing import Any ++ ++import pytest ++from click.testing import CliRunner ++ ++from polylogue.api import Polylogue ++from polylogue.archive.message.roles import Role ++from polylogue.archive.query.expression import ( ++ compile_expression, ++ parse_expression_ast, ++ parse_unit_source_expression, ++) ++from polylogue.core.enums import BlockType, Provider ++from polylogue.sources.parsers.base_models import ParsedContentBlock, ParsedMessage, ParsedSession ++from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore ++from polylogue.surfaces.payloads import QueryUnitAggregateEnvelope, QueryUnitEnvelope ++from tests.infra.query_manifest_oracle import ( ++ QUERY_CARDINALITY_MANIFEST, ++ ExpectedActionRow, ++ QueryActionFact, ++ QuerySessionFact, ++) ++from tests.infra.workload_artifacts import ( ++ SeededArchiveArtifact, ++ build_seeded_archive, ++ clone_seeded_archive, ++) ++ ++_SESSION_COHORT_MARKER = "td01-delete-cohort" ++_SESSION_COHORT_EXPRESSION = f"sessions where title:{_SESSION_COHORT_MARKER} AND origin:codex-session" ++_EXACT_ID_EXPRESSION = "sessions where session:codex-session:td01-shared" ++_ACTION_SESSION_ID = "codex-session:td01-action" ++_ACTION_ROWS_EXPRESSION = f"actions where session.id:{_ACTION_SESSION_ID} AND tool:bash | sort by time asc" ++_ACTION_COUNT_EXPRESSION = f"actions where session.id:{_ACTION_SESSION_ID} AND tool:bash | count" ++_ACTION_PARTITION_EXPRESSION = ( ++ f"actions where session.id:{_ACTION_SESSION_ID} AND tool:bash | group by is_error | count | sort by key asc" ++) ++ ++ ++@dataclass(frozen=True, slots=True) ++class QueryLawArchive: ++ root: Path ++ seeded_manifest_id: str ++ real_pipeline_target_id: str ++ ++ ++def _tool_input(action: QueryActionFact) -> dict[str, object]: ++ if action.command is not None: ++ return {"command": action.command} ++ if action.path is not None: ++ return {"file_path": action.path} ++ return {} ++ ++ ++def _parsed_session(fact: QuerySessionFact) -> ParsedSession: ++ messages: list[ParsedMessage] = [] ++ for position, action in enumerate(fact.actions): ++ blocks = [ ++ ParsedContentBlock( ++ type=BlockType.TOOL_USE, ++ tool_name=action.tool_name, ++ tool_id=action.tool_id, ++ tool_input=_tool_input(action), ++ ) ++ ] ++ if action.output_text is not None: ++ blocks.append( ++ ParsedContentBlock( ++ type=BlockType.TOOL_RESULT, ++ tool_id=action.tool_id, ++ text=action.output_text, ++ is_error=action.is_error, ++ exit_code=action.exit_code, ++ ) ++ ) ++ messages.append( ++ ParsedMessage( ++ provider_message_id=action.message_native_id, ++ role=Role.ASSISTANT, ++ text=f"planted query-law action {position}", ++ occurred_at_ms=action.occurred_at_ms, ++ position=position, ++ blocks=blocks, ++ ) ++ ) ++ if not messages: ++ messages.append( ++ ParsedMessage( ++ provider_message_id="m-session", ++ role=Role.USER, ++ text=fact.title, ++ occurred_at_ms=1_767_225_600_000, ++ position=0, ++ blocks=[ParsedContentBlock(type=BlockType.TEXT, text=fact.title)], ++ ) ++ ) ++ return ParsedSession( ++ source_name=Provider.from_string(fact.provider), ++ provider_session_id=fact.native_id, ++ title=fact.title, ++ created_at="2026-01-01T00:00:00+00:00", ++ updated_at="2026-01-01T00:00:00+00:00", ++ messages=messages, ++ ) ++ ++ ++@pytest.fixture(scope="module") ++def query_law_seed(tmp_path_factory: pytest.TempPathFactory) -> SeededArchiveArtifact: ++ """Build the existing C-03 provider-native acquire/parse/index artifact once.""" ++ return build_seeded_archive(cache_root=tmp_path_factory.mktemp("td01-seed") / "cache") ++ ++ ++@pytest.fixture ++def query_law_archive( ++ workspace_env: dict[str, Path], ++ query_law_seed: SeededArchiveArtifact, ++) -> QueryLawArchive: ++ """Clone the real-pipeline canary and append the independent oracle facts.""" ++ clone = clone_seeded_archive(query_law_seed, workspace_env["archive_root"]) ++ with ArchiveStore.open_existing(clone.root, read_only=False) as archive: ++ planted_ids = tuple( ++ archive.write_parsed(_parsed_session(session)) for session in QUERY_CARDINALITY_MANIFEST.sessions ++ ) ++ assert planted_ids == tuple(session.session_id for session in QUERY_CARDINALITY_MANIFEST.sessions) ++ target = next( ++ fact.expected_session_id ++ for fact in query_law_seed.facts ++ if fact.expected_session_id == "codex-session:c03-target" ++ ) ++ assert target is not None ++ return QueryLawArchive( ++ root=clone.root, ++ seeded_manifest_id=clone.source_manifest_id, ++ real_pipeline_target_id=target, ++ ) ++ ++ ++def _normalized_error(value: object) -> bool | None: ++ return None if value is None else bool(value) ++ ++ ++def _expected_action_signature(row: ExpectedActionRow) -> tuple[object, ...]: ++ return ( ++ row.session_id, ++ row.message_id, ++ row.tool_name, ++ row.output_text, ++ row.is_error, ++ row.exit_code, ++ ) ++ ++ ++def _action_signature(row: Any) -> tuple[object, ...]: ++ return ( ++ str(row.session_id), ++ str(row.message_id), ++ str(row.tool_name), ++ row.output_text, ++ _normalized_error(row.is_error), ++ row.exit_code, ++ ) ++ ++ ++def _json_action_signature(row: Mapping[str, object]) -> tuple[object, ...]: ++ return ( ++ str(row["session_id"]), ++ str(row["message_id"]), ++ str(row["tool_name"]), ++ row.get("output_text"), ++ _normalized_error(row.get("is_error")), ++ row.get("exit_code"), ++ ) ++ ++ ++@pytest.mark.query_routing ++@pytest.mark.asyncio ++async def test_session_membership_count_and_canonical_identity_agree_across_public_routes( ++ query_law_archive: QueryLawArchive, ++) -> None: ++ """Parser/lowerer, facade list/count, and CLI must select the oracle's exact IDs. ++ ++ Production dependencies: ``parse_expression_ast``/``compile_expression``, ++ ``SessionQuerySpec.list``/``count`` through ``Polylogue``, and the root CLI ++ list route. Dropping either title or origin selection admits a planted ++ provider shadow; suffix-only identity admits both ``td01-shared`` sessions. ++ """ ++ assert query_law_archive.seeded_manifest_id.startswith("seeded-manifest:sha256:") ++ expected = QUERY_CARDINALITY_MANIFEST.session_ids( ++ origin="codex-session", ++ title_contains=_SESSION_COHORT_MARKER, ++ ) ++ ast = parse_expression_ast(_SESSION_COHORT_EXPRESSION) ++ spec = compile_expression(_SESSION_COHORT_EXPRESSION) ++ assert ast.boolean_predicate is not None ++ assert spec.boolean_predicate == ast.boolean_predicate ++ assert spec.has_filters() ++ ++ facade = Polylogue( ++ archive_root=query_law_archive.root, ++ db_path=query_law_archive.root / "index.db", ++ ) ++ try: ++ sessions = await facade.list_sessions_for_spec(spec) ++ count = await spec.count(facade.config) ++ exact_spec = compile_expression(_EXACT_ID_EXPRESSION) ++ exact_sessions = await facade.list_sessions_for_spec(exact_spec) ++ assert await facade.get_session_summary(query_law_archive.real_pipeline_target_id) is not None ++ finally: ++ await facade.close() ++ ++ actual_ids = tuple(sorted(str(session.id) for session in sessions)) ++ assert actual_ids == expected ++ assert count == len(expected) ++ assert tuple(str(session.id) for session in exact_sessions) == ("codex-session:td01-shared",) ++ ++ from polylogue.cli import cli ++ ++ result = CliRunner().invoke( ++ cli, ++ ["--plain", "--format", "json", "find", _SESSION_COHORT_EXPRESSION], ++ ) ++ assert result.exit_code == 0, result.output ++ payload = json.loads(result.output) ++ cli_ids = tuple(sorted(str(item["id"]) for item in payload["items"])) ++ assert cli_ids == expected ++ assert payload["total"] == len(expected) ++ ++ ++@pytest.mark.query_routing ++@pytest.mark.asyncio ++async def test_action_rows_count_partition_and_pages_agree_across_repository_facade_and_cli( ++ query_law_archive: QueryLawArchive, ++) -> None: ++ """One explicit action selection must preserve identities and cardinality. ++ ++ Production dependencies: terminal-unit parsing, the canonical SQLite ++ action relation, repository row/count executors, ``Polylogue.query_units``, ++ and the terminal-unit CLI renderer. Moving selection global-first, dropping ++ the exact session predicate, or replacing rank pairing with equality join ++ changes at least one asserted row, count, partition, or page boundary. ++ """ ++ expected_rows = QUERY_CARDINALITY_MANIFEST.action_rows( ++ session_id=_ACTION_SESSION_ID, ++ tool_name="bash", ++ ) ++ expected_signatures = tuple(_expected_action_signature(row) for row in expected_rows) ++ source = parse_unit_source_expression(_ACTION_ROWS_EXPRESSION) ++ assert source is not None ++ assert source.unit == "action" ++ assert source.pipeline.sort is not None ++ assert source.pipeline.sort.field == "time" ++ ++ with ArchiveStore.open_existing(query_law_archive.root) as archive: ++ repository_rows = archive.query_actions( ++ source.predicate, ++ limit=10, ++ sort="time", ++ sort_direction="asc", ++ ) ++ repository_count = archive.query_unit_counts( ++ "action", ++ source.predicate, ++ ) ++ assert tuple(_action_signature(row) for row in repository_rows) == expected_signatures ++ assert [(row.group_key, row.count) for row in repository_count] == [("all", len(expected_rows))] ++ ++ facade = Polylogue( ++ archive_root=query_law_archive.root, ++ db_path=query_law_archive.root / "index.db", ++ ) ++ try: ++ first_page = await facade.query_units(_ACTION_ROWS_EXPRESSION, limit=2, offset=0) ++ assert isinstance(first_page, QueryUnitEnvelope) ++ assert first_page.next_offset == 2 ++ second_page = await facade.query_units( ++ _ACTION_ROWS_EXPRESSION, ++ limit=2, ++ offset=first_page.next_offset, ++ ) ++ whole = await facade.query_units(_ACTION_ROWS_EXPRESSION, limit=10, offset=0) ++ count_envelope = await facade.query_units(_ACTION_COUNT_EXPRESSION, limit=10) ++ partition_envelope = await facade.query_units(_ACTION_PARTITION_EXPRESSION, limit=10) ++ finally: ++ await facade.close() ++ ++ assert isinstance(second_page, QueryUnitEnvelope) ++ assert isinstance(whole, QueryUnitEnvelope) ++ assert second_page.next_offset is None ++ page_signatures = tuple(_action_signature(row) for row in (*first_page.items, *second_page.items)) ++ whole_signatures = tuple(_action_signature(row) for row in whole.items) ++ assert page_signatures == whole_signatures == expected_signatures ++ assert tuple(_action_signature(row) for row in first_page.items) == tuple( ++ _expected_action_signature(row) ++ for row in QUERY_CARDINALITY_MANIFEST.page_actions( ++ session_id=_ACTION_SESSION_ID, ++ tool_name="bash", ++ limit=2, ++ offset=0, ++ ) ++ ) ++ assert tuple(_action_signature(row) for row in second_page.items) == tuple( ++ _expected_action_signature(row) ++ for row in QUERY_CARDINALITY_MANIFEST.page_actions( ++ session_id=_ACTION_SESSION_ID, ++ tool_name="bash", ++ limit=2, ++ offset=2, ++ ) ++ ) ++ ++ assert isinstance(count_envelope, QueryUnitAggregateEnvelope) ++ assert [(row.group_key, row.count) for row in count_envelope.items] == [("all", len(expected_rows))] ++ assert isinstance(partition_envelope, QueryUnitAggregateEnvelope) ++ assert tuple((str(row.group_key), row.count) for row in partition_envelope.items) == ( ++ QUERY_CARDINALITY_MANIFEST.action_partition( ++ session_id=_ACTION_SESSION_ID, ++ tool_name="bash", ++ field="is_error", ++ ) ++ ) ++ assert sum(row.count for row in partition_envelope.items) == len(expected_rows) ++ ++ from polylogue.cli import cli ++ ++ result = CliRunner().invoke( ++ cli, ++ ["--plain", "--format", "json", "find", _ACTION_ROWS_EXPRESSION], ++ ) ++ assert result.exit_code == 0, result.output ++ payload = json.loads(result.output) ++ assert tuple(_json_action_signature(item) for item in payload["items"]) == expected_signatures ++ assert payload["total"] == len(expected_rows) ++ ++ ++@pytest.mark.query_routing ++@pytest.mark.asyncio ++async def test_delete_preview_and_apply_remove_the_same_oracle_membership( ++ query_law_archive: QueryLawArchive, ++) -> None: ++ """The stable CLI action route must preview and apply one complete ID set. ++ ++ Production dependencies: expression parsing/lowering, the mutation ++ cardinality resolver, dry-run payload, and ``ArchiveStore.delete_sessions``. ++ A page-limited preview or an origin predicate dropped before apply leaves a ++ mismatch that this pre/post membership proof detects. ++ """ ++ expected = QUERY_CARDINALITY_MANIFEST.session_ids( ++ origin="codex-session", ++ title_contains=_SESSION_COHORT_MARKER, ++ ) ++ ++ from polylogue.cli import cli ++ ++ runner = CliRunner() ++ preview = runner.invoke( ++ cli, ++ [ ++ "--plain", ++ "find", ++ _SESSION_COHORT_EXPRESSION, ++ "then", ++ "delete", ++ "--dry-run", ++ "--all", ++ "--json", ++ ], ++ ) ++ assert preview.exit_code == 0, preview.output ++ preview_payload = json.loads(preview.output) ++ preview_ids = tuple(sorted(str(session_id) for session_id in preview_payload["session_ids"])) ++ assert preview_payload["status"] == "preview" ++ assert preview_payload["session_count"] == len(expected) ++ assert preview_payload["affected_count"] == 0 ++ assert preview_ids == expected ++ assert len(preview_ids) == len(set(preview_ids)) ++ ++ applied = runner.invoke( ++ cli, ++ [ ++ "--plain", ++ "find", ++ _SESSION_COHORT_EXPRESSION, ++ "then", ++ "delete", ++ "--yes", ++ "--all", ++ "--json", ++ ], ++ ) ++ assert applied.exit_code == 0, applied.output ++ applied_payload = json.loads(applied.output) ++ assert applied_payload == { ++ "status": "deleted", ++ "operation": "delete", ++ "session_count": len(expected), ++ "affected_count": len(expected), ++ } ++ ++ spec = compile_expression(_SESSION_COHORT_EXPRESSION) ++ facade = Polylogue( ++ archive_root=query_law_archive.root, ++ db_path=query_law_archive.root / "index.db", ++ ) ++ try: ++ assert await spec.count(facade.config) == 0 ++ assert await facade.get_session_summary(query_law_archive.real_pipeline_target_id) is not None ++ for fact in QUERY_CARDINALITY_MANIFEST.sessions: ++ summary = await facade.get_session_summary(fact.session_id) ++ assert (summary is None) == (fact.session_id in expected) ++ finally: ++ await facade.close() ++ ++ ++@pytest.mark.query_routing ++def test_rank_pairing_survivor_rejects_plain_equality_join_mutation( ++ query_law_archive: QueryLawArchive, ++ monkeypatch: pytest.MonkeyPatch, ++) -> None: ++ """A representative equality-join mutant yields five rows instead of three. ++ ++ Two ``td01-repeat`` uses and two results become a 2x2 cross-product (four ++ rows), while the unmatched ``td01-missing`` use contributes one more. The ++ production rank-paired relation yields exactly three Bash actions. ++ """ ++ import polylogue.storage.sqlite.archive_tiers.archive as archive_module ++ ++ source = parse_unit_source_expression(_ACTION_ROWS_EXPRESSION) ++ assert source is not None ++ expected_rows = QUERY_CARDINALITY_MANIFEST.action_rows( ++ session_id=_ACTION_SESSION_ID, ++ tool_name="bash", ++ ) ++ expected_count = len(expected_rows) ++ assert tuple(row.tool_id for row in expected_rows) == ( ++ "td01-repeat", ++ "td01-repeat", ++ "td01-missing", ++ ) ++ ++ with ArchiveStore.open_existing(query_law_archive.root, read_only=False) as archive: ++ baseline = archive.query_unit_counts("action", source.predicate) ++ assert [(row.group_key, row.count) for row in baseline] == [("all", expected_count)] ++ archive._conn.execute( ++ """ ++ CREATE TEMP VIEW td01_plain_actions AS ++ SELECT ++ u.session_id, ++ u.message_id, ++ u.block_id AS tool_use_block_id, ++ u.tool_name, ++ u.semantic_type, ++ u.tool_command, ++ u.tool_path, ++ u.tool_input, ++ r.text AS output_text, ++ r.tool_result_is_error AS is_error, ++ r.tool_result_exit_code AS exit_code, ++ r.block_id AS tool_result_block_id, ++ NULL AS followup_class, ++ NULL AS followup_message_ref ++ FROM blocks AS u ++ LEFT JOIN blocks AS r ++ ON r.session_id = u.session_id ++ AND r.tool_id = u.tool_id ++ AND r.block_type = 'tool_result' ++ WHERE u.block_type = 'tool_use' ++ """ ++ ) ++ monkeypatch.setattr( ++ archive_module, ++ "_action_relation_for_query", ++ lambda **_kwargs: ("", "td01_plain_actions", []), ++ ) ++ mutant = archive.query_unit_counts("action", source.predicate) ++ ++ mutant_count = mutant[0].count ++ assert mutant_count == 5 ++ assert mutant_count != expected_count diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/TESTS.md new file mode 100644 index 000000000..4ac91252f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/a01/extracted/TESTS.md @@ -0,0 +1,256 @@ +# Test design and execution evidence + +## Oracle design + +The expected-result authority is `tests/infra/query_manifest_oracle.py`. It is intentionally independent of all production query machinery. It imports only `dataclasses`, stores planted input facts, and computes membership/cardinality with ordinary Python iteration and slicing. + +The test fixture converts those facts into existing typed parser models solely to plant them through `ArchiveStore.write_parsed`. The production writer is not consulted for expected output. The archive also contains the existing C-03 provider-native workload, so a dropped predicate can leak realistic irrelevant rows rather than passing against a tiny closed universe. + +### Planted session controls + +| Canonical session | Purpose | +| --- | --- | +| `codex-session:td01-action` | Repeated action ID, paired results, unmatched result shape, and a non-Bash control. | +| `codex-session:td01-action-decoy` | Same repeated tool ID in another session; detects dropped exact-session selection. | +| `codex-session:td01-delete-a` | Intended destructive target. | +| `codex-session:td01-delete-b` | Second intended target; detects first-row/page-only actions. | +| `claude-code-session:td01-delete-shadow` | Same title marker under another origin; detects dropped origin predicate. | +| `codex-session:td01-shared` | Exact canonical-ID target. | +| `claude-code-session:td01-shared` | Same native suffix under another origin; detects suffix-only identity. | +| `codex-session:td01-keep` | Post-delete retained control. | + +### Planted action controls in `td01-action` + +| Message | Tool ID | Result | Expected role in law | +| --- | --- | --- | --- | +| `m-action-01` | `td01-repeat` | success, exit 0 | First repeated pair. | +| `m-action-02` | `td01-repeat` | error, exit 7 | Second repeated pair. | +| `m-action-03` | `td01-missing` | none | Left-preserved unmatched use with null result fields. | +| `m-action-04` | `td01-read` | success | Non-Bash exclusion control. | + +The expected Bash order is the fact/transcript order: first repeated pair, second repeated pair, unmatched use. The expected `is_error` partition is `0: 1`, `1: 1`, `unknown: 1`. + +## Survivor laws + +### `test_session_membership_count_and_canonical_identity_agree_across_public_routes` + +Production dependencies exercised: + +- `parse_expression_ast` and `compile_expression` in the Lark query substrate; +- compiled `SessionQuerySpec` list/count behavior through `Polylogue`; +- canonical identity lookup; +- root CLI bare-list dispatch, `ArchiveStore.list_summaries`, and JSON rendering. + +Assertions: + +- the explicit Boolean AST retains its predicate after lowering; +- facade list membership is exactly the two Codex cohort IDs; +- facade count equals two; +- exact `codex-session:td01-shared` does not admit the Claude session with the same native ID; +- root CLI items and total agree with the independent oracle; +- the receipt-backed C-03 target remains readable. + +Representative mutation/removal: remove `boolean_predicate` from the filter map immediately before the CLI list call. The test fails with a 20-row unfiltered page instead of the two intended IDs. This proves the law depends on the repaired production forwarding edge. + +### `test_action_rows_count_partition_and_pages_agree_across_repository_facade_and_cli` + +Production dependencies exercised: + +- terminal-unit expression parsing/lowering; +- `_action_relation_for_query` and canonical SQLite action relation; +- repository row and aggregate executors; +- `Polylogue.query_units` public envelopes; +- terminal-unit CLI JSON rendering. + +Assertions: + +- repository rows have exact session/message/tool/output/error/exit-code identities; +- repository count is three; +- two facade pages (`limit=2`) concatenate exactly to the unpaged stable order; +- each page equals the corresponding independent oracle slice; +- count and `group by is_error | count` partition the same three rows; +- CLI rows and total equal repository/facade/oracle results; +- the cross-session same-tool-ID decoy and non-Bash action do not leak. + +Representative mutations: move the exact-session selection outside global ranking, drop the session predicate, change sort/page order, or replace rank pairing with equality pairing. Each changes at least one asserted identity, total, partition, or page boundary. + +### `test_delete_preview_and_apply_remove_the_same_oracle_membership` + +Production dependencies exercised: + +- expression parsing/lowering; +- mutation cardinality resolution; +- CLI dry-run payload; +- `ArchiveStore.delete_sessions` apply path; +- post-action facade count and reads. + +Assertions: + +- preview exposes exactly two unique canonical IDs; +- preview reports zero affected rows and the complete intended target count; +- apply reports two sessions and two affected rows; +- the same expression counts zero afterward; +- every planted non-target remains, including the provider shadow and canonical-ID controls; +- the existing C-03 real-pipeline target remains. + +Representative mutations: page-limit the preview/apply selection, drop the origin predicate during either phase, or resolve suffix-only IDs. Preview IDs, apply count, and post-state then disagree. + +### `test_rank_pairing_survivor_rejects_plain_equality_join_mutation` + +Production dependency exercised: the canonical action relation selected by `polylogue.storage.sqlite.archive_tiers.archive._action_relation_for_query`. + +The mutation creates a temporary relation that left-joins tool uses to tool results only on `(session_id, tool_id)`. With two uses and two results sharing `td01-repeat`, that mutation emits a 2×2 product of four rows. The unmatched `td01-missing` use adds one, for a mutant total of five. The independent manifest and production rank-paired relation require exactly three. The test first proves the production baseline, then proves the explicit mutant's wrong cardinality. + +## Commands and observed results + +All semantic pytest runs below used `-p no:randomly` because the available host plugin failed during fixture setup with an invalid generated seed. The project files themselves were not changed to suppress that external plugin behavior. + +### Patch-surface static checks + +```bash +python -m ruff format --check \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +``` + +Result: `3 files already formatted`. + +```bash +python -m ruff check \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +``` + +Result: `All checks passed!`. + +```bash +python -m mypy --strict \ + polylogue/cli/archive_query.py \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +``` + +Result: `Success: no issues found in 3 source files`. + +`python -m compileall` over the same files also completed successfully. + +### Managed focused test + +```bash +python -m devtools test \ + tests/unit/cli/test_query_composition_laws.py \ + -p no:randomly +``` + +Result: `4 passed, 8 warnings in 6.22s`; managed command reported `ok (20.0s)`. Warnings were Python 3.13 multiprocessing `fork()` deprecations from the workload artifact builder. + +### Fresh detached apply check + +`PATCH.diff` was checked and applied to a new detached worktree at `f654480cadb7cc4c194704e24dfd483199547b35`. The three applied files byte-matched the implementation worktree. `git diff --check`, changed-file Ruff format/check, `compileall`, and strict mypy passed there. The managed focused command was then rerun with an explicit disk basetemp because the container exposes only 64 MiB of `/dev/shm`: + +```bash +POLYLOGUE_PYTEST_TMPFS=0 \ +POLYLOGUE_PYTEST_BASETEMP_ROOT=/mnt/data/work-testdiet-01/pytest-tmp \ +python -m devtools test \ + tests/unit/cli/test_query_composition_laws.py \ + -p no:randomly +``` + +Fresh-apply result: `4 passed, 8 warnings in 6.45s`; managed command reported `ok (19.3s)`. + +### New laws plus nearest canaries + +A focused selection covering the four new laws and nearest parser/cardinality/C-03 tests produced: + +```text +12 passed, 16 warnings in 9.54s +``` + +### Complete owning files + +```bash +python -m pytest -p no:randomly \ + tests/unit/cli/test_query_expression.py \ + tests/unit/cli/test_query_exec_laws.py \ + tests/unit/cli/test_query_composition_laws.py \ + tests/unit/cli/test_verb_cardinality.py \ + tests/unit/storage/test_archive_tiers_archive.py +``` + +Result: `583 passed, 1 skipped, 17 warnings in 25.01s`. + +This run includes the existing C-03 tests: + +- `test_exact_session_action_count_bounds_pairing_before_global_ranking`; +- `test_c03_exact_session_actions_uses_real_provider_pipeline_and_planted_facts`. + +The first broad attempt exposed a compatibility issue in the draft: always passing `boolean_predicate=None` broke a narrow fake-backed test. The final implementation reuses the conditional typed filter map instead, after which the entire owning-file selection passed. The failed draft is not part of `PATCH.diff`. + +### Query security, parity, identity, and diagnostics + +```bash +python -m pytest -p no:randomly \ + tests/unit/storage/test_query_security.py \ + tests/unit/storage/test_query_parity.py \ + tests/unit/core/test_query_identity.py \ + tests/unit/archive/test_query_miss_diagnostics.py \ + tests/unit/archive/test_query_runtime_filters.py +``` + +Result: `62 passed in 13.07s`. + +### Reversible production mutation + +In a fresh detached worktree with the final patch applied, this line was temporarily inserted before the repaired list call: + +```python +filter_kwargs.pop("boolean_predicate", None) +``` + +Running only the session membership law produced one required failure. The expected IDs were: + +```text +codex-session:td01-delete-a +codex-session:td01-delete-b +``` + +The actual default page contained 20 IDs, including: + +```text +claude-code-session:td01-delete-shadow +claude-code-session:td01-shared +codex-session:c03-irrelevant-002 +codex-session:td01-action +codex-session:td01-action-decoy +codex-session:td01-keep +codex-session:td01-shared +``` + +The mutation worktree was isolated from the delivery patch. Removing the mutation returns the law to green. + +### Default host-plugin attempt + +Running the mutation-witness node without `-p no:randomly` did not reach the test body: + +```text +ValueError: Seed must be between 0 and 2**32 - 1 +``` + +This is recorded as an environment limitation, not as a product-test failure. + +### Full quick verifier + +`devtools verify --quick` did not complete in the available environment. Initial attempts lacked external `ruff`/`mypy` executables. With temporary wrappers, Ruff format/check completed, but the full-repository mypy stage was externally stopped after 240 seconds. There is no full-gate pass claim. The changed-file strict mypy command above is the completed type-check result. + +## Remaining verification + +The following remain explicitly unverified: + +- exact `uv.lock`/Nix toolchain execution; +- full repository test suite and true testmon affected selection; +- daemon/HTTP/MCP/browser behavior and live process boundaries; +- concurrent-write snapshot stability, cursor replay, and cancellation; +- live-scale SQLite plans, VM-step/RSS ceilings beyond the retained C-03 canary; +- deployed archive, operator secrets, and production data. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/EVIDENCE.md new file mode 100644 index 000000000..1c9f83597 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/EVIDENCE.md @@ -0,0 +1,243 @@ +# Evidence and authority record + +## Authority order used + +1. The mission prompt for job `testdiet-01`. +2. The supplied project-state archive and its current exported source. +3. Repository instructions and current typed interfaces. +4. Complete relevant Beads records, with later notes preferred over older descriptions. +5. Git history for intent and regression context. +6. The supplied Test Diet dossier as a stale planning aid, never as authority over current source. + +No API, helper, query contract, or production seam was invented to satisfy the mission. + +## Snapshot identity and integrity + +`polylogue-manifest.json` records: + +```text +project: polylogue +source: /realm/project/polylogue +generated_at: 2026-07-17T105328Z +branch: master +commit: b9052e09103502017c0f510ecc699aac395de23c +dirty: true +``` + +`polylogue-branch-delta.md` records `origin/master` as the base, the same commit as merge base, an empty diff stat, and no branch-only commits. The bundled Git commit is: + +```text +b9052e09103502017c0f510ecc699aac395de23c +fix(daemon): bound raw maintenance admission (#2975) +2026-07-17T08:28:35+02:00 +``` + +I reconstructed the repository from `polylogue-all-refs.bundle`, checked out that commit detached, overlaid `polylogue-working-tree.tar.gz`, and compared tracked files against the commit. The retained tracked tree was byte-clean. This does not negate the manifest's `dirty=true` flag: the archive does not preserve a recoverable tracked patch or enough metadata to identify omitted ignored/untracked state. The patch is therefore named against the commit and explicitly preserves this uncertainty. + +## Repository architecture and instructions + +`CLAUDE.md` / `AGENTS.md` establish that the substrate owns semantics and that new adapters or parallel frameworks should not duplicate query logic. `TESTING.md` identifies `devtools test` as the preferred managed entry point. The implementation follows those constraints: + +- no new production query abstraction; +- no duplicate SQL evaluator; +- no direct row insertion used as a substitute for ingest; +- no source archive included in the deliverable; +- only test-owned facts and tests are added. + +The managed runner's storage policy was inspected in `devtools/verify_runs.py`. It defaults to bounded `/dev/shm` and refuses a disk fallback when adaptive tmpfs capacity falls below 64 MiB. It also explicitly preserves `POLYLOGUE_PYTEST_TMPFS=0`, which was used for the successful managed run in this container. + +## Current production route findings + +### Provider ingestion + +`polylogue/sources/parsers/codex.py` recognizes Codex JSONL `session_meta` and `response_item` records. Function calls become `tool_use` blocks; `function_call_output` records become `tool_result` blocks. Structured output metadata containing `exit_code` drives `tool_result_is_error` and `tool_result_exit_code`. + +`polylogue/pipeline/services/archive_ingest.py` supplies `parse_sources_archive`, the real source acquisition/parse/materialize/index service used by the patch. The survivor writes native JSONL and invokes this service rather than calling the parser directly or seeding SQLite tables. + +### Public expression parsing and lowering + +`polylogue/archive/query/expression.py` provides the current public parser and lowerer: + +- `parse_expression_ast` +- `parse_unit_source_expression` +- `compile_expression` +- terminal source and pipeline parsing +- structural `exists` lowering + +The current grammar accepts action terminal sources, `where`, `group by`, `count`, `limit`, `offset`, sorting, and session-scoped predicates. It retains explicit errors for unsupported terminal shapes, unknown fields, malformed clauses, invalid numeric values, and ambiguous identities. + +### Shared terminal execution + +`polylogue/archive/query/unit_results.py` is the shared terminal executor. `query_unit_rows` applies pipeline limit/offset, obtains the query-unit descriptor, and dispatches to one registered terminal executor. `_execute_rows_terminal` calls the descriptor's SQL query method; `_execute_count_terminal` delegates one-field counts to `ArchiveStore.query_unit_counts`. The module states that this executor is shared across CLI find, MCP query units, daemon query units, and Python API. + +The new law calls `query_unit_rows` directly for row, count, group, limit, and offset agreement and separately calls the public CLI route to prove reparsing and rendering. + +### Canonical action relation + +`polylogue/storage/sqlite/action_relation.py` is the source of truth for tool-use/result pairing. `action_relation_select_sql`: + +- ranks tool uses by `(session_id, tool_id)` and message/block order; +- ranks tool results by the same key and order; +- pairs on session, tool ID, and `result_rank = use_rank`; +- retains uses with missing results through a left join; +- retains tool uses without an ID through a separate `UNION ALL` branch; +- can place the same exact-session bound on each physical branch. + +`bounded_action_relation_cte` applies one selected session set to all three branches. + +`polylogue/storage/sqlite/archive_tiers/archive.py` extracts exact owning-session bounds from predicates in `_exact_session_ids_from_predicate` and constructs the bounded relation in `_action_relation_for_query`. `query_actions` uses the canonical relation and stable occurrence/block ordering. `query_unit_counts` compiles the same structural predicate over the SQL-backed unit relation and performs exact grouped counts. + +The anti-vacuity mutation removes the rank equality in a private archive view. This targets the concrete relational mechanism rather than a test helper. + +### Public read and action routes + +The root Click CLI is exported from `polylogue.cli`. Existing routing in `polylogue/cli/archive_query.py` sends terminal unit expressions to the shared query-unit executor and emits typed JSON envelopes. Existing destructive verbs in `polylogue/cli/query_verbs.py` and `polylogue/cli/verb_cardinality.py` resolve the full selected session set, enforce cardinality guards, and support dry-run/all/apply behavior. + +The patch uses: + +```text +find +find then delete --dry-run --all +find then delete --yes --all +``` + +against private archive clones. It does not call callbacks or mock the resolver. + +## Existing workload canary findings + +`tests/infra/workload_artifacts.py` defines deterministic provider-native archive artifacts and immutable cache/clone helpers. The default seeded archive includes the realized C-03 Codex tool-heavy workload: 64 sessions plus an exact target and provider-native call/result relationships. + +`tests/unit/storage/test_archive_tiers_archive.py` contains two directly relevant current tests: + +- `test_exact_session_action_count_bounds_pairing_before_global_ranking` +- `test_c03_exact_session_actions_uses_real_provider_pipeline_and_planted_facts` + +They prove exact-session physical bounding and the generated provider pipeline. They do not independently prove the broad logical agreement of membership, counts, partitions, pages, and destructive preview/apply. The new manifest is layered onto this artifact rather than replacing it. + +Both existing nodes passed in the focused run. + +## Affected test and helper inspection + +### `tests/unit/cli/test_query_expression.py` + +At this snapshot: 6,494 lines, 22 classes, 328 test functions. The file covers materially distinct obligations, including: + +- lexer and AST shape; +- boolean precedence and negation; +- quoted phrases and escaped quotes; +- exact session identity, native/canonical identity, and ambiguous-prefix errors; +- structural unit `exists` predicates; +- terminal unit parsing and execution; +- group/count/sort/limit/offset ordering; +- explicitly named unsupported pipeline shapes; +- FTS, vector, lineage, sequence, file, action, assertion, run, and context-snapshot paths; +- field registry completeness and examples; +- JSON-spec strictness; +- CLI/root request and cross-surface compatibility; +- malformed count, unknown field, invalid origin, and security-sensitive broadening rejections. + +These obligations are not deleted or collapsed by the patch. The entire file passed in the focused run. + +### `tests/unit/cli/test_query_exec_laws.py` + +At this snapshot: 3,552 lines, 42 classes, 66 test functions. It covers archive-vs-daemon routing, structured and lexical reads, exact/native reference resolution, projection/output, count/group behavior, filters, pagination/cursors, semantic retrieval, mutation actions, delivery, and error paths. The file contains many local `FakeArchiveStore` definitions and patched collaborators, matching the Test Diet dossier's concern about forwarding-heavy tests. They remain present because this package does not include deletion certification. + +The entire file passed in the focused run. + +### Existing infrastructure + +- `tests/infra/query_cases.py`: 54 lines; `ArchiveQueryCase` shadow case representation. +- `tests/infra/surfaces.py`: 616 lines; repository, facade, CLI, MCP, daemon, and SQLite adapters, including partial query translation. +- `tests/infra/semantic_facts.py`: 252 lines; `SessionFacts` and `ArchiveFacts` semantic expectations. + +The new helper does not modify or replace these consumers. It is narrower: one independent provider-wire relation specifically for query algebra/cardinality survival. + +### Security and destructive cardinality + +`tests/unit/storage/test_query_security.py` was included in the compatibility run. `tests/unit/cli/test_verb_cardinality.py::TestDeleteCardinalityLargeNonMocked::test_guard_dry_run_and_deleted_sets_are_identical_and_unlimited` was also rerun. This preserves parameter binding and the historical full-set preview/apply contract that the new survivor exercises through the actual CLI. + +## Beads findings + +### `polylogue-1xc.14.1` — archive workload profiles + +The record requires provider-native bytes, production acquire/parse/materialize/index/query routes, deterministic correlated shapes, and canaries for duplicate/missing/late/error tool-result pairing. Its design explicitly permits hand-authored fixtures for independent known-answer oracles while prohibiting a handwritten replacement for workload profiles. It also names C-03 as the first query canary and requires a global-first mutation to fail. + +The patch follows this boundary: it reuses the realized C-03 workload artifact and adds only a fixed independent known-answer micro-corpus. + +The issue status remains `in_progress`, and its notes describe an earlier incomplete slice. Current source at `b9052e0` already contains the C-03 artifact and relation changes from `c20286459`; source wins over stale status prose for what exists in this snapshot. + +### `polylogue-yeq.3` — query laws and cross-surface parity + +This open task defines the exact laws most relevant here: + +- page concatenation enumerates each logical member once; +- grouped counts sum to the matching-grain population; +- identity and stable order persist; +- duplicate/missing/late result shapes are represented; +- broken predicate pushdown, continuation state, public type, and reference routes should fail. + +The new survivor implements the membership/count/group/page subset and a broken relational-pairing witness. It does not claim completion of the broader cross-surface, cancellation, live-census, or resource-receipt program. + +### `polylogue-b054.1.1.4` — real production mutation + +This open task rejects source-spelling tests and asks for a semantic mutation that makes a dependent real-route test fail. It specifically concerns the ordinary testmon affected gate, dependency graph, isolated worktrees, and selection boundedness. + +The new mutation is semantically real and exercises production SQL/repository behavior, but it does not execute the testmon affected-selection harness. That remaining distinction is stated as a limitation rather than blurred into a completion claim. + +## Git history findings + +Relevant history inspected: + +- `c20286459` (`2026-07-17`) — `feat(schemas): derive archive workload profiles (#2934)`. Added `action_relation.py`, C-03 workload artifacts, exact-session action changes, and canary tests. This is the current structural foundation. +- `fb9073cfc` (`2026-06-27`) — `feat(query): add Terminal pipeline stage; route verbs through one executor (#2006) (#2463)`. Established the shared terminal execution route used by the survivor. +- `0eb2300bb` (`2026-06-14`) — `fix: delete full-set cardinality + Codex P2 read-surface findings (#1873) (#1876)`. Repaired destructive full-set selection and added cardinality guards; this history motivates explicit preview/apply equality. +- `13d19ae36` (`2026-07-13`) — `fix(actions): read legacy Codex commands without rewriting evidence (#2855)`. Reinforces that command extraction belongs in the existing action relation/query route. +- `5d5edaf49` (`2026-07-10`) — `fix: repair 3 flagship CLI query bugs found in prod smoke test (#2626)`. Added query execution regressions around public CLI behavior. +- `fd7b35492` (`2026-07-17`) — archive read admission/cancellation changes touching the action/query route. +- `805d49286` (`2026-07-17`) — raw authority replay changes touching storage route after C-03. + +The current snapshot includes all of these ancestors. The patch is based on the exact named snapshot, not a newer bundled remote ref. + +## Test Diet packet findings and contradictions + +The supplied dossier is explicitly marked `prepared-not-execution-grade` and names stale head `21f78b4db2ba62ff44b5f16dfab96067bc249b4c`. It proposes exactly the two new paths used here: + +```text +tests/infra/query_manifest_oracle.py +tests/unit/cli/test_query_composition_laws.py +``` + +It asks for independent planted facts, exact membership/count/partition/page/preview/apply laws, duplicate/join sensitivity, and retention of diagnostic/security branches. Those recommendations remain applicable. + +Material stale/current contradictions: + +1. The dossier says no realized sensitivity artifact exists and that C-03/upstream receipts are prerequisites. The current snapshot is later and contains the C-03 implementation from `c20286459`. This patch therefore starts from the realized canary as the mission requires. +2. The dossier names `polylogue/cli/query.py` as an authoritative route. Current source routes root query execution through `polylogue/cli/archive_query.py`, `polylogue/cli/query_verbs.py`, and the exported `polylogue.cli` group. The test uses current source. +3. The area packet suggests eventual repository, facade, CLI, HTTP, and rewritten MCP parity. It also marks MCP/web as rewrite boundaries. The mission asks for stable public read/action routes; this patch uses stable CLI read and delete routes and does not invent cross-surface adapters. +4. The dossier proposes multiple separate survivor nodes and an irrelevant-growth work law. This package chooses one internally coherent real-route behavior plus one mutation witness, preserving C-03's existing bound test. It does not claim the larger `polylogue-yeq.3` program. +5. The dossier identifies deletion candidates but explicitly requires dominance proof first. No deletion is performed. + +## Why the selected fact shape is discriminating + +- Duplicate call IDs detect many-to-many joins and lost ordinal pairing. +- Two results with different exit codes detect incorrect result association, not just row count. +- Missing result detects accidental inner joins and loss of unknown state. +- Orphan result detects result-first row manufacture. +- An unselected call in a selected session detects session-only selection. +- A decoy whose output contains the token detects broadening from command to output/global text. +- Two selected sessions make destructive preview/apply cardinality non-singleton. +- Five rows with a limit of two require three pages and expose early limit/global-first errors. +- The `0/1/unknown` partition detects null collapse and aggregate grain drift. +- Private archive clones make destructive and mutation tests isolated from the shared C-03 cache. + +## Unverified external evidence + +No claim is made about: + +- the operator's live daemon or archive; +- browser behavior; +- secrets or authentication state; +- a NixOS deployment; +- the current operator worktree beyond the supplied snapshot; +- newer remote refs bundled after the named snapshot; +- full-suite or release-gate status. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/HANDOFF.md new file mode 100644 index 000000000..dde64e54f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/HANDOFF.md @@ -0,0 +1,199 @@ +# testdiet-01 handoff: query algebra and cardinality survivor + +## Mission and delivered result + +This package adds one coherent real-route survivor for Polylogue query algebra and relational cardinality. It starts from the realized C-03 seeded archive, layers a small independent provider-wire manifest onto a private clone, ingests those bytes through the production Codex acquisition/parse/materialize/index path, and exercises public expressions through parsing, lowering, SQL relation construction, repository reads, terminal-unit execution, root CLI reads, and the public `find ... then delete` preview/apply route. + +The survivor establishes one fixed logical population of five selected actions in two sessions. Membership, ungrouped count, error partition, stable pagination, CLI payloads, destructive preview, destructive apply, and post-apply absence must all agree with that population. The planted shape includes duplicate tool IDs, paired successes and errors, a missing result, an orphan result, an unselected call, and an output-only decoy. A second test installs a deliberately broken same-session/same-tool-ID join and proves that the survivor rejects the resulting row multiplication: seven selected rows instead of five. + +No production code was changed. The current snapshot's action relation already implements the required ordinal pairing and selective physical bound. Adding a second seam or query framework would have weakened the substrate-first architecture; the patch therefore consists only of a test-owned provider-wire oracle and real-route survivor tests. + +## Snapshot identity + +The attached project-state archive is authoritative for this draft. + +- Project: `polylogue` +- Snapshot source recorded by manifest: `/realm/project/polylogue` +- Manifest generation time: `2026-07-17T105328Z` +- Branch: `master` +- Commit: `b9052e09103502017c0f510ecc699aac395de23c` +- Commit subject: `fix(daemon): bound raw maintenance admission (#2975)` +- Commit author/commit timestamp: `2026-07-17T08:28:35+02:00` +- Manifest dirty flag: `true` +- Branch delta: empty against `origin/master`; merge base is the same commit + +I reconstructed a detached checkout from `polylogue-all-refs.bundle`, checked out the named commit, overlaid the exported working tree, and compared tracked content with the commit. No retained tracked-file delta was present. The snapshot still records `dirty=true`; the supplied artifacts do not preserve enough information to identify whether that flag came from ignored, untracked, or otherwise omitted local state. This package does not claim that the operator's original worktree was clean. + +## Inspected evidence + +Repository instructions and verification policy: + +- `CLAUDE.md` / `AGENTS.md` +- `TESTING.md` +- `pyproject.toml` +- `devtools/verify_runs.py` and the managed pytest scratch policy + +Production query and ingest route: + +- `polylogue/sources/parsers/codex.py` +- `polylogue/pipeline/services/archive_ingest.py` +- `polylogue/archive/query/expression.py` +- `polylogue/archive/query/unit_results.py` +- `polylogue/storage/sqlite/action_relation.py` +- `polylogue/storage/sqlite/archive_tiers/index.py` +- `polylogue/storage/sqlite/archive_tiers/archive.py` +- `polylogue/cli/archive_query.py` +- `polylogue/cli/query_verbs.py` +- `polylogue/cli/verb_cardinality.py` +- `polylogue/surfaces/payloads.py` + +Affected tests and helpers: + +- `tests/infra/workload_artifacts.py` +- `tests/infra/query_cases.py` +- `tests/infra/surfaces.py` +- `tests/infra/semantic_facts.py` +- `tests/unit/storage/test_archive_tiers_archive.py` +- `tests/unit/sources/test_parsers_codex.py` +- `tests/unit/cli/test_query_expression.py` +- `tests/unit/cli/test_query_exec_laws.py` +- `tests/unit/cli/test_verb_cardinality.py` +- `tests/unit/storage/test_query_security.py` + +Authority and intent records: + +- `.beads/issues.jsonl`, especially `polylogue-1xc.14.1`, `polylogue-yeq.3`, and `polylogue-b054.1.1.4` +- Snapshot Beads export and branch-delta artifacts +- Test Diet dossier `dossiers/exact-query-selection.md` +- Test Diet area packet `areas/query-composition.md` +- Relevant Git history, including `c20286459`, `fb9073cfc`, `0eb2300bb`, `13d19ae36`, and `5d5edaf49` + +`EVIDENCE.md` records the concrete findings and stale/current contradictions. + +## Mechanism + +### Independent expected fact set + +`tests/infra/query_manifest_oracle.py` owns a fixed Codex JSONL micro-corpus. Its dataclasses are provider facts, not production query models. The helper writes native `session_meta`, `response_item/function_call`, and `response_item/function_call_output` records. It computes expected actions directly from those planted facts by pairing the nth call with the nth result sharing a call ID. It does not import Polylogue parsing, lowering, SQL, repository, or payload code. + +The selected public predicate is: + +```text +actions where session.origin:codex-session AND command:testdiet-cardinality-law +``` + +The independently expected relation is: + +| Order | Session | Command suffix | Error state | Exit code | +| ---: | --- | --- | --- | ---: | +| 1 | `codex-session:testdiet-query-alpha` | `alpha-one` | success (`0`) | 0 | +| 2 | `codex-session:testdiet-query-alpha` | `alpha-two` | error (`1`) | 2 | +| 3 | `codex-session:testdiet-query-alpha` | `alpha-missing` | unknown | — | +| 4 | `codex-session:testdiet-query-beta` | `beta-success` | success (`0`) | 0 | +| 5 | `codex-session:testdiet-query-beta` | `beta-error` | error (`1`) | 3 | + +Expected partition: `0 -> 2`, `1 -> 2`, `unknown -> 1`. Expected selected sessions: alpha and beta. The decoy session has the token only in result output, so command selection must not delete it. + +### Production route exercised + +1. Build the existing deterministic C-03 seeded archive using `build_seeded_archive`. +2. Clone it with `clone_seeded_archive`; no shared cached artifact is mutated. +3. Render three native Codex JSONL sources from the independent manifest. +4. Ingest them with production `parse_sources_archive` and `Source(name="codex", ...)`. +5. Parse the action terminal expression with `parse_unit_source_expression`. +6. Lower the corresponding session selector with `compile_expression`. +7. Execute action membership through `ArchiveStore.query_actions`, whose source is the canonical action relation SQL. +8. Execute rows, `count`, `group by is_error | count`, limit, and offset through `query_unit_rows` and the shared terminal executor. +9. Reparse and execute the same expression through root CLI `find --format json`. +10. Resolve the session expression through public `find ... then delete --dry-run --all`, then apply it with `--yes --all` against a private archive clone. +11. Verify exact preview/apply cardinality, deletion of the selected sessions, survival of the output-only decoy, and an empty post-delete public read. + +### Anti-vacuity mutation + +The mutation test replaces only the private test archive's `actions` view with the historical naive relation: + +```sql +LEFT JOIN blocks r + ON r.session_id = u.session_id + AND r.tool_id = u.tool_id + AND r.block_type = 'tool_result' +``` + +It deliberately omits ordinal `ROW_NUMBER()` pairing and `result_rank = use_rank`. In alpha, two tool uses and two results share one ID, so the naive join creates a 2x2 product: four rows where the intended relation has two. The complete selected population becomes seven instead of five. The test requires the real repository-membership survivor to raise and checks the precise `expected 5 ... got 7` diagnostic. This is a semantic behavior mutation; it does not inspect source spelling. + +## Decisions + +- Reused the realized C-03 artifact rather than inventing a Test Diet corpus or query framework. +- Kept the oracle test-owned and independent from production query semantics. +- Used native Codex wire bytes and production ingestion rather than inserting rows directly. +- Exercised one stable public read surface and one stable destructive action surface: root CLI `find` and `find ... then delete`. +- Kept all existing tests and helpers. No deletion or migration is part of this patch. +- Preserved parser diagnostic, quoting/escaping, security, compatibility, terminal-unit, and cardinality tests; the focused compatibility run is reported below. +- Did not add an HTTP or MCP law. The current Test Diet packet explicitly identifies MCP/web as rewrite boundaries, and the mission can be satisfied through stable CLI read/action routes without broadening the patch. +- Did not add a new work-bound measurement. The test starts from and reruns the existing C-03 exact-session work-bound canary; the new law concentrates on logical membership and cardinality composition. + +## Changed files + +- `tests/infra/query_manifest_oracle.py` — new independent provider-wire manifest, expected relation, membership, session, and partition oracle. +- `tests/unit/cli/test_query_composition_laws.py` — new real-route survivor and duplicate-ID join mutation witness. + +`PATCH.diff` is the complete apply-ready change. `FILES/` is omitted because the unified diff fully and unambiguously represents both new files. + +## Acceptance matrix + +| Obligation | Evidence in patch | Status | +| --- | --- | --- | +| Independent oracle from planted inputs | Test-owned Codex dataclasses and nth-call/nth-result evaluator | Met | +| Production provider bytes and ingest | JSONL renderer -> `parse_sources_archive` | Met | +| Parse and lower public expressions | `parse_unit_source_expression`; `compile_expression` | Met | +| SQL/repository membership | `ArchiveStore.query_actions` against canonical action relation | Met | +| Count equals membership | `| count` returns exactly 5 | Met | +| Group partitions conserve population | `group by is_error | count`; sum is 5 | Met | +| Stable pagination | pages at offsets 0, 2, 4 concatenate to exact ordered population once | Met | +| Stable public read route | root CLI `find --format json` | Met | +| Preview/apply agreement | dry-run and apply select/delete exactly alpha and beta | Met | +| Decoy exclusion | output-only token session survives | Met | +| Duplicate/missing/orphan result shapes | all planted and asserted | Met | +| Row multiplication sensitivity | naive duplicate-ID join yields 7 and is rejected | Met | +| Existing C-03 selective pairing/work bound | existing two C-03 tests rerun green | Preserved | +| Parser diagnostics/security/compatibility | focused 541-test run includes the affected parser and security suites | Preserved | +| Full repository suite | not run | Unverified | +| `devtools verify --quick` | not run | Unverified | +| Live daemon/browser/NixOS/operator worktree | unavailable by contract | Unverified | + +## Apply order + +From a clean checkout of `b9052e09103502017c0f510ecc699aac395de23c`: + +```bash +git apply --check PATCH.diff +git apply PATCH.diff +POLYLOGUE_PYTEST_TMPFS=0 .venv/bin/python -m devtools test \ + tests/unit/cli/test_query_composition_laws.py -x +``` + +Then run the broader compatibility command listed in `TESTS.md`, followed by the repository's ordinary full verification in the operator environment. + +## Verification performed + +- Managed project runner on the new module: **2 passed in 6.80s**; supervisor step **ok in 12.2s**. +- Focused affected compatibility set: **541 passed, 1 skipped in 26.19s**. +- Ruff lint: passed. +- Ruff format check: passed; two files already formatted. +- Mypy on both new files: passed, no issues. +- Patch apply check against a clean named-snapshot worktree: performed during package validation. +- Result ZIP member, placeholder, snapshot-copy, size, hash, and extraction validation: recorded in the final package validation section of the operator response and reproducible from the ZIP itself. + +The first managed-run attempt used the default tmpfs policy and was refused before test collection because the container exposed only 64 MiB free in `/dev/shm`. Re-running with the repository-supported explicit `POLYLOGUE_PYTEST_TMPFS=0` storage choice passed. Raw pytest was also used for the 541-test compatibility run. `TESTS.md` preserves both the environmental refusal and successful commands. + +## Risks and limitations + +The mutation is an executable semantic mutation of the persisted production relation in an isolated archive clone, not a temporary edit of `polylogue/storage/sqlite/action_relation.py` followed by testmon affected-selection. That is sufficient to prove the survivor detects the representative relational defect requested here. A stricter testmon-selection proof belongs to `polylogue-b054.1.1.4` and would be a broader harness task. + +The survivor proves stable paging by repeated public terminal execution with limit/offset, not a daemon continuation token. It proves one public read surface and one destructive action surface, not cross-surface CLI/Python/HTTP/MCP parity. It layers facts onto C-03 but does not add a new irrelevant-growth/SQLite-VM-step assertion. Existing parser and security suites were retained and run, but the full repository suite and `devtools verify --quick` remain unverified. + +## Value of another iteration + +A small repair pass would likely add little value: the patch applies cleanly, both survivor nodes pass, the representative mutation is killed, and the affected compatibility set is green. + +A substantial second pass could add meaningful but separate scope: a reversible source mutation wired through the real testmon affected gate, an irrelevant-growth VM-step law, and HTTP/Python cross-surface parity using the same manifest. That would improve sensitivity and surface breadth, but it would no longer be a small repair; it would approach the open `polylogue-b054.1.1.4` and `polylogue-yeq.3` programs. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/PATCH.diff new file mode 100644 index 000000000..74b1ff289 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/PATCH.diff @@ -0,0 +1,647 @@ +diff --git a/tests/infra/query_manifest_oracle.py b/tests/infra/query_manifest_oracle.py +new file mode 100644 +index 0000000000000000000000000000000000000000..0f3096398db3b7cb0a2a976553b6ad9de3f56827 +--- /dev/null ++++ b/tests/infra/query_manifest_oracle.py +@@ -0,0 +1,323 @@ ++"""Independent provider-wire facts for the query cardinality survivor. ++ ++The manifest in this module is deliberately test-owned. It renders Codex ++JSONL inputs and computes the expected action relation directly from those ++planted facts; it never asks Polylogue's parser, query compiler, SQL views, or ++repository for an expected value. ++""" ++ ++from __future__ import annotations ++ ++import json ++from collections import Counter, defaultdict ++from dataclasses import dataclass ++from datetime import datetime ++from pathlib import Path ++from typing import TypeAlias ++ ++QUERY_CARDINALITY_TOKEN = "testdiet-cardinality-law" ++ ++ActionIdentity: TypeAlias = tuple[str, str, str | None, int | None, int | None] ++ ++ ++@dataclass(frozen=True) ++class PlantedCodexCall: ++ """One provider-native function call planted in a Codex JSONL source.""" ++ ++ call_id: str ++ command: str ++ timestamp: str ++ ++ ++@dataclass(frozen=True) ++class PlantedCodexResult: ++ """One provider-native function result, including orphan-result cases.""" ++ ++ call_id: str ++ output: str ++ exit_code: int ++ timestamp: str ++ ++ @property ++ def wire_output(self) -> str: ++ return json.dumps( ++ {"metadata": {"exit_code": self.exit_code}, "output": self.output}, ++ sort_keys=True, ++ separators=(",", ":"), ++ ) ++ ++ ++@dataclass(frozen=True) ++class PlantedCodexSession: ++ """Provider facts for one source file and its explicit canonical identity.""" ++ ++ native_session_id: str ++ canonical_session_id: str ++ timestamp: str ++ calls: tuple[PlantedCodexCall, ...] ++ results: tuple[PlantedCodexResult, ...] ++ ++ def wire_records(self) -> tuple[dict[str, object], ...]: ++ records: list[dict[str, object]] = [ ++ { ++ "type": "session_meta", ++ "payload": { ++ "id": self.native_session_id, ++ "timestamp": self.timestamp, ++ }, ++ } ++ ] ++ for index, call in enumerate(self.calls): ++ records.append( ++ { ++ "type": "response_item", ++ "timestamp": call.timestamp, ++ "payload": { ++ "type": "function_call", ++ "id": f"fc-{self.native_session_id}-{index}", ++ "call_id": call.call_id, ++ "name": "exec_command", ++ "arguments": json.dumps({"cmd": call.command}, sort_keys=True, separators=(",", ":")), ++ }, ++ } ++ ) ++ for index, result in enumerate(self.results): ++ records.append( ++ { ++ "type": "response_item", ++ "timestamp": result.timestamp, ++ "payload": { ++ "type": "function_call_output", ++ "id": f"fco-{self.native_session_id}-{index}", ++ "call_id": result.call_id, ++ "output": result.wire_output, ++ }, ++ } ++ ) ++ return tuple(records) ++ ++ ++@dataclass(frozen=True) ++class ExpectedActionFact: ++ """The intended ordinal pairing of one planted use with its result.""" ++ ++ session_id: str ++ command: str ++ occurred_at_ms: int ++ output_text: str | None ++ is_error: int | None ++ exit_code: int | None ++ ++ @property ++ def identity(self) -> ActionIdentity: ++ return ( ++ self.session_id, ++ self.command, ++ self.output_text, ++ self.is_error, ++ self.exit_code, ++ ) ++ ++ ++@dataclass(frozen=True) ++class QueryCardinalityManifest: ++ """Fixed known-answer corpus for membership and cardinality laws.""" ++ ++ sessions: tuple[PlantedCodexSession, ...] ++ ++ def write_sources(self, root: Path) -> tuple[Path, ...]: ++ root.mkdir(parents=True, exist_ok=True) ++ paths: list[Path] = [] ++ for session in self.sessions: ++ path = root / f"{session.native_session_id}.jsonl" ++ path.write_text( ++ "".join( ++ json.dumps(record, sort_keys=True, separators=(",", ":")) + "\n" ++ for record in session.wire_records() ++ ), ++ encoding="utf-8", ++ ) ++ paths.append(path) ++ return tuple(paths) ++ ++ def all_actions(self) -> tuple[ExpectedActionFact, ...]: ++ """Pair the nth call/result sharing an ID, directly from planted facts.""" ++ actions: list[ExpectedActionFact] = [] ++ for session in self.sessions: ++ result_groups: dict[str, list[PlantedCodexResult]] = defaultdict(list) ++ for planted_result in session.results: ++ result_groups[planted_result.call_id].append(planted_result) ++ call_ranks: Counter[str] = Counter() ++ for call in session.calls: ++ call_rank = call_ranks[call.call_id] ++ call_ranks[call.call_id] += 1 ++ matching_results = result_groups.get(call.call_id, []) ++ paired_result = matching_results[call_rank] if call_rank < len(matching_results) else None ++ actions.append( ++ ExpectedActionFact( ++ session_id=session.canonical_session_id, ++ command=call.command, ++ occurred_at_ms=_epoch_ms(call.timestamp), ++ output_text=paired_result.wire_output if paired_result is not None else None, ++ is_error=(int(paired_result.exit_code != 0) if paired_result is not None else None), ++ exit_code=paired_result.exit_code if paired_result is not None else None, ++ ) ++ ) ++ return tuple(actions) ++ ++ def matching_actions(self) -> tuple[ExpectedActionFact, ...]: ++ """Evaluate the survivor's fixed public predicate without production code.""" ++ return tuple( ++ sorted( ++ (action for action in self.all_actions() if QUERY_CARDINALITY_TOKEN in action.command), ++ key=lambda action: (action.occurred_at_ms, action.identity), ++ ) ++ ) ++ ++ def matching_action_identities(self) -> tuple[ActionIdentity, ...]: ++ return tuple(action.identity for action in self.matching_actions()) ++ ++ def matching_session_ids(self) -> tuple[str, ...]: ++ selected = {action.session_id for action in self.matching_actions()} ++ return tuple( ++ session.canonical_session_id for session in self.sessions if session.canonical_session_id in selected ++ ) ++ ++ def is_error_partition(self) -> dict[str, int]: ++ counts: Counter[str] = Counter() ++ for action in self.matching_actions(): ++ key = "unknown" if action.is_error is None else str(action.is_error) ++ counts[key] += 1 ++ return dict(counts) ++ ++ @property ++ def decoy_session_id(self) -> str: ++ return "codex-session:testdiet-query-decoy" ++ ++ ++def query_cardinality_manifest() -> QueryCardinalityManifest: ++ """Return the stable micro-corpus layered onto the realized C-03 archive.""" ++ return QueryCardinalityManifest( ++ sessions=( ++ PlantedCodexSession( ++ native_session_id="testdiet-query-alpha", ++ canonical_session_id="codex-session:testdiet-query-alpha", ++ timestamp="2026-07-01T00:00:00Z", ++ calls=( ++ PlantedCodexCall( ++ call_id="shared-duplicate-id", ++ command=f"printf {QUERY_CARDINALITY_TOKEN}-alpha-one", ++ timestamp="2026-07-01T00:00:01Z", ++ ), ++ PlantedCodexCall( ++ call_id="shared-duplicate-id", ++ command=f"printf {QUERY_CARDINALITY_TOKEN}-alpha-two", ++ timestamp="2026-07-01T00:00:02Z", ++ ), ++ PlantedCodexCall( ++ call_id="missing-result", ++ command=f"printf {QUERY_CARDINALITY_TOKEN}-alpha-missing", ++ timestamp="2026-07-01T00:00:03Z", ++ ), ++ PlantedCodexCall( ++ call_id="alpha-decoy", ++ command="printf alpha-command-outside-law", ++ timestamp="2026-07-01T00:00:04Z", ++ ), ++ ), ++ results=( ++ PlantedCodexResult( ++ call_id="shared-duplicate-id", ++ output="alpha-one", ++ exit_code=0, ++ timestamp="2026-07-01T00:00:10Z", ++ ), ++ PlantedCodexResult( ++ call_id="shared-duplicate-id", ++ output="alpha-two", ++ exit_code=2, ++ timestamp="2026-07-01T00:00:11Z", ++ ), ++ PlantedCodexResult( ++ call_id="alpha-decoy", ++ output="alpha-decoy", ++ exit_code=0, ++ timestamp="2026-07-01T00:00:12Z", ++ ), ++ PlantedCodexResult( ++ call_id="orphan-result", ++ output=f"{QUERY_CARDINALITY_TOKEN}-orphan-must-not-be-an-action", ++ exit_code=7, ++ timestamp="2026-07-01T00:00:13Z", ++ ), ++ ), ++ ), ++ PlantedCodexSession( ++ native_session_id="testdiet-query-beta", ++ canonical_session_id="codex-session:testdiet-query-beta", ++ timestamp="2026-07-01T00:01:00Z", ++ calls=( ++ PlantedCodexCall( ++ call_id="beta-success", ++ command=f"printf {QUERY_CARDINALITY_TOKEN}-beta-success", ++ timestamp="2026-07-01T00:00:05Z", ++ ), ++ PlantedCodexCall( ++ call_id="beta-error", ++ command=f"printf {QUERY_CARDINALITY_TOKEN}-beta-error", ++ timestamp="2026-07-01T00:00:06Z", ++ ), ++ ), ++ results=( ++ PlantedCodexResult( ++ call_id="beta-success", ++ output="beta-success", ++ exit_code=0, ++ timestamp="2026-07-01T00:01:10Z", ++ ), ++ PlantedCodexResult( ++ call_id="beta-error", ++ output="beta-error", ++ exit_code=3, ++ timestamp="2026-07-01T00:01:11Z", ++ ), ++ ), ++ ), ++ PlantedCodexSession( ++ native_session_id="testdiet-query-decoy", ++ canonical_session_id="codex-session:testdiet-query-decoy", ++ timestamp="2026-07-01T00:02:00Z", ++ calls=( ++ PlantedCodexCall( ++ call_id="decoy-output-only", ++ command="printf output-only-lookalike", ++ timestamp="2026-07-01T00:00:07Z", ++ ), ++ ), ++ results=( ++ PlantedCodexResult( ++ call_id="decoy-output-only", ++ output=f"{QUERY_CARDINALITY_TOKEN}-appears-only-in-output", ++ exit_code=0, ++ timestamp="2026-07-01T00:02:10Z", ++ ), ++ ), ++ ), ++ ) ++ ) ++ ++ ++def _epoch_ms(timestamp: str) -> int: ++ return int(datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp() * 1000) ++ ++ ++__all__ = [ ++ "ActionIdentity", ++ "ExpectedActionFact", ++ "PlantedCodexCall", ++ "PlantedCodexResult", ++ "PlantedCodexSession", ++ "QUERY_CARDINALITY_TOKEN", ++ "QueryCardinalityManifest", ++ "query_cardinality_manifest", ++] +diff --git a/tests/unit/cli/test_query_composition_laws.py b/tests/unit/cli/test_query_composition_laws.py +new file mode 100644 +index 0000000000000000000000000000000000000000..9f8c3cba965d8655af5e6d0738b6aa4e4e2f41e7 +--- /dev/null ++++ b/tests/unit/cli/test_query_composition_laws.py +@@ -0,0 +1,312 @@ ++"""Real-route query algebra and relational-cardinality survivor laws.""" ++ ++from __future__ import annotations ++ ++import asyncio ++import json ++import os ++import shutil ++import sqlite3 ++from collections.abc import Iterator ++from contextlib import contextmanager ++from dataclasses import dataclass ++from pathlib import Path ++from typing import Any, cast ++ ++import pytest ++from click.testing import CliRunner ++ ++from polylogue.archive.query.expression import compile_expression, parse_unit_source_expression ++from polylogue.archive.query.unit_results import query_unit_rows ++from polylogue.cli import cli ++from polylogue.config import Source ++from polylogue.pipeline.services.archive_ingest import parse_sources_archive ++from polylogue.storage.sqlite.archive_tiers.archive import ArchiveStore ++from polylogue.surfaces.payloads import ( ++ ActionQueryRowPayload, ++ QueryUnitAggregateEnvelope, ++ QueryUnitEnvelope, ++) ++from tests.infra.query_manifest_oracle import ( ++ QUERY_CARDINALITY_TOKEN, ++ ActionIdentity, ++ QueryCardinalityManifest, ++ query_cardinality_manifest, ++) ++from tests.infra.workload_artifacts import build_seeded_archive, clone_seeded_archive ++ ++_ACTION_EXPRESSION = f"actions where session.origin:codex-session AND command:{QUERY_CARDINALITY_TOKEN}" ++_SESSION_EXPRESSION = f"exists action(session.origin:codex-session AND command:{QUERY_CARDINALITY_TOKEN})" ++ ++ ++@dataclass(frozen=True) ++class _PreparedArchive: ++ root: Path ++ manifest: QueryCardinalityManifest ++ ++ ++@contextmanager ++def _archive_environment(root: Path) -> Iterator[None]: ++ updates = { ++ "POLYLOGUE_ARCHIVE_ROOT": str(root), ++ "POLYLOGUE_INGEST_PARSE_WORKERS": "1", ++ "POLYLOGUE_FORCE_PLAIN": "1", ++ } ++ previous = {key: os.environ.get(key) for key in updates} ++ os.environ.update(updates) ++ try: ++ yield ++ finally: ++ for key, value in previous.items(): ++ if value is None: ++ os.environ.pop(key, None) ++ else: ++ os.environ[key] = value ++ ++ ++@pytest.fixture(scope="module") ++def query_cardinality_archive(tmp_path_factory: pytest.TempPathFactory) -> _PreparedArchive: ++ """Layer independent Codex facts onto the immutable realized C-03 canary.""" ++ work = tmp_path_factory.mktemp("query-cardinality-survivor") ++ artifact = build_seeded_archive(cache_root=work / "seeded-cache") ++ clone = clone_seeded_archive(artifact, work / "augmented-archive") ++ manifest = query_cardinality_manifest() ++ sources = [Source(name="codex", path=path) for path in manifest.write_sources(work / "wire")] ++ with _archive_environment(clone.root): ++ result = asyncio.run(parse_sources_archive(clone.root, sources)) ++ assert result.parse_failures == 0 ++ assert result.processed_ids == set(manifest.matching_session_ids()) | {manifest.decoy_session_id} ++ return _PreparedArchive(root=clone.root, manifest=manifest) ++ ++ ++def _action_identity(row: Any) -> ActionIdentity: ++ return ( ++ str(row.session_id), ++ str(row.tool_command), ++ cast(str | None, row.output_text), ++ cast(int | None, row.is_error), ++ cast(int | None, row.exit_code), ++ ) ++ ++ ++def _payload_action_identity(row: dict[str, object]) -> ActionIdentity: ++ return ( ++ str(row["session_id"]), ++ str(row["tool_command"]), ++ cast(str | None, row.get("output_text")), ++ cast(int | None, row.get("is_error")), ++ cast(int | None, row.get("exit_code")), ++ ) ++ ++ ++def _assert_repository_membership(root: Path, manifest: QueryCardinalityManifest) -> None: ++ """Assert the action_relation_select_sql -> query_actions production path.""" ++ source = parse_unit_source_expression(_ACTION_EXPRESSION) ++ assert source is not None ++ with ArchiveStore.open_existing(root) as archive: ++ rows = archive.query_actions(source.predicate, limit=100) ++ actual = tuple(_action_identity(row) for row in rows) ++ expected = manifest.matching_action_identities() ++ assert actual == expected, ( ++ f"expected {len(expected)} independently planted action rows, got {len(actual)}; " ++ f"expected={expected!r}, actual={actual!r}" ++ ) ++ ++ ++def _copy_archive(source: Path, destination: Path) -> Path: ++ shutil.copytree(source, destination) ++ return destination ++ ++ ++def _cli_env(root: Path) -> dict[str, str]: ++ return { ++ "POLYLOGUE_ARCHIVE_ROOT": str(root), ++ "POLYLOGUE_FORCE_PLAIN": "1", ++ } ++ ++ ++def test_query_algebra_cardinality_survives_real_read_and_action_routes( ++ query_cardinality_archive: _PreparedArchive, ++ tmp_path: Path, ++) -> None: ++ """Survive parser/lowerer, action relation, repository, CLI, and delete. ++ ++ Production dependencies exercised: Codex parser/materializer, ++ ``parse_unit_source_expression``/``compile_expression``, ++ ``action_relation_select_sql``, ``ArchiveStore.query_actions`` and ++ ``query_unit_counts``, ``query_unit_rows``, root CLI ``find``, and the ++ public ``find ... then delete`` preview/apply route. Removing the command ++ predicate, losing ordinal result pairing, applying LIMIT before selection, ++ or letting preview/apply resolve different sets makes this test fail. ++ """ ++ root = query_cardinality_archive.root ++ manifest = query_cardinality_archive.manifest ++ expected = manifest.matching_action_identities() ++ ++ # Parse the public terminal expression and lower its action-existence form. ++ source = parse_unit_source_expression(_ACTION_EXPRESSION) ++ assert source is not None ++ assert source.unit == "action" ++ compiled_selector = compile_expression(_SESSION_EXPRESSION) ++ assert compiled_selector.boolean_predicate is not None ++ ++ # SQL repository membership and the shared terminal-row envelope agree with ++ # the independent provider-wire oracle, including duplicate IDs, a missing ++ # result, and an orphan result that must not create an action. ++ _assert_repository_membership(root, manifest) ++ with ArchiveStore.open_existing(root) as archive: ++ envelope = query_unit_rows(archive, source, query=_ACTION_EXPRESSION, limit=100) ++ assert isinstance(envelope, QueryUnitEnvelope) ++ assert tuple(_action_identity(row) for row in envelope.items) == expected ++ assert all(isinstance(row, ActionQueryRowPayload) for row in envelope.items) ++ ++ # Exact count and a complete partition must conserve the same row grain. ++ count_expression = f"{_ACTION_EXPRESSION} | count" ++ count_source = parse_unit_source_expression(count_expression) ++ assert count_source is not None ++ partition_expression = f"{_ACTION_EXPRESSION} | group by is_error | count" ++ partition_source = parse_unit_source_expression(partition_expression) ++ assert partition_source is not None ++ with ArchiveStore.open_existing(root) as archive: ++ count_envelope = query_unit_rows(archive, count_source, query=count_expression, limit=20) ++ partition_envelope = query_unit_rows( ++ archive, ++ partition_source, ++ query=partition_expression, ++ limit=20, ++ ) ++ assert isinstance(count_envelope, QueryUnitAggregateEnvelope) ++ assert [(item.group_key, item.count) for item in count_envelope.items] == [("all", len(expected))] ++ assert isinstance(partition_envelope, QueryUnitAggregateEnvelope) ++ partition = {str(item.group_key): item.count for item in partition_envelope.items} ++ assert partition == manifest.is_error_partition() ++ assert sum(partition.values()) == len(expected) ++ ++ # Concatenating bounded public pages returns every logical member once and ++ # in the same stable order as the independent timestamp-ordered fact set. ++ paged: list[ActionIdentity] = [] ++ offsets: list[int] = [] ++ offset = 0 ++ with ArchiveStore.open_existing(root) as archive: ++ while True: ++ page = query_unit_rows( ++ archive, ++ source, ++ query=_ACTION_EXPRESSION, ++ limit=2, ++ offset=offset, ++ ) ++ assert isinstance(page, QueryUnitEnvelope) ++ offsets.append(offset) ++ paged.extend(_action_identity(row) for row in page.items) ++ if page.next_offset is None: ++ break ++ assert page.next_offset == offset + len(page.items) ++ offset = page.next_offset ++ assert offsets == [0, 2, 4] ++ assert tuple(paged) == expected ++ assert len(set(paged)) == len(expected) ++ ++ # The stable public CLI read route reparses and reexecutes the expression. ++ read_result = CliRunner().invoke( ++ cli, ++ ["--plain", "--format", "json", "find", _ACTION_EXPRESSION], ++ env=_cli_env(root), ++ ) ++ assert read_result.exit_code == 0, read_result.output ++ read_payload = cast(dict[str, object], json.loads(read_result.output)) ++ read_items = cast(list[dict[str, object]], read_payload["items"]) ++ assert tuple(_payload_action_identity(item) for item in read_items) == expected ++ assert read_payload["total"] == len(expected) ++ ++ # Preview and apply run against a private clone. The previewed identities ++ # equal the planted session set, apply reports the same cardinality, the ++ # selected sessions disappear, and the output-only decoy remains. ++ mutation_root = _copy_archive(root, tmp_path / "mutation-archive") ++ runner = CliRunner() ++ preview_result = runner.invoke( ++ cli, ++ ["--plain", "find", _SESSION_EXPRESSION, "then", "delete", "--dry-run", "--all"], ++ env=_cli_env(mutation_root), ++ ) ++ assert preview_result.exit_code == 0, preview_result.output ++ preview = cast(dict[str, object], json.loads(preview_result.output)) ++ preview_ids = tuple(cast(list[str], preview["session_ids"])) ++ assert preview["status"] == "preview" ++ assert preview["affected_count"] == 0 ++ assert set(preview_ids) == set(manifest.matching_session_ids()) ++ assert preview["session_count"] == len(manifest.matching_session_ids()) ++ ++ apply_result = runner.invoke( ++ cli, ++ ["--plain", "find", _SESSION_EXPRESSION, "then", "delete", "--yes", "--all"], ++ env=_cli_env(mutation_root), ++ ) ++ assert apply_result.exit_code == 0, apply_result.output ++ applied = cast(dict[str, object], json.loads(apply_result.output)) ++ assert applied["session_count"] == preview["session_count"] ++ assert applied["affected_count"] == preview["session_count"] ++ ++ with ArchiveStore.open_existing(mutation_root) as archive: ++ for session_id in preview_ids: ++ with pytest.raises(KeyError): ++ archive.read_summary(session_id) ++ decoy = archive.read_summary(manifest.decoy_session_id) ++ assert decoy.session_id == manifest.decoy_session_id ++ ++ post_result = runner.invoke( ++ cli, ++ ["--plain", "--format", "json", "find", _ACTION_EXPRESSION], ++ env=_cli_env(mutation_root), ++ ) ++ assert post_result.exit_code == 2, post_result.output ++ post_payload = cast(dict[str, object], json.loads(post_result.output)) ++ assert post_payload["items"] == [] ++ assert post_payload["total"] == 0 ++ ++ ++def test_survivor_detects_naive_duplicate_id_join_mutation( ++ query_cardinality_archive: _PreparedArchive, ++ tmp_path: Path, ++) -> None: ++ """Dropping ordinal pairing creates seven rows where the oracle requires five. ++ ++ The mutation replaces the production ``actions`` view with the historical ++ same-session/same-tool-id join, equivalent to removing ++ ``result_rank = use_rank`` from ``action_relation_select_sql``. The two ++ duplicate uses and two duplicate results then form a 2x2 product (four ++ rows instead of two), and the real-route membership survivor must reject it. ++ """ ++ root = _copy_archive(query_cardinality_archive.root, tmp_path / "naive-join-archive") ++ with sqlite3.connect(root / "index.db") as conn: ++ conn.executescript( ++ """ ++ DROP VIEW actions; ++ CREATE VIEW actions AS ++ SELECT ++ u.session_id, ++ u.message_id, ++ u.block_id AS tool_use_block_id, ++ u.tool_name, ++ u.semantic_type, ++ u.tool_command, ++ u.tool_path, ++ u.tool_input, ++ r.text AS output_text, ++ r.tool_result_is_error AS is_error, ++ r.tool_result_exit_code AS exit_code, ++ r.block_id AS tool_result_block_id ++ FROM blocks u ++ LEFT JOIN blocks r ++ ON r.session_id = u.session_id ++ AND r.tool_id = u.tool_id ++ AND r.block_type = 'tool_result' ++ WHERE u.block_type = 'tool_use'; ++ """ ++ ) ++ ++ with pytest.raises(AssertionError) as caught: ++ _assert_repository_membership(root, query_cardinality_archive.manifest) ++ message = str(caught.value) ++ assert "expected 5 independently planted action rows, got 7" in message ++ assert QUERY_CARDINALITY_TOKEN in message diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/TESTS.md new file mode 100644 index 000000000..adba00472 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-01/r02/extracted/TESTS.md @@ -0,0 +1,253 @@ +# Test design and execution evidence + +## Survivor design + +The patch adds two tests in `tests/unit/cli/test_query_composition_laws.py` and one independent fact helper in `tests/infra/query_manifest_oracle.py`. + +The helper is intentionally outside production query code. It describes native Codex records, renders JSONL, and derives expected action facts using only the planted calls and results. Expected values are never obtained from a Polylogue parser, compiler, SQL view, repository method, facade, or CLI response. + +The micro-corpus is layered onto the existing deterministic C-03 seeded archive. This gives the test a real mixed archive and preserves the current exact-session action work canary while adding a discriminating known-answer relation. + +## Planted relation + +The public action expression is: + +```text +actions where session.origin:codex-session AND command:testdiet-cardinality-law +``` + +The public session expression used by destructive actions is: + +```text +exists action(session.origin:codex-session AND command:testdiet-cardinality-law) +``` + +The manifest plants seven tool uses overall. Five match the command predicate: + +| Stable order | Canonical session | Call ID shape | Command | Result state | +| ---: | --- | --- | --- | --- | +| 1 | `codex-session:testdiet-query-alpha` | duplicate ID, ordinal 1 | `printf testdiet-cardinality-law-alpha-one` | output `alpha-one`, exit 0, `is_error=0` | +| 2 | `codex-session:testdiet-query-alpha` | duplicate ID, ordinal 2 | `printf testdiet-cardinality-law-alpha-two` | output `alpha-two`, exit 2, `is_error=1` | +| 3 | `codex-session:testdiet-query-alpha` | missing result | `printf testdiet-cardinality-law-alpha-missing` | output/error/exit unknown | +| 4 | `codex-session:testdiet-query-beta` | ordinary ID | `printf testdiet-cardinality-law-beta-success` | output `beta-success`, exit 0, `is_error=0` | +| 5 | `codex-session:testdiet-query-beta` | ordinary ID | `printf testdiet-cardinality-law-beta-error` | output `beta-error`, exit 3, `is_error=1` | + +Two additional uses are deliberately outside the selected population: + +- alpha has an ordinary unselected command and a paired result; +- the decoy session has an unselected command whose result output contains `testdiet-cardinality-law`. + +An orphan result containing the token is also planted. It must not manufacture an action row. + +Independent expected values: + +```text +selected actions: 5 +selected sessions: 2 +is_error=0: 2 +is_error=1: 2 +is_error=unknown: 1 +page offsets at limit 2: 0, 2, 4 +``` + +## Test 1: real read and action routes + +Node: + +```text +tests/unit/cli/test_query_composition_laws.py::test_query_algebra_cardinality_survives_real_read_and_action_routes +``` + +### Production dependencies exercised + +- `tests.infra.workload_artifacts.build_seeded_archive` +- `tests.infra.workload_artifacts.clone_seeded_archive` +- `polylogue.config.Source` +- `polylogue.pipeline.services.archive_ingest.parse_sources_archive` +- `polylogue.sources.parsers.codex` through source dispatch +- parsed-session materialization and SQLite indexing +- `polylogue.archive.query.expression.parse_unit_source_expression` +- `polylogue.archive.query.expression.compile_expression` +- `polylogue.storage.sqlite.action_relation.action_relation_select_sql` through `ArchiveStore.query_actions` +- `polylogue.storage.sqlite.archive_tiers.archive.ArchiveStore.query_actions` +- `ArchiveStore.query_unit_counts` +- `polylogue.archive.query.unit_results.query_unit_rows` +- terminal `rows` and `count` executors +- root Click CLI `find` +- public `find ... then delete --dry-run --all` +- public `find ... then delete --yes --all` + +### Assertions + +1. All three planted Codex source files ingest without parse failures and produce exactly alpha, beta, and decoy session IDs. +2. The action terminal expression parses as unit `action`. +3. The session selector lowers to a boolean predicate. +4. Repository action membership is byte-for-byte equivalent at the chosen public identity grain to the independent oracle. +5. `query_unit_rows` returns the same members and typed `ActionQueryRowPayload` rows. +6. `| count` returns one `all` group with count 5. +7. `group by is_error | count` returns exactly `0 -> 2`, `1 -> 2`, and `unknown -> 1`; the groups sum to 5. +8. Limit-2 pages at offsets 0, 2, and 4 concatenate to the exact stable unpaged order with no duplicate logical member. +9. Root CLI JSON `find` returns the same five identities and total 5. +10. On a private archive clone, delete dry-run previews exactly alpha and beta and reports zero affected rows. +11. Apply reports the same session count and exactly two affected sessions. +12. Alpha and beta are absent after apply; the output-only decoy session remains readable. +13. The original action expression returns the public typed empty result after deletion. + +### Representative defects killed + +The combined law fails if any of these semantic defects are introduced: + +- the command predicate is dropped or broadened to output text; +- ordinal duplicate-ID result pairing is removed; +- an orphan result becomes an action; +- missing results are dropped instead of represented as unknown; +- count/group executes over a different grain than rows; +- limit/offset is applied before the effective selection; +- stable order changes between pages; +- CLI reparsing changes selection; +- delete preview and apply resolve different populations; +- the destructive action truncates to a default display page. + +## Test 2: anti-vacuity mutation + +Node: + +```text +tests/unit/cli/test_query_composition_laws.py::test_survivor_detects_naive_duplicate_id_join_mutation +``` + +The test clones the prepared archive and replaces that clone's `actions` view with a deliberately wrong implementation. The mutation joins every tool result to every use sharing `(session_id, tool_id)` and omits ordinal ranks. + +For alpha's duplicated call ID: + +```text +2 uses x 2 results = 4 naive rows +2 ordinal pairs = 2 correct rows +``` + +The other three selected uses remain one row each, so the wrong total is 7 rather than 5. The test invokes the same real `ArchiveStore.query_actions` membership helper and requires it to raise. It then verifies the exact failure signal: + +```text +expected 5 independently planted action rows, got 7 +``` + +This is a behavioral mutation of the real SQL relation in an isolated archive, not a source-text assertion. Removing the duplicate shape, consulting production output for expected values, or weakening membership to a mere non-empty assertion would make this mutation witness stop proving the intended law. + +## Compatibility tests retained and inspected + +No existing test or helper was deleted. The affected cluster was inspected broadly: + +- `tests/unit/cli/test_query_expression.py`: 6,494 lines, 328 tests. It retains lexer/AST contracts, boolean precedence, quoting and escaped quotes, exact identity/ambiguity, terminal source and pipeline diagnostics, unsupported-shape diagnostics, field registry coverage, FTS/vector/lineage/sequence branches, JSON strictness, and CLI/daemon/MCP compatibility checks. +- `tests/unit/cli/test_query_exec_laws.py`: 3,552 lines, 66 tests. It retains daemon/local routing, structured and lexical paths, count/group forwarding, projection/output contracts, mutations, semantic search, cursor/list behavior, and error handling. Many cases use local fakes; they remain untouched. +- `tests/unit/storage/test_query_security.py`: parameter binding and query-security obligations. +- `tests/unit/cli/test_verb_cardinality.py`: full-set destructive guard, dry-run, and apply identity. +- `tests/unit/storage/test_archive_tiers_archive.py`: current exact-session action bound and C-03 provider-pipeline canary. +- `tests/unit/sources/test_parsers_codex.py`: structured function result exit-code/error mapping. +- `tests/infra/query_cases.py`, `tests/infra/surfaces.py`, and `tests/infra/semantic_facts.py`: existing cross-surface/shadow fact infrastructure retained for later certification. + +## Commands and results + +### Project-managed survivor run + +Command: + +```bash +POLYLOGUE_PYTEST_TMPFS=0 .venv/bin/python -m devtools test \ + tests/unit/cli/test_query_composition_laws.py -x +``` + +Result: + +```text +collected 2 items +2 passed in 6.80s +supervisor: ok (12.2s) +``` + +The explicit environment variable is a supported repository policy choice that disables the managed tmpfs default and uses the configured disk scratch area. + +### Focused compatibility run + +Command: + +```bash +.venv/bin/pytest -q \ + tests/unit/cli/test_query_composition_laws.py \ + tests/unit/storage/test_archive_tiers_archive.py::test_exact_session_action_count_bounds_pairing_before_global_ranking \ + tests/unit/storage/test_archive_tiers_archive.py::test_c03_exact_session_actions_uses_real_provider_pipeline_and_planted_facts \ + tests/unit/sources/test_parsers_codex.py::TestMessageParsing::test_function_call_output_captures_structured_exit_code \ + tests/unit/cli/test_query_expression.py \ + tests/unit/cli/test_query_exec_laws.py \ + tests/unit/storage/test_query_security.py \ + tests/unit/cli/test_verb_cardinality.py::TestDeleteCardinalityLargeNonMocked::test_guard_dry_run_and_deleted_sets_are_identical_and_unlimited +``` + +Result: + +```text +541 passed, 1 skipped in 26.19s +``` + +The skip was pre-existing in the selected suite; no test in the new module skipped. + +### Static checks + +Commands: + +```bash +.venv/bin/ruff check \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py + +.venv/bin/ruff format --check \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py + +.venv/bin/mypy \ + tests/infra/query_manifest_oracle.py \ + tests/unit/cli/test_query_composition_laws.py +``` + +Results: + +```text +All checks passed! +2 files already formatted +Success: no issues found in 2 source files +``` + +### Environmental refusal preserved honestly + +The first managed-run attempt used the default adaptive tmpfs policy. It stopped before pytest collection with status 125: + +```text +only 64 MiB free in /dev/shm; refusing disk-backed pytest +``` + +No test failed in that attempt. The managed run passed after setting `POLYLOGUE_PYTEST_TMPFS=0`. During environment preparation, dependency synchronization rewrote registry URLs in `uv.lock`; that unrelated generated delta was reverted and is not present in `PATCH.diff`. + +## Verification not performed + +- Full repository test suite. +- `devtools verify --quick`. +- Coverage-context or testmon affected-selection certification. +- Daemon HTTP or MCP parity using the new manifest. +- A live operator archive, daemon, browser, deployment, secrets, or NixOS environment. +- New SQLite VM-step or wall-time bounds under irrelevant archive growth; the existing C-03 work-bound tests were rerun instead. + +## Proposed dominated tests for later certification + +No deletion is included. The following remain candidates only after local dominance evidence, coverage contexts, and semantic mutation receipts exist: + +- mock-forwarding permutations in `tests/unit/cli/test_query_exec_laws.py` that are fully dominated by real route laws; +- repetitive one-field lowering examples in `tests/unit/cli/test_query_expression.py`, after a reviewed table retains every unique diagnostic, precedence, exact-ID, quoting, and security branch; +- shadow query fields in `tests/infra/query_cases.py` after all consumers use public expressions; +- the partial query translator in `tests/infra/surfaces.py` after a shared real-route manifest proves each stable surface; +- any synthetic fact duplication in `tests/infra/semantic_facts.py` that becomes redundant with provider-wire known-answer corpora. + +Required certification before deletion: + +1. Per-test coverage contexts for the candidate and survivor. +2. At least one semantic mutation killed by the survivor and formerly killed by the candidate. +3. A diagnostic/compatibility inventory proving no unique parser, security, serialization, cancellation, vector, lineage, or terminal-unit branch is lost. +4. Focused and full-suite green receipts in the operator environment. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/EVIDENCE.md new file mode 100644 index 000000000..d9194e6d4 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/EVIDENCE.md @@ -0,0 +1,179 @@ +# Evidence and design findings + +## Authority and snapshot reconstruction + +The supplied project-state archive is the code authority. Its manifest identifies: + +```text +source: /realm/project/polylogue +generated_at: 2026-07-17T043202Z +branch: master +commit: f654480cadb7cc4c194704e24dfd483199547b35 +dirty: true +``` + +The all-refs bundle resolves `master`, `origin/master`, and `origin/HEAD` to that commit. The branch-delta patch, file list, and log are empty. + +A clean detached worktree was created from the bundle and the supplied working-tree tar was overlaid. `git status --short --untracked-files=all` and `git diff --stat` were empty. A direct tree comparison found no differing common files; differences were packaging exclusions/ignored material (`.agent`, `.beads`, `.claude`, lockfiles omitted from the tar; `.benchmarks` and a browser-extension package lock present only in the tar). Therefore the manifest's dirty flag does not identify a recoverable tracked source patch. The apply base is the named commit. + +## Repository constraints + +`AGENTS.md` establishes the constraints used here: + +- semantics belong in the substrate/product route, not duplicated surface logic; +- the daemon owns writes and remains the sole SQLite writer; +- `ops.db` holds `convergence_debt` and other daemon operational state; +- `DaemonConverger` stages have path, batch, and session-scoped variants; +- session-scoped methods exist specifically to retry debt without resolving back to source paths; +- `false_means_pending` means bounded successful work can remain pending rather than becoming a failure; +- hot sources intentionally defer insights until quiet. + +The patch changes the daemon's existing production retry composition and uses the existing ledger/upsert. It does not add a second scheduler, repository, state machine, or lifecycle table. + +## Test Suite Diet decisions applied + +### Concurrent writes, publication, and resume + +`architecture/03-concurrent-writes-publication-and-resume.md` says cross-tier work is an observable idempotent saga; checkpoints mean effects are durably receipted; resume skips only proven effects and retries pending/unknown effects idempotently; convergence debt retains its own domain state; deterministic transaction/publication barriers are preferred over sleeps. + +Applied here: + +- debt identity is treated as the effect identity; +- the existing upsert advances attempts on the same row; +- success clears only the proven stage effect; +- retry deadlines are deterministic barriers; +- raw/source authority is checked not to change during derived retry; +- generic legacy debt is migrated to exact pending stages without deleting unrelated exact rows. + +### Capture delivery and deployed status + +`architecture/09-capture-delivery-and-deployed-status.md` requires production-route composition, durable/idempotent queue/receipt behavior, explicit residual debt, bounded cleanup, and no parallel harness registry. + +Applied here: + +- the fresh process imports the production daemon drain; +- the harness only seeds and observes existing production types; +- final public debt status is checked against the durable ledger; +- residual debt is explicit throughout and empty only at quiescence; +- the existing `WorkloadReceipt` records the declared phase/cleanup result; +- no claim is made about a service-supervisor termination receipt or deployed service graph. + +## Dossier and area packet + +The supplied `dossiers/convergence-restart-retry.md` names these obligations: + +- honest `false_means_pending` handling; +- debt survival across process restart; +- session-scoped retry that does not widen to irrelevant backlog; +- idempotence/no duplication; +- equality with uninterrupted final facts; +- status/alert agreement with debt and quiescence. + +It proposes exactly: + +- `tests/infra/convergence_harness.py` +- `tests/unit/daemon/test_convergence_restart_law.py` + +The area packet asks for real SQLite, explicit barriers, the sequence `source append -> writer commit -> bounded stage -> debt -> restart -> retry -> quiescence`, and mutation of debt persistence/session scope/retry. The delivered test realizes that first slice with the current partial-convergence workload type. + +## Beads + +### `polylogue-1xc.14` + +The shared `WorkloadEnvelopeSpec`/`WorkloadReceipt` contract is still open. Its design prohibits turning resource limits into semantic caps and requires explicit workload/build/archive/phase/cleanup evidence. + +The survivor uses the existing receipt model and does not claim complete adoption across all performance paths. + +### `polylogue-1xc.14.1` + +The workload-profile child is `in_progress`. Its current notes say a first implementation slice landed deterministic workload profiles, archive composition, typed workload artifacts, and partial-convergence canary substrate; production-route canaries and further profile work remain. + +The patch reuses `partial_convergence_canary_spec` and the typed writer. It does not invent a corpus generator or claim the Bead is complete. + +## Production route findings + +### `polylogue/daemon/convergence.py` + +- `ConvergenceStage` already defines `check_sessions` and `execute_sessions`. +- `false_means_pending=True` maps a false result to `StageState.PENDING` and preserves an honest error. +- `DaemonConverger.converge_sessions` can target exact session subjects. + +No new convergence state machine was needed. + +### `polylogue/daemon/convergence_stages.py` + +- The real insights stage defers active-growing source sessions until quiet. +- The real FTS stage supports exact session check/execute methods. +- Default stages carry stable names used in persisted debt rows. + +These facts make stage-scoped retry implementable by selecting the existing stage object. + +### `polylogue/sources/live/convergence_outcome.py` + +- Path convergence output is resolved to session ids once when debt is created. +- Session debt is then durable and can be retried without rereading/re-resolving the source. + +The retry patch preserves that intended boundary. + +### `polylogue/sources/live/cursor.py` and ops DDL/write route + +- `convergence_debt` has `UNIQUE(stage, target_type, target_id)` and a stable `debt_id`. +- `record_convergence_debt` uses `BEGIN IMMEDIATE` around read/modify/write. +- The SQL upsert explicitly preserves the existing `debt_id`, increments attempts, preserves `created_at_ms`, and updates the retry/error/status fields. +- Insights quiet deferral is stored as `status='deferred'`; other errors are `failed`. + +The old CLI drain bypassed the value of this upsert by deleting the row first. + +### `polylogue/daemon/convergence_debt_status.py` + +- Public status reads `ops.db`. +- Deferred rows do not count as failures. +- Failed counts and retry-due counts are projected from the durable rows. + +The survivor checks both the deferred and final/quiescent projections. + +### Defect in `polylogue/daemon/cli.py` + +At the named snapshot, `_drain_convergence_debt_once`: + +1. selected due rows; +2. collapsed all session ids and paths without grouping by `debt.stage`; +3. instantiated one `DaemonConverger` with every default stage; +4. ran all stages for every selected subject; +5. on success, cleared all debt for the subject; +6. on pending/failure, cleared all debt for the subject and recreated rows from the whole subject state. + +The persisted stage identity therefore did not control execution or cleanup. A due insights row could repair a non-due FTS stage, clear its future obligation, and replace its own durable identity. + +## History + +`git blame` shows the retry loop originated in `a73d69b758` (`perf(daemon): observe and retry live catch-up convergence (#1033)`) and accumulated session and FTS support later. The stage-agnostic grouping/clear behavior survived those additions. + +Relevant later architecture/history inspected: + +- `a952221cdcc4813ffcc4c9c18c4fd8981d5bbb2a`: session-scoped standing-query convergence and durable receipts. +- `36001d023b2cfe793cb19fdd7c42a87597356f48`: durable Sinex publication obligations and convergence barriers. +- `f0c1b489b84cd04aac840315e7e55fa23eb97e39`: trustworthy archive contract verification. +- `c20286459cf2c3d1e4c968a8584f13e7cd382ff2`: workload profiles and receipt-backed production-route artifacts. + +These commits reinforce exact identity, durable receipt, session scoping, and real-route anti-vacuity rather than a new generic lifecycle abstraction. + +## Mutation evidence + +The survivor and helper were copied onto a clean unmodified worktree at the named snapshot. The test failed after the first fresh-process hot retry: + +```text +assert deferred_again.debt_id == due_insights_debt.debt_id +``` + +The UUIDs differed. This is direct evidence that the old clear/recreate implementation loses durable debt identity. The delivered patch passes the same test and advances attempts from 1 to 2 on the same row. + +The survivor also contains independent assertions that the future FTS row and stale FTS materialization remain untouched during the insights retry. They guard the second half of the defect: stage widening and subject-wide cleanup. + +## Contradictions and boundaries + +- Manifest `dirty=true` versus clean reconstructed tracked tree: reported explicitly; no dirty source patch is fabricated. +- Dossier generated against older `git_head=21f78b4db2ba62ff44b5f16dfab96067bc249b4c` versus current snapshot `f654480cadb7cc4c194704e24dfd483199547b35`: current source and later Beads notes win; all named paths/types were re-resolved. +- Dossier readiness says `prepared-not-execution-grade`, while the current snapshot now contains the workload-profile/receipt first slice. The delivered implementation uses that current substrate but does not claim full program completion. +- `ops.db` is disposable, yet debt survives ordinary process restart when the file remains. The patch does not claim survival after operator reset/deletion. +- The production service graph/termination receipt work in architecture 09 is not present as a complete deployed proof here. Fresh interpreter restart is verified; live supervisor/systemd behavior is not. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/HANDOFF.md new file mode 100644 index 000000000..b50341cda --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/HANDOFF.md @@ -0,0 +1,257 @@ +# testdiet-02 — convergence restart and debt liveness handoff + +## Mission and result + +This package implements a survivor law for daemon convergence across bounded deferral, durable debt, process restart, stage-scoped retry, repeated convergence, and eventual quiescence. + +The investigation found a production composition defect in `polylogue.daemon.cli._drain_convergence_debt_once`: each persisted row already carried a stage identity, but the retry loop discarded that identity, ran every default convergence stage for the subject, cleared every debt row for that subject, and recreated only the stages still pending. That behavior could: + +- replay an unrelated derived stage during a retry; +- consume a future-dated debt row that was not selected for retry; +- replace a durable debt identity instead of advancing its attempt history; +- reset retry history after a still-pending result. + +`PATCH.diff` changes the production drain to run the recorded stage for the recorded subject, update that exact row after another defer/failure, and clear only that exact row after success. The historical generic `stage="convergence"` row remains an explicit all-stage migration fallback. No universal lifecycle table, alternate convergence state machine, or test-only production trace was added. + +A real-SQLite survivor now composes the typed archive writer, source-to-session resolution, `DaemonConverger`, the production insights and FTS stages, the `CursorStore` `ops.db` ledger, public debt status, the current partial-convergence workload declaration, and fresh Python interpreter processes. It proves that a hot insights retry preserves row identity and increments attempts without touching unrelated FTS debt, a later quiet retry materializes only the intended session, the independent FTS retry drains only its own row, repeated retry is a no-op, cleanup is complete, and the stable terminal facts equal an uninterrupted run. + +## Snapshot identity + +Patch base: + +- Source archive manifest: `polylogue-manifest.json` +- Manifest source: `/realm/project/polylogue` +- Manifest generated: `2026-07-17T043202Z` +- Manifest branch: `master` +- Commit: `f654480cadb7cc4c194704e24dfd483199547b35` +- Commit subject: `chore(beads): file archive-insight benchmark findings wofr/vhjs/r7p6/1wtm` +- Bundle refs: `master`, `origin/master`, and `origin/HEAD` all resolve to that commit +- Manifest dirty flag: `true` +- Recoverable tracked dirty patch: none + +The archive's branch-delta patch, changed-file list, and branch-only log are empty. Overlaying the supplied `polylogue-working-tree.tar.gz` onto a clean worktree at the named commit leaves `git status` clean and no tracked diff. A direct tree comparison found no differing common tracked files. The tar intentionally omits some repository-local files and contains ignored packaging/runtime material, so the manifest's `dirty=true` cannot be converted into a source patch. `PATCH.diff` therefore targets the named commit, not an invented dirty state. + +## Inspected evidence + +Repository instructions and architecture: + +- `AGENTS.md`: substrate-first semantics, daemon sole-writer rule, five-tier durability model, `ops.db` convergence ledger, `false_means_pending`, hot-file quiet deferral, and session-scoped retry methods. +- Test Suite Diet `architecture/03-concurrent-writes-publication-and-resume.md`: domain-specific receipt-backed sagas, observable intermediate states, exact effect identity, idempotent resume, and deterministic failpoints rather than sleeps. +- Test Suite Diet `architecture/09-capture-delivery-and-deployed-status.md`: production-route composition, durable/idempotent retry, explicit residual debt, bounded cleanup, and no parallel test-only service registry. +- Test Suite Diet `dossiers/convergence-restart-retry.md` and `areas/daemon-convergence.md`: the exact survivor obligations, planned paths, real-SQLite/frozen-barrier guidance, final-state equality, scoped retry, status agreement, and mutation requirement. + +Production source followed beyond the obvious files: + +- `polylogue/daemon/convergence.py` +- `polylogue/daemon/convergence_stages.py` +- `polylogue/daemon/convergence_standing_queries.py` +- `polylogue/daemon/cli.py` +- `polylogue/daemon/convergence_debt_status.py` +- `polylogue/sources/live/convergence_debt.py` +- `polylogue/sources/live/convergence_outcome.py` +- `polylogue/sources/live/convergence_debt_retry.py` +- `polylogue/sources/live/cursor.py` +- `polylogue/sources/live/batch_observability.py` +- `polylogue/storage/sqlite/archive_tiers/ops.py` +- `polylogue/storage/sqlite/archive_tiers/ops_write.py` +- `polylogue/storage/sqlite/archive_tiers/write.py` +- `polylogue/scenarios/workload.py` + +Tests and test infrastructure inspected: + +- `tests/unit/daemon/test_daemon_convergence.py` +- `tests/unit/daemon/test_convergence_stages.py` +- `tests/unit/daemon/test_convergence_final_state.py` +- `tests/unit/daemon/test_convergence_debt_alert.py` +- `tests/unit/daemon/test_daemon_cli.py` +- `tests/unit/daemon/test_daemon_status.py` +- `tests/unit/daemon/test_catch_up_observability.py` +- `tests/unit/sources/test_live_batch_support.py` +- `tests/unit/sources/test_live_catchup_planning.py` +- `tests/infra/frozen_clock.py` + +Beads inspected from the supplied export: + +- `polylogue-1xc.14`, “Declare workload envelopes and resource receipts once” (`open`) +- `polylogue-1xc.14.1`, “Derive archive-scale workload profiles from provider schemas” (`in_progress`) + +The later `polylogue-1xc.14.1` notes say the partial-convergence canary and workload-receipt substrate landed as a first slice, while production-route canaries remained open. The implementation reuses those current types and does not claim the parent Beads are closed. + +History inspected: + +- Function lineage for `_drain_convergence_debt_once`, including its origin in `a73d69b758` and later session/FTS additions. +- `c20286459` — workload profiles and receipt-backed production-route artifacts. +- `f0c1b489b` — archive contract verification repair. +- `36001d023` — durable publication convergence and receipt barriers. +- `a952221cd` — session-scoped standing-query convergence stage. +- Recent `polylogue/daemon/cli.py` and convergence-path history through the named snapshot. + +## Mechanism + +### Production retry selection + +The drain now builds the default stage tuple once, indexes it by `stage.name`, and groups due `source_path`/`session_id` debt by the persisted stage. For each group it creates a real `DaemonConverger` containing only that stage and executes the existing path- or session-scoped production method. Results are keyed by `(stage, subject_type, subject_id)` so two rows for the same session cannot share or overwrite each other's outcome. + +The generic legacy `convergence` stage is the only all-stage fallback. If it remains pending, only the generic row is removed and replaced with exact pending-stage rows; unrelated exact rows survive. + +### Durable row lifecycle + +For exact stage rows: + +- success clears only `(stage, subject_type, subject_id)`; +- defer/failure calls `CursorStore.record_convergence_debt` on that same identity; +- the existing SQL upsert preserves `debt_id` and `created_at_ms` while incrementing attempts when the retry is due; +- `materializer_version` is retained; +- an unavailable stage is recorded honestly on the same row instead of silently disappearing. + +FTS-surface debt uses the same exact-row clear/update rule. + +### Survivor and oracle + +`tests/infra/convergence_harness.py` is a narrow adapter around existing typed writers and real SQLite tables. It does not implement convergence decisions. It supplies: + +- two typed Codex archive sessions, one target and one unrelated; +- a sparse active-growing source variant using the production hot-source threshold; +- direct, deterministic retry-deadline barriers in the real `ops.db` row; +- real message-FTS staleness by deleting only the target session's FTS rows; +- raw-authority facts from `source.db`; +- a stable materialization oracle over profile, materialization receipts, work events, phases, threads, thread membership, and table cardinalities, excluding attempt-time timestamps. + +The survivor creates debt in the parent pytest process and invokes the real production drain in fresh Python interpreters. This is a real persistence/process boundary without a sleep or a fake daemon registry. + +## Decisions + +- Keep the domain-specific `convergence_debt` ledger; do not introduce a universal lifecycle table. +- Reuse `false_means_pending`, existing session-scoped stage methods, and the existing SQL upsert. +- Treat stage identity as part of retry identity, not metadata. +- Preserve generic `convergence` rows as a compatibility fallback rather than deleting support for them. +- Use `next_retry_at` updates and source size/mtime boundaries as deterministic barriers; do not sleep. +- Use a fresh interpreter for restart proof; do not claim a systemd/service-supervisor kill test. +- Use durable facts and an uninterrupted baseline as the oracle; do not add a production trace seam that mirrors the algorithm. +- Emit the existing `WorkloadReceipt` for the declared `partial_convergence_canary_spec`; do not create another receipt vocabulary. +- Leave all existing tests and helpers in place. + +## Changed files + +- `polylogue/daemon/cli.py` + - stage-scoped debt selection; + - exact-row success/failure/defer handling; + - preservation of materializer version; + - generic-row migration fallback. +- `tests/infra/convergence_harness.py` + - real-SQLite typed archive fixture; + - durable debt-row and retry-deadline probes; + - independent raw/materialized fact oracles; + - real FTS staleness and query probes. +- `tests/unit/daemon/test_convergence_restart_law.py` + - one cohesive restart/retry/quiescence survivor with process boundaries, public status, cleanup, baseline equivalence, and workload receipt. + +`FILES/` is intentionally omitted because the unified patch fully disambiguates all changes. + +## Acceptance matrix + +| Mission obligation | Evidence in patch | Result | +| --- | --- | --- | +| Bounded work may defer honestly | Real insights stage returns pending for a hot source; ops row is `deferred`; public failed count remains zero | Satisfied | +| Debt survives restart | Debt is written before a fresh Python process invokes the production drain | Satisfied | +| Retry addresses intended session/stage | Insights-only converger selection; future-dated FTS debt and stale FTS rows remain unchanged | Satisfied | +| No unrelated acquisition replay | Session-scoped retry uses the persisted session id; raw source facts remain unchanged; unrelated session remains unmaterialized | Satisfied | +| Retry count and debt identity survive | Same `debt_id`/`created_at_ms`, attempts advance from 1 to 2 after a hot retry | Satisfied | +| Consume debt only after success | Hot retry retains exact row; quiet retry clears it after materialization | Satisfied | +| Repeated convergence is idempotent | Final fresh-process drain returns zero and stable facts do not change | Satisfied | +| Same terminal facts as uninterrupted | Stable insight/profile/event/thread oracle equals a separately seeded uninterrupted archive | Satisfied | +| No duplication | Stable facts and table counts remain identical after FTS retry and repeated drain | Satisfied | +| Public status and cleanup agree | Final ledger empty, failed/retry-due counts zero, quiescent receipt has cleanup complete | Satisfied | +| Deterministic test control | Direct retry deadlines plus sparse-size/quiet boundary; no sleep/polling | Satisfied | +| Real production route | Typed writer, real stage factories, real `DaemonConverger`, real `CursorStore`, real CLI drain, real SQLite | Satisfied | +| Mutation sensitivity | Unmodified snapshot fails on changed debt UUID after first hot retry | Satisfied | +| No parallel lifecycle framework | Existing ledger and workload receipt reused | Satisfied | + +## Proposed dominated tests — not deleted + +These are review candidates only, pending independent local certification and coverage evidence: + +- `tests/unit/daemon/test_daemon_cli.py::test_drain_convergence_debt_retries_due_items_without_source_failure` +- `tests/unit/daemon/test_daemon_cli.py::test_drain_convergence_debt_retries_session_subjects_without_source_lookup` +- `tests/unit/daemon/test_convergence_stages.py::test_profile_canary_defers_hot_session_then_converges_when_quiet` + +The survivor dominates their broad happy-path temporal story, but the first still isolates source-path handling, the second is a fast session-method diagnostic, and the third is a direct stage/receipt diagnostic. No deletion is included. + +`tests/unit/daemon/test_convergence_final_state.py` is not proposed for deletion: it exercises live-ingest composition, although its legacy debt-table assertion deserves a separate cleanup review. + +## Apply order + +From a clean checkout at `f654480cadb7cc4c194704e24dfd483199547b35`: + +```bash +git apply --check PATCH.diff +git apply PATCH.diff +python -m pytest -q tests/unit/daemon/test_convergence_restart_law.py +``` + +Then run the broader local gate appropriate to the operator's environment. + +## Exact verification performed + +Successful checks: + +```text +ruff format/check on all three changed files + result: clean; one file was formatted before final patch generation + +python -m py_compile on all three changed files + result: pass + +mypy polylogue/daemon/cli.py tests/infra/convergence_harness.py tests/unit/daemon/test_convergence_restart_law.py + result: Success: no issues found in 3 source files + +pytest -q tests/unit/daemon/test_convergence_restart_law.py -vv + result: 1 passed in 8.29s + +pytest -q tests/unit/daemon/test_daemon_cli.py tests/unit/daemon/test_convergence_stages.py tests/unit/daemon/test_convergence_restart_law.py + result: 126 passed in 15.65s + +pytest survivor with --randomly-seed=101, 202, 303 + result: 1 passed in 7.75s; 1 passed in 8.42s; 1 passed in 7.98s + +pytest -q test_catch_up_observability.py test_convergence_debt_alert.py test_live_batch_support.py test_live_catchup_planning.py + result: 106 passed in 22.65s + +git diff --check + result: pass + +git apply --check PATCH.diff in a separate clean worktree at the named commit + result: pass + +apply PATCH.diff in that clean worktree, then run the survivor through the shared venv with that worktree first on PYTHONPATH + result: 1 passed in 8.16s + +anti-vacuity run: copy the survivor/harness onto the unmodified snapshot and run it + result: expected failure, exit 1; 1 failed in 3.18s + first failure: debt_id changed after the first still-hot retry +``` + +Supplemental check with known baseline failures: + +```text +pytest over catch-up/status/debt/source planning set + result: 158 passed, 2 failed + +The same two failures reproduce on the unmodified clean snapshot: +- test_build_daemon_status_detects_broken_append_head_blocks_converged +- test_daemon_and_direct_claim_guard_share_mixed_frontier_summary +``` + +The repository's `.venv/bin/python -m devtools test tests/unit/daemon/test_convergence_restart_law.py` wrapper was attempted but refused to run because the container exposes only 64 MiB at `/dev/shm`; it exited 125 before pytest. Direct pytest execution is the verified result. + +## Risks and remaining verification + +- The process-restart proof uses fresh Python interpreters, not the operator's live daemon, systemd unit, NixOS deployment, watcher process, or signal/kill lifecycle. Those checks are unverified. +- The complete repository suite, Nix build, packaging gate, pre-push gate, and `devtools verify --quick` were not run. The focused and adjacent suites above are verified. +- The manifest advertises a dirty source, but no tracked dirty patch is recoverable. Any operator-only change excluded from the archive is necessarily unverified. +- Legacy generic `stage="convergence"` rows intentionally retain all-stage behavior because they carry no narrower stage identity. Exact rows are scoped. +- The survivor covers insights and FTS stage coexistence. It does not independently repeat the law for embeddings, standing queries, or Sinex publication barriers. +- `ops.db` is explicitly disposable in current architecture. This patch proves restart survival when that file survives process restart; it does not redefine tier durability across operator deletion/reset. +- The two unrelated daemon-status failures are baseline defects, not fixed or masked here. + +A further iteration is likely a **small repair** if CI exposes formatting, platform, or test-isolation differences. A **substantial second pass** would be justified only to add a real service-supervisor kill/restart witness, extend the same law across embedding/standing-query/Sinex stages, or reconcile the two existing status-frontier failures. Those extensions would add meaningful coverage but are not required to make this patch coherent or apply-ready. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/PATCH.diff new file mode 100644 index 000000000..7ac8b0286 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/PATCH.diff @@ -0,0 +1,917 @@ +diff --git a/polylogue/daemon/cli.py b/polylogue/daemon/cli.py +index 9796eab28..4f770bed5 100644 +--- a/polylogue/daemon/cli.py ++++ b/polylogue/daemon/cli.py +@@ -817,7 +817,13 @@ def _drain_session_insights_once(*, limit: int = _SESSION_INSIGHT_CONVERGENCE_BA + + + def _drain_convergence_debt_once(db: Path, *, limit: int = 100) -> int: +- """Retry due derived convergence debt without rereading source payloads.""" ++ """Retry due derived convergence debt without rereading source payloads. ++ ++ Debt identity is stage-scoped. A retry therefore runs only the recorded ++ stage for the recorded subject and updates that same ops-ledger row on a ++ further deferral/failure. The legacy ``convergence`` stage remains an ++ all-stage fallback for older generic rows. ++ """ + from polylogue.daemon.convergence import DaemonConverger + from polylogue.daemon.convergence_stages import make_default_convergence_stages + from polylogue.sources.live.cursor import CursorStore +@@ -831,60 +837,109 @@ def _drain_convergence_debt_once(db: Path, *, limit: int = 100) -> int: + ] + if not due_debt: + return 0 +- session_ids = tuple(dict.fromkeys(debt.subject_id for debt in due_debt if debt.subject_type == "session_id")) +- paths = tuple(dict.fromkeys([Path(debt.subject_id) for debt in due_debt if debt.subject_type == "source_path"])) ++ + fts_surfaces = tuple(dict.fromkeys(debt.subject_id for debt in due_debt if debt.subject_type == "fts_surface")) +- if not paths and not session_ids and not fts_surfaces: +- return 0 + fts_surface_results = _drain_fts_surface_debt(db, fts_surfaces) +- converger = DaemonConverger(stages=make_default_convergence_stages(db), max_workers=2) +- path_states, _path_timings = converger.converge_batch(paths) +- session_states, _session_timings = converger.converge_sessions(session_ids) ++ ++ subject_states: dict[tuple[str, str, str], object] = {} ++ retryable_debt = tuple(debt for debt in due_debt if debt.subject_type in {"source_path", "session_id"}) ++ if retryable_debt: ++ default_stages = make_default_convergence_stages(db) ++ stages_by_name = {stage.name: stage for stage in default_stages} ++ for stage_name in dict.fromkeys(debt.stage for debt in retryable_debt): ++ selected_stages = ( ++ default_stages ++ if stage_name == "convergence" ++ else (stages_by_name[stage_name],) ++ if stage_name in stages_by_name ++ else () ++ ) ++ if not selected_stages: ++ continue ++ stage_debt = tuple(debt for debt in retryable_debt if debt.stage == stage_name) ++ paths = tuple( ++ dict.fromkeys(Path(debt.subject_id) for debt in stage_debt if debt.subject_type == "source_path") ++ ) ++ session_ids = tuple( ++ dict.fromkeys(debt.subject_id for debt in stage_debt if debt.subject_type == "session_id") ++ ) ++ converger = DaemonConverger(stages=selected_stages, max_workers=2) ++ path_states, _path_timings = converger.converge_batch(paths) ++ session_states, _session_timings = converger.converge_sessions(session_ids) ++ subject_states.update( ++ ((stage_name, "source_path", str(path)), state) for path, state in path_states.items() ++ ) ++ subject_states.update( ++ ((stage_name, "session_id", session_id), state) for session_id, state in session_states.items() ++ ) ++ + retried = 0 + for debt in due_debt: +- subject_states: list[object | None] +- if debt.subject_type == "session_id": +- subject_states = [session_states.get(debt.subject_id)] +- elif debt.subject_type == "fts_surface": ++ retried += 1 ++ if debt.subject_type == "fts_surface": + if fts_surface_results.get(debt.subject_id) is True: +- cursor.clear_convergence_debt(subject_type=debt.subject_type, subject_id=debt.subject_id) +- retried += 1 ++ cursor.clear_convergence_debt( ++ stage=debt.stage, ++ subject_type=debt.subject_type, ++ subject_id=debt.subject_id, ++ ) + continue +- cursor.clear_convergence_debt(subject_type=debt.subject_type, subject_id=debt.subject_id) + cursor.record_convergence_debt( + stage=debt.stage, + subject_type=debt.subject_type, + subject_id=debt.subject_id, + error="FTS freshness convergence did not converge", ++ materializer_version=debt.materializer_version, + ) +- retried += 1 + continue +- else: +- subject_states = [path_states.get(Path(debt.subject_id))] +- converged = all(state is not None and bool(getattr(state, "converged", False)) for state in subject_states) +- if converged: +- cursor.clear_convergence_debt(subject_type=debt.subject_type, subject_id=debt.subject_id) +- retried += 1 ++ ++ state = subject_states.get((debt.stage, debt.subject_type, debt.subject_id)) ++ if state is None: ++ cursor.record_convergence_debt( ++ stage=debt.stage, ++ subject_type=debt.subject_type, ++ subject_id=debt.subject_id, ++ error=f"convergence retry stage unavailable: {debt.stage}", ++ materializer_version=debt.materializer_version, ++ ) ++ continue ++ if bool(getattr(state, "converged", False)): ++ cursor.clear_convergence_debt( ++ stage=debt.stage, ++ subject_type=debt.subject_type, ++ subject_id=debt.subject_id, ++ ) ++ continue ++ ++ last_error = getattr(state, "last_error", None) ++ retry_error = last_error if isinstance(last_error, str) and last_error else "retry did not converge" ++ if debt.stage != "convergence": ++ cursor.record_convergence_debt( ++ stage=debt.stage, ++ subject_type=debt.subject_type, ++ subject_id=debt.subject_id, ++ error=retry_error, ++ materializer_version=debt.materializer_version, ++ ) + continue +- failed_stages: tuple[str, ...] = () +- last_error: object = None +- for state in subject_states: +- stages = getattr(state, "stages", {}) if state is not None else {} +- last_error = getattr(state, "last_error", None) if state is not None else None +- failed_stages = _failed_convergence_stage_names(stages) +- if failed_stages: +- break +- if not failed_stages: +- failed_stages = ("convergence",) +- cursor.clear_convergence_debt(subject_type=debt.subject_type, subject_id=debt.subject_id) ++ ++ # Generic rows predate stage-scoped retry identity. Preserve the old ++ # migration behavior by replacing only that generic row with the exact ++ # stages that remain pending; other stage rows for the subject survive. ++ failed_stages = _failed_convergence_stage_names(getattr(state, "stages", {})) or ("convergence",) ++ cursor.clear_convergence_debt( ++ stage=debt.stage, ++ subject_type=debt.subject_type, ++ subject_id=debt.subject_id, ++ ) + for stage in failed_stages: + cursor.record_convergence_debt( + stage=stage, + subject_type=debt.subject_type, + subject_id=debt.subject_id, +- error=last_error if isinstance(last_error, str) and last_error else "retry did not converge", ++ error=retry_error, ++ materializer_version=debt.materializer_version, + ) +- retried += 1 + return retried + + +diff --git a/tests/infra/convergence_harness.py b/tests/infra/convergence_harness.py +new file mode 100644 +index 000000000..316a3a27f +--- /dev/null ++++ b/tests/infra/convergence_harness.py +@@ -0,0 +1,396 @@ ++"""Real-SQLite fixtures and independent facts for convergence survivor tests. ++ ++This module adapts the production archive writers, daemon stages, and ops ++ledger. It deliberately owns no alternate convergence state machine. ++""" ++ ++from __future__ import annotations ++ ++import hashlib ++import sqlite3 ++from dataclasses import dataclass ++from pathlib import Path ++from typing import cast ++ ++import polylogue.daemon.convergence_stages as convergence_stages ++from polylogue.archive.message.roles import Role ++from polylogue.core.enums import BlockType, Provider ++from polylogue.scenarios import WorkloadEnvelopeSpec, partial_convergence_canary_spec ++from polylogue.sources.parsers.base import ParsedContentBlock, ParsedMessage, ParsedSession ++from polylogue.storage.sqlite.archive_tiers.bootstrap import initialize_archive_tier ++from polylogue.storage.sqlite.archive_tiers.types import ArchiveTier ++from polylogue.storage.sqlite.archive_tiers.write import write_parsed_session_to_archive ++from polylogue.storage.sqlite.connection import open_connection ++ ++SqlValue = str | int | float | bytes | None ++FactRow = tuple[SqlValue, ...] ++ ++ ++@dataclass(frozen=True, slots=True) ++class PartialConvergenceArchive: ++ root: Path ++ index_db: Path ++ source_db: Path ++ ops_db: Path ++ target_source: Path ++ unrelated_source: Path ++ target_session_id: str ++ unrelated_session_id: str ++ workload_spec: WorkloadEnvelopeSpec ++ ++ def make_target_quiet(self) -> None: ++ truncate_sparse(self.target_source, 1_024) ++ ++ ++@dataclass(frozen=True, slots=True) ++class DebtLedgerRow: ++ debt_id: str ++ stage: str ++ subject_type: str ++ subject_id: str ++ status: str ++ attempts: int ++ last_error: str | None ++ next_retry_at: str | None ++ materializer_version: str | None ++ created_at_ms: int ++ updated_at_ms: int ++ ++ ++@dataclass(frozen=True, slots=True) ++class SessionMaterializationFacts: ++ """Stable terminal facts, excluding attempt-time materialization stamps.""" ++ ++ profile: FactRow | None ++ materializations: tuple[FactRow, ...] ++ work_events: tuple[FactRow, ...] ++ phases: tuple[FactRow, ...] ++ threads: tuple[FactRow, ...] ++ thread_sessions: tuple[FactRow, ...] ++ table_counts: tuple[tuple[str, int], ...] ++ ++ ++def seed_partial_convergence_archive(root: Path, *, target_hot: bool) -> PartialConvergenceArchive: ++ """Seed the current partial-convergence workload through typed archive writes.""" ++ root.mkdir(parents=True, exist_ok=True) ++ index_db = root / "index.db" ++ source_db = root / "source.db" ++ ops_db = root / "ops.db" ++ target_source = root / "profile-growing-codex.jsonl" ++ unrelated_source = root / "unrelated-codex.jsonl" ++ target_size = convergence_stages._HOT_INSIGHT_SOURCE_BYTES + 1 if target_hot else 1_024 ++ truncate_sparse(target_source, target_size) ++ truncate_sparse(unrelated_source, 1_024) ++ ++ with open_connection(index_db) as conn: ++ target_session_id = _seed_raw_source_session( ++ conn, ++ session_id="convergence-survivor", ++ source_path=target_source, ++ ) ++ unrelated_session_id = _seed_raw_source_session( ++ conn, ++ session_id="convergence-unrelated", ++ source_path=unrelated_source, ++ ) ++ conn.commit() ++ ++ return PartialConvergenceArchive( ++ root=root, ++ index_db=index_db, ++ source_db=source_db, ++ ops_db=ops_db, ++ target_source=target_source, ++ unrelated_source=unrelated_source, ++ target_session_id=target_session_id, ++ unrelated_session_id=unrelated_session_id, ++ workload_spec=partial_convergence_canary_spec( ++ profile_id="workload-profile:testdiet-02-partial-convergence", ++ archive_id="archive:testdiet-02-partial-convergence", ++ ), ++ ) ++ ++ ++def truncate_sparse(path: Path, size: int) -> None: ++ path.parent.mkdir(parents=True, exist_ok=True) ++ with path.open("wb") as handle: ++ handle.truncate(size) ++ ++ ++def debt_ledger_row( ++ ops_db: Path, ++ *, ++ stage: str, ++ subject_type: str, ++ subject_id: str, ++) -> DebtLedgerRow | None: ++ with sqlite3.connect(ops_db) as conn: ++ row = conn.execute( ++ """ ++ SELECT debt_id, stage, target_type, target_id, status, attempts, ++ last_error, next_retry_at, materializer_version, ++ created_at_ms, updated_at_ms ++ FROM convergence_debt ++ WHERE stage = ? AND target_type = ? AND target_id = ? ++ """, ++ (stage, subject_type, subject_id), ++ ).fetchone() ++ if row is None: ++ return None ++ return DebtLedgerRow( ++ debt_id=str(row[0]), ++ stage=str(row[1]), ++ subject_type=str(row[2]), ++ subject_id=str(row[3]), ++ status=str(row[4]), ++ attempts=int(row[5]), ++ last_error=None if row[6] is None else str(row[6]), ++ next_retry_at=None if row[7] is None else str(row[7]), ++ materializer_version=None if row[8] is None else str(row[8]), ++ created_at_ms=int(row[9]), ++ updated_at_ms=int(row[10]), ++ ) ++ ++ ++def set_debt_retry_at( ++ ops_db: Path, ++ *, ++ stage: str, ++ subject_type: str, ++ subject_id: str, ++ retry_at: str, ++) -> None: ++ with sqlite3.connect(ops_db) as conn: ++ cursor = conn.execute( ++ """ ++ UPDATE convergence_debt ++ SET next_retry_at = ? ++ WHERE stage = ? AND target_type = ? AND target_id = ? ++ """, ++ (retry_at, stage, subject_type, subject_id), ++ ) ++ conn.commit() ++ if cursor.rowcount != 1: ++ raise AssertionError(f"expected one convergence debt row, updated {cursor.rowcount}") ++ ++ ++def make_messages_fts_stale(index_db: Path, *, session_id: str) -> int: ++ """Delete only this session's real FTS rows to create unrelated stage debt.""" ++ with sqlite3.connect(index_db) as conn: ++ row_ids = [ ++ int(row[0]) ++ for row in conn.execute( ++ "SELECT rowid FROM blocks WHERE session_id = ? ORDER BY rowid", ++ (session_id,), ++ ).fetchall() ++ ] ++ conn.executemany("DELETE FROM messages_fts WHERE rowid = ?", ((row_id,) for row_id in row_ids)) ++ conn.commit() ++ if not row_ids: ++ raise AssertionError(f"session {session_id!r} has no indexed blocks") ++ return len(row_ids) ++ ++ ++def messages_fts_match_count(index_db: Path, query: str) -> int: ++ with sqlite3.connect(index_db) as conn: ++ row = conn.execute( ++ "SELECT COUNT(*) FROM messages_fts WHERE messages_fts MATCH ?", ++ (query,), ++ ).fetchone() ++ return 0 if row is None else int(row[0]) ++ ++ ++def raw_authority_facts(source_db: Path) -> tuple[FactRow, ...]: ++ with sqlite3.connect(source_db) as conn: ++ rows = conn.execute( ++ """ ++ SELECT raw_id, origin, native_id, source_path, hex(blob_hash), ++ blob_size, acquired_at_ms ++ FROM raw_sessions ++ ORDER BY raw_id ++ """ ++ ).fetchall() ++ return _fact_rows(rows) ++ ++ ++def session_materialization_facts(index_db: Path, *, session_id: str) -> SessionMaterializationFacts: ++ with sqlite3.connect(index_db) as conn: ++ profile_row = conn.execute( ++ """ ++ SELECT session_id, logical_session_id, materializer_version, ++ source_updated_at, source_sort_key, input_high_water_mark, ++ input_high_water_mark_source, input_row_count, source_name, ++ title, message_count, work_event_count, phase_count, ++ word_count, tool_use_count, thinking_count, total_cost_usd, ++ total_duration_ms, workflow_shape, terminal_state, ++ total_input_tokens, total_output_tokens, ++ evidence_payload_json, inference_payload_json, ++ enrichment_payload_json ++ FROM session_profiles ++ WHERE session_id = ? ++ """, ++ (session_id,), ++ ).fetchone() ++ materializations = conn.execute( ++ """ ++ SELECT insight_type, session_id, materializer_version, ++ source_updated_at_ms, source_sort_key_ms, ++ input_high_water_mark_ms, input_high_water_mark_source, ++ input_row_count ++ FROM insight_materialization ++ WHERE session_id = ? ++ ORDER BY insight_type ++ """, ++ (session_id,), ++ ).fetchall() ++ work_events = conn.execute( ++ """ ++ SELECT session_id, position, work_event_type, summary, confidence, ++ start_index, end_index, started_at_ms, ended_at_ms, ++ duration_ms, file_paths_json, tools_used_json, ++ input_high_water_mark, input_high_water_mark_source, ++ evidence_json, inference_json, search_text ++ FROM session_work_events ++ WHERE session_id = ? ++ ORDER BY position ++ """, ++ (session_id,), ++ ).fetchall() ++ phases = conn.execute( ++ """ ++ SELECT session_id, position, start_index, end_index, ++ started_at_ms, ended_at_ms, duration_ms, ++ tool_counts_json, word_count, ++ input_high_water_mark, input_high_water_mark_source, ++ evidence_json, inference_json, search_text ++ FROM session_phases ++ WHERE session_id = ? ++ ORDER BY position ++ """, ++ (session_id,), ++ ).fetchall() ++ threads = conn.execute( ++ """ ++ SELECT t.thread_id, t.dominant_repo_id, t.materializer_version, ++ t.source_updated_at, t.input_high_water_mark, ++ t.input_high_water_mark_source, t.input_row_count, ++ t.start_time, t.end_time, t.dominant_repo, ++ t.session_ids_json, t.session_count, t.depth, t.branch_count, ++ t.total_messages, t.total_cost_usd, t.wall_duration_ms, ++ t.work_event_breakdown_json, t.payload_json, t.search_text ++ FROM threads AS t ++ JOIN thread_sessions AS ts ON ts.thread_id = t.thread_id ++ WHERE ts.session_id = ? ++ ORDER BY t.thread_id ++ """, ++ (session_id,), ++ ).fetchall() ++ thread_sessions = conn.execute( ++ """ ++ SELECT thread_id, session_id, position ++ FROM thread_sessions ++ WHERE session_id = ? ++ ORDER BY thread_id, position ++ """, ++ (session_id,), ++ ).fetchall() ++ count_tables = ( ++ "sessions", ++ "messages", ++ "blocks", ++ "session_profiles", ++ "insight_materialization", ++ "session_work_events", ++ "session_phases", ++ "threads", ++ "thread_sessions", ++ ) ++ table_counts = tuple( ++ (table, int(conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0])) for table in count_tables ++ ) ++ return SessionMaterializationFacts( ++ profile=None if profile_row is None else cast(FactRow, tuple(profile_row)), ++ materializations=_fact_rows(materializations), ++ work_events=_fact_rows(work_events), ++ phases=_fact_rows(phases), ++ threads=_fact_rows(threads), ++ thread_sessions=_fact_rows(thread_sessions), ++ table_counts=table_counts, ++ ) ++ ++ ++def _seed_raw_source_session(conn: sqlite3.Connection, *, session_id: str, source_path: Path) -> str: ++ raw_id = f"raw-{session_id}" ++ source_db = _main_db_path(conn).with_name("source.db") ++ with sqlite3.connect(source_db) as source_conn: ++ initialize_archive_tier(source_conn, ArchiveTier.SOURCE) ++ source_conn.execute( ++ """ ++ INSERT OR REPLACE INTO raw_sessions ( ++ raw_id, origin, native_id, source_path, blob_hash, blob_size, ++ acquired_at_ms ++ ) VALUES (?, ?, ?, ?, ?, ?, ?) ++ """, ++ ( ++ raw_id, ++ "codex-session", ++ session_id, ++ str(source_path), ++ hashlib.sha256(f"raw:{session_id}".encode()).digest(), ++ source_path.stat().st_size, ++ 1_769_000_000_000, ++ ), ++ ) ++ source_conn.commit() ++ return write_parsed_session_to_archive( ++ conn, ++ ParsedSession( ++ source_name=Provider.CODEX, ++ provider_session_id=session_id, ++ title=session_id, ++ created_at="2026-05-24T01:00:00+00:00", ++ updated_at="2026-05-24T01:00:00+00:00", ++ messages=[ ++ ParsedMessage( ++ provider_message_id="msg-1", ++ role=Role.normalize("user"), ++ text=f"Message for {session_id}", ++ position=0, ++ blocks=[ ++ ParsedContentBlock( ++ type=BlockType.TEXT, ++ text=f"Message for {session_id}", ++ ) ++ ], ++ ) ++ ], ++ ), ++ raw_id=raw_id, ++ content_hash=hashlib.sha256(f"session:{session_id}".encode()).hexdigest(), ++ ) ++ ++ ++def _main_db_path(conn: sqlite3.Connection) -> Path: ++ row = conn.execute("PRAGMA database_list").fetchone() ++ if row is None or not row[2]: ++ raise AssertionError("archive index connection has no main database path") ++ return Path(str(row[2])) ++ ++ ++def _fact_rows(rows: list[tuple[object, ...]]) -> tuple[FactRow, ...]: ++ return tuple(cast(FactRow, tuple(row)) for row in rows) ++ ++ ++__all__ = [ ++ "DebtLedgerRow", ++ "PartialConvergenceArchive", ++ "SessionMaterializationFacts", ++ "debt_ledger_row", ++ "make_messages_fts_stale", ++ "messages_fts_match_count", ++ "raw_authority_facts", ++ "seed_partial_convergence_archive", ++ "session_materialization_facts", ++ "set_debt_retry_at", ++ "truncate_sparse", ++] +diff --git a/tests/unit/daemon/test_convergence_restart_law.py b/tests/unit/daemon/test_convergence_restart_law.py +new file mode 100644 +index 000000000..7f2b66d74 +--- /dev/null ++++ b/tests/unit/daemon/test_convergence_restart_law.py +@@ -0,0 +1,346 @@ ++"""Survivor law for convergence interruption, restart, retry, and quiescence.""" ++ ++from __future__ import annotations ++ ++import os ++import subprocess ++import sys ++from pathlib import Path ++ ++import pytest ++ ++from polylogue.daemon.convergence import DaemonConverger, StageState ++from polylogue.daemon.convergence_debt_status import convergence_debt_summary_info ++from polylogue.daemon.convergence_stages import make_fts_stage, make_insights_stage ++from polylogue.scenarios import WorkloadPhaseObservation, WorkloadReceipt, WorkloadRunStatus ++from polylogue.sources.live.convergence_debt import convergence_debt_from_states ++from polylogue.sources.live.convergence_outcome import record_convergence_outcome ++from polylogue.sources.live.cursor import CursorStore ++from tests.infra.convergence_harness import ( ++ debt_ledger_row, ++ make_messages_fts_stale, ++ messages_fts_match_count, ++ raw_authority_facts, ++ seed_partial_convergence_archive, ++ session_materialization_facts, ++ set_debt_retry_at, ++) ++ ++_DUE_RETRY_AT = "1970-01-01T00:00:00+00:00" ++_NOT_DUE_RETRY_AT = "9999-01-01T00:00:00+00:00" ++_FUTURE_MTIME = 4_102_444_800 ++ ++ ++def _run_retry_in_fresh_process(index_db: Path) -> int: ++ """Execute the production debt drain across a genuine interpreter restart.""" ++ repo_root = Path(__file__).resolve().parents[3] ++ env = os.environ.copy() ++ existing_pythonpath = env.get("PYTHONPATH") ++ env["PYTHONPATH"] = str(repo_root) if not existing_pythonpath else f"{repo_root}{os.pathsep}{existing_pythonpath}" ++ script = ( ++ "from pathlib import Path\n" ++ "from polylogue.daemon.cli import _drain_convergence_debt_once\n" ++ f"print('RETRIED=' + str(_drain_convergence_debt_once(Path({str(index_db)!r}))))\n" ++ ) ++ completed = subprocess.run( ++ [sys.executable, "-c", script], ++ cwd=repo_root, ++ env=env, ++ capture_output=True, ++ text=True, ++ timeout=60, ++ check=False, ++ ) ++ assert completed.returncode == 0, ( ++ f"fresh-process convergence retry failed\nstdout:\n{completed.stdout}\nstderr:\n{completed.stderr}" ++ ) ++ for line in reversed(completed.stdout.splitlines()): ++ if line.startswith("RETRIED="): ++ return int(line.removeprefix("RETRIED=")) ++ raise AssertionError(f"fresh-process convergence retry emitted no result marker: {completed.stdout!r}") ++ ++ ++@pytest.mark.contract ++@pytest.mark.timeout(90) ++def test_convergence_debt_survives_restart_and_reaches_one_terminal_fact_set(tmp_path: Path) -> None: ++ """The real daemon route preserves exact debt until its own stage succeeds. ++ ++ Production dependencies exercised: the typed archive writer, source-to-session ++ resolver, ``DaemonConverger`` false-means-pending contract, insights and FTS ++ stage implementations, ``CursorStore`` ops.db ledger, public debt status, ++ and ``_drain_convergence_debt_once`` in fresh Python processes. ++ ++ Anti-vacuity mutations: deleting debt before stage success loses the hot ++ retry row; deleting/recreating it changes ``debt_id`` or resets attempts; ++ ignoring ``debt.stage`` repairs the deliberately stale FTS rows during an ++ insights retry; skipping retry leaves the profile absent; resolving the ++ wrong source materializes the unrelated session. ++ """ ++ recovered = seed_partial_convergence_archive(tmp_path / "recovered", target_hot=True) ++ os.utime(recovered.target_source, (_FUTURE_MTIME, _FUTURE_MTIME)) ++ raw_facts_before = raw_authority_facts(recovered.source_db) ++ ++ # Bounded work is allowed to defer, but the exact stage/session obligation ++ # must be written durably before this process ends. ++ insights = make_insights_stage(recovered.index_db) ++ initial_states, _timings = DaemonConverger((insights,)).converge_batch((recovered.target_source,)) ++ initial_state = initial_states[recovered.target_source] ++ assert initial_state.stages == {"insights": StageState.PENDING} ++ assert initial_state.last_error == "insights deferred until source quiet" ++ record_convergence_outcome( ++ CursorStore(recovered.index_db), ++ recovered.target_source, ++ convergence_debt_from_states((recovered.target_source,), initial_states), ++ archive_root=recovered.root, ++ ) ++ ++ initial_insights_debt = debt_ledger_row( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ assert initial_insights_debt is not None ++ assert initial_insights_debt.status == "deferred" ++ assert initial_insights_debt.attempts == 1 ++ assert convergence_debt_summary_info(recovered.index_db).failed_count == 0 ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ).profile ++ is None ++ ) ++ ++ # Add unrelated real FTS debt for the same session. Its future deadline is ++ # a deterministic barrier: an insights retry must not execute, consume, or ++ # rewrite this other stage's obligation. ++ deleted_fts_rows = make_messages_fts_stale( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ) ++ fts = make_fts_stage(recovered.index_db) ++ assert fts.check_sessions is not None ++ assert fts.check_sessions((recovered.target_session_id,)) == {recovered.target_session_id} ++ cursor = CursorStore(recovered.index_db) ++ cursor.record_convergence_debt( ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ error="deliberate unrelated FTS backlog", ++ ) ++ set_debt_retry_at( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ retry_at=_NOT_DUE_RETRY_AT, ++ ) ++ unrelated_fts_debt = debt_ledger_row( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ assert unrelated_fts_debt is not None ++ assert unrelated_fts_debt.status == "failed" ++ assert messages_fts_match_count(recovered.index_db, "Message") == 1 ++ ++ status_with_fts_debt = convergence_debt_summary_info(recovered.index_db) ++ assert status_with_fts_debt.failed_count == 1 ++ assert status_with_fts_debt.retry_due_count == 0 ++ assert [(item.stage, item.failed_count) for item in status_with_fts_debt.stage_summaries] == [("fts", 1)] ++ ++ # First restart: the source is still hot. Retrying must update the same ++ # insights row in place and leave both FTS materialization and FTS debt ++ # untouched. ++ set_debt_retry_at( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ retry_at=_DUE_RETRY_AT, ++ ) ++ due_insights_debt = debt_ledger_row( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ assert due_insights_debt is not None ++ assert _run_retry_in_fresh_process(recovered.index_db) == 1 ++ ++ deferred_again = debt_ledger_row( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ assert deferred_again is not None ++ assert deferred_again.debt_id == due_insights_debt.debt_id ++ assert deferred_again.created_at_ms == due_insights_debt.created_at_ms ++ assert deferred_again.attempts == 2 ++ assert deferred_again.status == "deferred" ++ assert ( ++ debt_ledger_row( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ == unrelated_fts_debt ++ ) ++ assert fts.check_sessions((recovered.target_session_id,)) == {recovered.target_session_id} ++ assert messages_fts_match_count(recovered.index_db, "Message") == 1 ++ assert raw_authority_facts(recovered.source_db) == raw_facts_before ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ).profile ++ is None ++ ) ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.unrelated_session_id, ++ ).profile ++ is None ++ ) ++ ++ # Second restart: after the source becomes quiet, only the recorded ++ # insights stage may complete and consume only its exact debt row. ++ recovered.make_target_quiet() ++ set_debt_retry_at( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ retry_at=_DUE_RETRY_AT, ++ ) ++ assert _run_retry_in_fresh_process(recovered.index_db) == 1 ++ assert ( ++ debt_ledger_row( ++ recovered.ops_db, ++ stage="insights", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ is None ++ ) ++ assert ( ++ debt_ledger_row( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ == unrelated_fts_debt ++ ) ++ assert fts.check_sessions((recovered.target_session_id,)) == {recovered.target_session_id} ++ recovered_terminal_facts = session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ) ++ assert recovered_terminal_facts.profile is not None ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.unrelated_session_id, ++ ).profile ++ is None ++ ) ++ assert raw_authority_facts(recovered.source_db) == raw_facts_before ++ ++ # The independent FTS obligation is due only now. Its own retry repairs the ++ # missing rows without replaying or duplicating insights materialization. ++ set_debt_retry_at( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ retry_at=_DUE_RETRY_AT, ++ ) ++ assert _run_retry_in_fresh_process(recovered.index_db) == 1 ++ assert ( ++ debt_ledger_row( ++ recovered.ops_db, ++ stage="fts", ++ subject_type="session_id", ++ subject_id=recovered.target_session_id, ++ ) ++ is None ++ ) ++ assert fts.check_sessions((recovered.target_session_id,)) == set() ++ assert messages_fts_match_count(recovered.index_db, "Message") == deleted_fts_rows + 1 ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ) ++ == recovered_terminal_facts ++ ) ++ ++ # Repeated convergence is a no-op at quiescence and public status agrees ++ # with the durable ledger rather than hiding a residual row. ++ assert _run_retry_in_fresh_process(recovered.index_db) == 0 ++ assert CursorStore(recovered.index_db).list_convergence_debt(limit=20) == [] ++ quiescent_status = convergence_debt_summary_info(recovered.index_db) ++ assert quiescent_status.failed_count == 0 ++ assert quiescent_status.retry_due_count == 0 ++ assert ( ++ session_materialization_facts( ++ recovered.index_db, ++ session_id=recovered.target_session_id, ++ ) ++ == recovered_terminal_facts ++ ) ++ assert raw_authority_facts(recovered.source_db) == raw_facts_before ++ ++ # Independent terminal oracle: an uninterrupted quiet archive reaches the ++ # same stable profile/receipt/event/thread facts and leaves the unrelated ++ # session untouched. ++ baseline = seed_partial_convergence_archive(tmp_path / "baseline", target_hot=False) ++ baseline_states, _baseline_timings = DaemonConverger((make_insights_stage(baseline.index_db),)).converge_batch( ++ (baseline.target_source,) ++ ) ++ assert baseline_states[baseline.target_source].converged ++ baseline_terminal_facts = session_materialization_facts( ++ baseline.index_db, ++ session_id=baseline.target_session_id, ++ ) ++ assert baseline_terminal_facts == recovered_terminal_facts ++ assert ( ++ session_materialization_facts( ++ baseline.index_db, ++ session_id=baseline.unrelated_session_id, ++ ).profile ++ is None ++ ) ++ ++ receipt = WorkloadReceipt.from_observations( ++ spec=recovered.workload_spec, ++ status=WorkloadRunStatus.SUCCEEDED, ++ build_id="git:f654480cadb7cc4c194704e24dfd483199547b35", ++ runtime_id="pytest:fresh-python-process-retries", ++ archive_id="archive:testdiet-02-partial-convergence", ++ generation_id="typed-archive-writer:testdiet-02", ++ frame_id=None, ++ phases=( ++ WorkloadPhaseObservation(name="generate"), ++ WorkloadPhaseObservation(name="ingest"), ++ WorkloadPhaseObservation(name="observe-debt", progress_completed=0, progress_total=1), ++ WorkloadPhaseObservation(name="converge", progress_completed=1, progress_total=1), ++ WorkloadPhaseObservation(name="query"), ++ WorkloadPhaseObservation(name="quiescent", cleanup_complete=True, quiescent=True), ++ ), ++ evidence_refs=( ++ f"ops-debt:{due_insights_debt.debt_id}", ++ f"session:{recovered.target_session_id}", ++ ), ++ cleanup_complete=True, ++ ) ++ assert receipt.spec.workload_id == "canary:partial-convergence-drain" ++ assert receipt.status is WorkloadRunStatus.SUCCEEDED ++ assert receipt.cleanup_complete is True ++ assert receipt.phases[-1].quiescent is True diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/TESTS.md new file mode 100644 index 000000000..a62fbc190 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-02/a01/extracted/TESTS.md @@ -0,0 +1,201 @@ +# Test design and execution + +## Survivor + +Primary test: + +```text +tests/unit/daemon/test_convergence_restart_law.py::test_convergence_debt_survives_restart_and_reaches_one_terminal_fact_set +``` + +The test is one state law rather than a set of disconnected mock examples. + +### Sequence + +1. Seed two Codex sessions through `write_parsed_session_to_archive`, including real `source.db` raw authority and real `index.db` parsed rows. +2. Make only the target source active-growing with the production hot-source size threshold and a deterministic future mtime. +3. Run the real insights stage through `DaemonConverger.converge_batch`. +4. Observe `StageState.PENDING` and the production deferred error. +5. Map the source path to its exact session through `record_convergence_outcome`; verify a durable `ops.db` insights row with attempts 1 and status `deferred`. +6. Delete only the target session's real FTS rows and record a separate FTS session debt row with a future retry deadline. +7. Force only insights due and launch a fresh Python process that calls `_drain_convergence_debt_once`. +8. While the source remains hot, verify the same insights `debt_id` and `created_at_ms`, attempts 2, deferred status, untouched FTS row, still-stale FTS materialization, unchanged raw authority, and no profile for target or unrelated session. +9. Truncate the target source below the production hot-source threshold, force insights due, and retry in another fresh interpreter. +10. Verify only insights debt is cleared, only the target profile is materialized, unrelated session remains untouched, FTS debt remains exact and future-dated, and source authority is unchanged. +11. Force FTS due and retry in a third fresh interpreter. +12. Verify only FTS debt is cleared, FTS rows are repaired, and all insight facts remain byte/value-equivalent to their pre-FTS snapshot. +13. Run another fresh-process drain; verify zero work, empty ledger, zero public failed/due counts, unchanged facts, and unchanged raw authority. +14. Seed a separate quiet archive and run uninterrupted insights convergence. +15. Compare stable profile/materialization/work-event/phase/thread/table facts between interrupted/restarted and uninterrupted archives. +16. Build the current `partial_convergence_canary_spec` `WorkloadReceipt` with all declared phases in order and cleanup/quiescence complete. + +## Production dependencies exercised + +- `polylogue.storage.sqlite.archive_tiers.write.write_parsed_session_to_archive` +- `polylogue.sources.live.batch_observability.session_ids_for_source_path` through `record_convergence_outcome` +- `polylogue.sources.live.convergence_debt.convergence_debt_from_states` +- `polylogue.sources.live.convergence_outcome.record_convergence_outcome` +- `polylogue.sources.live.cursor.CursorStore` +- `polylogue.storage.sqlite.archive_tiers.ops_write.add_convergence_debt` +- `polylogue.daemon.convergence.DaemonConverger` +- `polylogue.daemon.convergence_stages.make_insights_stage` +- `polylogue.daemon.convergence_stages.make_fts_stage` +- `polylogue.daemon.cli._drain_convergence_debt_once` +- `polylogue.daemon.convergence_debt_status.convergence_debt_summary_info` +- `polylogue.scenarios.partial_convergence_canary_spec` +- `polylogue.scenarios.WorkloadReceipt` + +The harness reads SQLite only for independent facts and deterministic deadline control. It never decides whether a stage needs work or how to converge it. + +## Independent oracle + +Debt oracle: + +- exact `debt_id`; +- exact `(stage, subject_type, subject_id)`; +- `created_at_ms` preservation; +- attempt count; +- status and retry deadline; +- cleanup/absence. + +Materialized oracle: + +- `session_profiles` stable columns; +- `insight_materialization` receipts excluding attempt-time materialization timestamps; +- session work events; +- session phases; +- threads and thread membership; +- archive table cardinalities; +- FTS match count and stage check; +- raw authority identity/hash/size/acquisition facts. + +Public oracle: + +- deferred insights are not reported as failed; +- unrelated failed FTS debt is visible; +- final failed and retry-due counts are zero. + +The terminal comparison is against a separately seeded uninterrupted archive. It does not infer success from the production retry loop's own trace. + +## Determinism + +No sleeps or polling loops are used. + +- Retry readiness is controlled by writing `next_retry_at` on the actual `ops.db` row. +- Hotness is held deterministically with a sparse file above the production threshold and a future mtime. +- Quietness is reached deterministically by truncating below the size threshold. +- Restart is a fresh interpreter process, not an object recreation in the same process. +- The unrelated stage is blocked by a future retry deadline and checked through real FTS state. + +## Anti-vacuity mutation + +Representative mutation: restore the snapshot's old retry implementation, which ignores `debt.stage`, executes all default stages for the selected subject, clears all subject debt, and recreates pending rows. + +Actual sensitivity run: + +```text +PYTHONPATH= /bin/pytest -q \ + tests/unit/daemon/test_convergence_restart_law.py -x +``` + +Result: + +```text +exit status 1 +1 failed in 3.18s +first failure: deferred_again.debt_id != due_insights_debt.debt_id +``` + +Why it fails: the old loop deletes the due insights row and recreates it after the still-hot retry, so durable effect identity and accumulated attempt history are lost. The same old all-stage execution also repairs/consumes the deliberately unrelated FTS obligation; later assertions protect that stage-scope dimension. + +Other named mutations guarded by the survivor: + +- Drop `record_convergence_debt` after a bounded false result: no durable row exists before restart. +- Clear debt before stage success: the still-hot retry loses the insights row. +- Skip `_drain_convergence_debt_once`: the quiet target never gains a profile and debt never reaches quiescence. +- Resolve a different source/session: the unrelated session gains facts or target facts remain absent. +- Run all stages for exact debt: FTS rows and FTS debt change during the insights retry. +- Recreate instead of upsert exact debt: UUID/creation time changes and attempts reset. +- Clear by subject instead of exact stage: future-dated FTS debt disappears during insights completion. + +## Commands and results + +### Formatting, lint, syntax, typing + +```text +.venv/bin/ruff format polylogue/daemon/cli.py \ + tests/infra/convergence_harness.py \ + tests/unit/daemon/test_convergence_restart_law.py +result: one file formatted; final files stable + +.venv/bin/ruff check +result: All checks passed + +.venv/bin/python -m py_compile +result: pass + +.venv/bin/mypy +result: Success: no issues found in 3 source files +``` + +### Primary and adjacent tests + +```text +.venv/bin/pytest -q tests/unit/daemon/test_convergence_restart_law.py -vv +result: 1 passed in 8.29s + +.venv/bin/pytest -q \ + tests/unit/daemon/test_daemon_cli.py \ + tests/unit/daemon/test_convergence_stages.py \ + tests/unit/daemon/test_convergence_restart_law.py +result: 126 passed in 15.65s + +for seed in 101 202 303; do + .venv/bin/pytest -q --randomly-seed="$seed" \ + tests/unit/daemon/test_convergence_restart_law.py +done +result: 1 passed in 7.75s; 1 passed in 8.42s; 1 passed in 7.98s + +.venv/bin/pytest -q \ + tests/unit/daemon/test_catch_up_observability.py \ + tests/unit/daemon/test_convergence_debt_alert.py \ + tests/unit/sources/test_live_batch_support.py \ + tests/unit/sources/test_live_catchup_planning.py +result: 106 passed in 22.65s +``` + +### Patch integrity + +```text +git diff --check +result: pass + +git apply --check PATCH.diff in a separate clean worktree at +f654480cadb7cc4c194704e24dfd483199547b35 +result: pass + +apply patch in that worktree and run survivor +result: 1 passed in 8.16s +``` + +### Supplemental baseline failures + +A larger adjacent run produced `158 passed, 2 failed`. Both failures reproduce unchanged on the unmodified snapshot: + +```text +tests/unit/daemon/test_daemon_status.py::test_build_daemon_status_detects_broken_append_head_blocks_converged +tests/unit/daemon/test_daemon_status.py::test_daemon_and_direct_claim_guard_share_mixed_frontier_summary +``` + +They concern append-head/frontier status and are outside this patch. + +### Repository wrapper limitation + +```text +.venv/bin/python -m devtools test tests/unit/daemon/test_convergence_restart_law.py +result: exit 125 before pytest +reason: wrapper requires a non-disk-backed pytest temp area and refused the +container's 64 MiB /dev/shm +``` + +Direct pytest execution above is verified. The complete repository suite, Nix/package build, live daemon, service supervisor, and deployment checks remain unverified. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/README.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/README.md new file mode 100644 index 000000000..b53749aef --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/README.md @@ -0,0 +1,16 @@ +# Derived freshness solution bundle + +This is the rebuilt delivery with a plain ASCII filename. It is rooted at base commit `f654480cadb7cc4c194704e24dfd483199547b35`. + +Contents: +- `solution.patch`: selected binary-safe Git patch. +- `resulting-files/`: changed files after applying the selected patch to a clean checkout. +- `requirements/`: the supplied task specification. +- `validation/`: clean-apply, diff hygiene, test, and archive-verification logs. +- `candidate-index.json`: all recovered patch candidates and their scoring/apply-check results. +- `selection.json`: exact selection rationale. +- `manifest.json`: per-file hashes and build facts. + +Selected candidate: `none` +Selected patch size: 0 bytes +Materialized changed paths: 0 diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/candidate-index.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/candidate-index.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/candidate-index.json @@ -0,0 +1 @@ +[] diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/changed-paths.txt b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/changed-paths.txt new file mode 100644 index 000000000..e69de29bb diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/input-identities.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/input-identities.json new file mode 100644 index 000000000..11f58043e --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/input-identities.json @@ -0,0 +1,17 @@ +[ + { + "name": "00-polylogue-all.tar(9).gz", + "sha256": "3d0c5a2e5024632c05438dbc3a08aae1dcd31529472b1185ad5b80c0cd933e30", + "size": 92457778 + }, + { + "name": "00-slightly-stale-context-testsuite-diet.tar(9).gz", + "sha256": "d4fa7fc31c70ff30db076e526836ffc81b8124d3f54c7add5e58d3f57c9dcbff", + "size": 934006 + }, + { + "name": "04-derived-freshness.md", + "sha256": "72b34413851a796814cf1bddde0821317ed836382dd55857239598b4c4efe072", + "size": 6516 + } +] diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/manifest.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/manifest.json new file mode 100644 index 000000000..1e1cbe339 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/manifest.json @@ -0,0 +1,70 @@ +{ + "base_commit": "f654480cadb7cc4c194704e24dfd483199547b35", + "base_repositories": [], + "candidate_apply_checks": [], + "changed_paths": [], + "files": [ + { + "path": "README.md", + "sha256": "f8bd92106f54f4e0e0271ad8fd5b8f6f897c49d6ac32ce9b7aff40047b54a1b5", + "size": 736 + }, + { + "path": "candidate-index.json", + "sha256": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570", + "size": 3 + }, + { + "path": "changed-paths.txt", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0 + }, + { + "path": "input-identities.json", + "sha256": "fd6e7a31df91aec9d0d97e76d8508c0917e16ce1a25ff8e661ec5e2d2b06cfcf", + "size": 480 + }, + { + "path": "requirements/04-derived-freshness.md", + "sha256": "72b34413851a796814cf1bddde0821317ed836382dd55857239598b4c4efe072", + "size": 6516 + }, + { + "path": "selection.json", + "sha256": "f1dacdc1aa39d1754cd1d5c333c9c80313fe4ecdbda6d5505d335bedfabf598b", + "size": 205 + }, + { + "path": "solution.patch", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0 + } + ], + "finished_utc": "2026-07-17T11:36:58Z", + "patch_bytes": 0, + "prior_zips": [ + { + "members": 6, + "ok": true, + "path": "/mnt/data/derived-freshness-r02.zip" + }, + { + "members": 6, + "ok": true, + "path": "/mnt/data/derived-freshness.zip" + } + ], + "repositories": [], + "selected_candidate": null, + "started_utc": "2026-07-17T11:36:56Z", + "steps": [ + { + "ok": true, + "source_extract_members": 45 + }, + { + "ok": true, + "tests_extract_members": 73 + } + ] +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/requirements/04-derived-freshness.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/requirements/04-derived-freshness.md new file mode 100644 index 000000000..f4457337d --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/requirements/04-derived-freshness.md @@ -0,0 +1,116 @@ +Title: "[testdiet 04] Monotonic derived freshness" + +Job ID: `testdiet-04` +Result ZIP: `testdiet-04-derived-freshness-r01.zip` +Dependency: integrate only after `testdiet-02` and `testdiet-03` foundations are +adjudicated. + +## Mission + +Implement a survivor law that derived Polylogue facts never silently move +backward relative to their content and recipe/generation authority. Cover the +current FTS, insight, and—where the source contract makes it meaningful— +embedding catch-up identities through ingest, reprocess, recipe change, +restart, and rebuild. A reader must see a current generation, an explicit +pending/degraded state, or an honest absence; never a stale value presented as +current. + +Use the recommended contract in +`architecture/05-derived-freshness.md`: one small typed `DerivationKey` and +attempt predicate, with domain-specific ledgers for FTS, embeddings, and +insights. Validate the named source/recipe/output fields against current source +and full Bead notes, but do not reopen the rejected timestamp/count/boolean +alternatives without contradictory evidence. Do not invent a Diet-specific +freshness token or a universal derivation table. Reuse the convergence and +rebuild survivor mechanisms produced by the prerequisite lanes. Include varied +arrival orders, superseded workers, and an independent expected freshness +relation. + +Name a mutation that ignores recipe identity, lets an old generation mark a +new key current, retains a stale materialization, or marks debt complete early. +If live source proves one tier cannot yet implement the recommended seam, +implement the coherent decided slice and return the exact source conflict and +smallest dependency—not a fresh menu of architecture alternatives. + +## Context and authority + +You are a long-running ChatGPT Pro engineering worker. A recent Polylogue +project-state archive will be attached. Retrieve and inspect it broadly; do not +assume attachment bytes consume your active prompt context. The attached +snapshot is the code authority. This prompt defines your mission. Repository +instructions and complete relevant Beads records define constraints and intent; +later Beads notes may supersede older descriptions. Current source wins when a +stale plan names paths or APIs that no longer exist. + +Start by reporting the snapshot commit/branch/dirty-patch identity you found and +the source, tests, Beads, and history you inspected. Follow dependencies beyond +the obvious files when they affect the production route. Do not invent an API, +test helper, product contract, or parallel framework to make the task easy. + +## Working contract + +- Produce the largest internally coherent implementation draft that fits the + mission. Prefer one real end-to-end behavior over disconnected scaffolding. +- Preserve Polylogue's substrate-first architecture and existing typed + interfaces. Small production seams are allowed only when real production + behavior needs observation or control. +- Write concrete production changes and real-route tests. A test must name the + production dependency it exercises and the representative implementation + mutation/removal that should make it fail. +- Do not delete existing tests or helpers. Identify proposed dominated + deletions separately for independent local certification. +- Use your container and run meaningful self-contained checks when possible. + Never claim access to the operator's live daemon, browser, archive, secrets, + NixOS deployment, or current worktree. Mark those checks `unverified`. +- If the full scope is unsafe, complete the strongest coherent subset and make + the remaining decisions and exact continuation steps explicit. Do not return + placeholders, ellipses, pseudocode presented as code, or a generic plan in + place of implementation. + +## Deliverable + +Create the exact `Result ZIP` named near the top of this prompt under +`/mnt/data/`. Do not include the supplied repository/project-state archive or +other copied inputs in the result. The finished ZIP must be attached to the +conversation through a working, user-clickable download link. Work left only +in an internal shell directory, temporary notebook, scattered sandbox files, +or prose is not delivered. + +The ZIP must contain: + +- `HANDOFF.md`: mission, snapshot identity, inspected evidence, mechanism, + decisions, changed files, acceptance matrix, apply order, risks, and exact + verification performed/remaining; +- `PATCH.diff`: one apply-ready unified diff against the named snapshot; +- `TESTS.md`: test design, production dependencies, anti-vacuity mutation, + commands, and honest execution results; +- `EVIDENCE.md`: relevant source/Bead/history findings and any contradictions; +- `FILES/`: complete replacements only where they materially disambiguate the + patch; omit it when unnecessary. + +Before answering, reopen the ZIP, list and validate its members, compute its +SHA-256 and byte size, and confirm that `PATCH.diff` has no placeholders or +copied source snapshot. Your final chat response must begin with a substantive +operator-readable report of what you did and why. It must also state important +limitations, missing or unverified work, and how much additional value another +iteration could plausibly add—distinguishing a small repair from a substantial +second pass. Then report verification and risks and give a prominent working +link to the exact `/mnt/data/` ZIP. A bare download receipt is not acceptable. + +## Continuation protocol + +Do not perform a separate adversarial review unless the user explicitly asks +for one. If the user asks to **iterate** or **continue**, preserve valid prior +work, perform the highest-value remaining implementation/research pass, and +publish a new cohesive package revision with the same complete structure—not a +loose supplemental patch. Explain exactly what changed, what improved, what +still remains, and whether another iteration is likely to pay off. + +If the user explicitly asks for an **adversarial review**, attack your prior +result against the original mission and current attached authority: search for +unsupported claims, invented or stale APIs, missing call sites, composition +failures, unsafe assumptions, vacuous tests, patch/apply defects, incomplete +acceptance criteria, and evidence that would falsify the design. Preserve work +that survives. Then repair every legitimate finding you can, regenerate the +entire cohesive package as the next revision, and report findings, repairs, +remaining disputes, and the value of another adversarial/implementation pass. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/selection.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/selection.json new file mode 100644 index 000000000..58c23fb98 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/selection.json @@ -0,0 +1,5 @@ +{ + "changed_paths_materialized": [], + "selected": null, + "selection_rule": "highest relevance score among patches that pass git apply --check against the recorded base; +1000 for clean applicability" +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/solution.patch b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/solution.patch new file mode 100644 index 000000000..e69de29bb diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/validation/archive-verification.json b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/validation/archive-verification.json new file mode 100644 index 000000000..b66339390 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-04/r02/extracted/derived_freshness_solution/validation/archive-verification.json @@ -0,0 +1,7 @@ +{ + "bad_member": null, + "member_count": 8, + "safe_paths": true, + "sha256": "7f0fa52908d60301002d23369893c77afc2177cc5757341af5ae4a027fb57819", + "size": 5966 +} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/EVIDENCE.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/EVIDENCE.md new file mode 100644 index 000000000..37c13a00d --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/EVIDENCE.md @@ -0,0 +1,158 @@ +# testdiet-05 evidence record + +## Repository and snapshot evidence + +The attached project-state archive identifies: + +- generated snapshot time: `2026-07-17T043202Z`; +- repository branch: `master`; +- commit: `f654480c` (full hash `f654480cadb7cc4c194704e24dfd483199547b35`); +- merge base against `origin/master`: the same full hash; +- branch delta patch: 0 bytes; +- branch delta file list: 0 bytes; +- branch delta log: 0 bytes; +- branch diff stat: empty. + +The overview also says `dirty=true` and reports 1,123.3 MiB of ignored local runtime state. Because every tracked-delta artifact is empty and the reconstructed tracked worktree is clean at the named commit, the patch is based on the clean tracked snapshot. Ignored local state was neither inspected as code authority nor copied into the result. + +## Repository instructions + +Root `AGENTS.md` establishes the relevant architectural constraints: + +- semantics belong in the substrate/product layer, while surfaces remain thin adapters; +- public query behavior is shared across CLI, MCP, Python API, and daemon HTTP; +- `actions` is a derived view and exact-selection behavior depends on bounded relation composition; +- query-unit expressions lower through the current Lark DSL and SQLite archive relations; +- adding a new production module would require topology regeneration. + +This patch adds no production module. It places aggregation in the SQLite archive substrate and only threads context through surface adapters. + +## Architecture decision inspected + +`architecture/06-query-cancellation-and-bounds.md` recommends: + +- one immutable outer `QueryExecutionContext`; +- one owned read lifecycle; +- dedicated read-only SQLite connection in its worker thread; +- progress-handler and `interrupt()` cancellation; +- common terminal states including work-budget exhaustion; +- logical selected/scanned/emitted progress; +- no hard semantic row caps or renderer substitution; +- lossless page/spool result lifecycle; +- parser token/depth/length preflight; +- deterministic cancellation and cleanup proof. + +The current snapshot already contains the first execution-control slice. This patch composes with that implementation, adds the missing read transaction/work receipt/budget pieces, and fixes the named aggregate materialization defect. It does not claim the absent parser or durable spool portions. + +## Beads evidence + +### `polylogue-z9gh.1` + +Title: `Make archive queries interruptible and resource-bounded`. + +The design names `QueryExecutionContext`, `QueryAdmissionController`, and `InterruptibleSQLiteRead`, requires a dedicated read-only worker connection, SQLite progress-handler cancellation, exact-connection interrupt, common cleanup ownership, and no permanent semantic refusal by size. + +Its 2026-07-16 investigation comment identifies the exact mechanism fixed here: + +- `_all_aggregate_rows` in `archive/query/unit_results.py` is the executor behind multi-field `group by ... | count` pipelines; +- it manually pages every matching row with `LIMIT/OFFSET`; +- it accumulates all rows as live Python objects before grouping; +- a progress-handler primitive alone does not fix the Python accumulation loop; +- the accepted implementation direction is SQL `GROUP BY`/`COUNT(*)`, not a hard row cap. + +This is the primary authority for replacing all-row materialization with the SQLite aggregate page. + +### `polylogue-z9gh.9.1` + +Title: `Land the shared query transaction across every read surface`. + +It requires one canonical request/executor/page boundary, exact totals, stable page boundaries, snapshot/ref identity, lossless advancing continuation, result refs/spools for aggregate/unstable plans, and parity across CLI/MCP/HTTP/Python. It explicitly says no path should serialize or materialize a full result merely to discard it. + +The current patch is deliberately a coherent subset: it removes one full-result materialization path and passes one context through API/MCP/HTTP query units. Durable result refs/spools and migration of every read surface remain open. + +### `polylogue-1xc.14.1` + +This workload-profile Bead names C-03 as the first production query canary: a mixed archive plus exact-session action query must keep the selective bound in both ranking legs, and a mutation restoring global-first composition must fail. It also requires shared receipts with work, cancellation/progress, and cleanup evidence. + +The snapshot already contains a hand-authored C-03 storage canary. This patch reuses its source-backed 50,000-VM-step discriminator through `InterruptibleSQLiteRead` and the production receipt, without claiming the full workload-profile generator. + +## Relevant source findings + +### Existing execution-control survivor + +Commit `fd7b3549292927fbd69e0cb07dff9a1205d8e6c8` added the production execution-control layer and tests across API/MCP/HTTP. The baseline source already has: + +- dedicated `ArchiveStore.open_existing(...)` per controlled call; +- worker-thread offload for async routes; +- shared cancellation event; +- SQLite progress handler; +- exact-connection `interrupt()`; +- weighted admission with interactive reservation; +- cancellation, timeout, disconnect, failure receipts; +- API/MCP/HTTP `query_units` integration. + +Therefore this patch extends those types and call paths instead of creating `transaction.py`, a second controller, or surface-local timeout logic. + +### Concrete aggregate defect + +Baseline `archive/query/unit_results.py` contains: + +- `_all_aggregate_rows`, which repeatedly calls the terminal row query method with a page size of 1,000; +- an unbounded Python `rows` list; +- Python `Counter` grouping after all matching rows have been retained; +- page slicing only after the complete group set has been created. + +The ordinary rows-terminal executor is already bounded to one `limit + 1` page. The defect is specific to multi-field aggregation. Single-field aggregate queries already use `ArchiveStore.query_unit_counts` and remain on that route. + +### Current descriptor authority + +`archive/query/metadata.py` advertises aggregate fields beyond the baseline Python row map, including `session.repo`, assertions, and observed events. The new closed SQL lowerer follows the current descriptors and current relation implementations. Tests cover messages, actions, blocks, files, assertions, observed events, and delegations. + +### Existing C-03 evidence + +`tests/unit/storage/test_archive_tiers_archive.py::test_exact_session_action_count_bounds_pairing_before_global_ranking` builds 512 sessions, measures real SQLite progress callbacks, and verifies: + +- bounded and mutant queries return the same exact-session result; +- the bounded relation performs fewer than 50,000 VM steps; +- a global-first mutation performs at least 50,000 VM steps. + +The new controlled-runner canary uses the same production relation mutation and threshold while exercising receipt accounting and cleanup. + +## Tests and public routes inspected + +- `tests/unit/archive/query/test_execution_control.py` +- `tests/unit/archive/test_query_multi_aggregate.py` +- `tests/unit/storage/test_archive_tiers_archive.py` +- `tests/unit/api/test_facade_contracts.py` +- `tests/unit/mcp/test_server_surfaces.py` +- `tests/unit/daemon/test_web_reader.py` +- `tests/unit/cli/test_query_expression.py` +- `polylogue/api/archive.py` +- `polylogue/mcp/archive_support.py` +- `polylogue/mcp/server_tools.py` +- `polylogue/daemon/http.py` +- current query expression, metadata, relation, and payload code followed from those call sites. + +## History inspected + +- `fd7b3549292927fbd69e0cb07dff9a1205d8e6c8` — first execution-control implementation; +- `6610f3b0726a5fda353109d119eaefa8b31ef993` — Beads closure note for that slice; +- `f654480cadb7cc4c194704e24dfd483199547b35` — attached baseline. + +The attached Beads export still reports `polylogue-z9gh.1` as open, while the repository history contains a later commit whose subject says it closes `z9gh.1`. This is a source-of-record contradiction. The current code and later open shared-transaction Bead show that the first execution-control slice landed but the broader bounded-query contract remains incomplete. The patch treats current source as authoritative for what exists and the complete Bead text as authority for remaining intent. + +## Dependency contradiction + +The mission says to integrate after the query algebra survivor in `testdiet-01`. The attached testsuite archive contains no `testdiet-01` result package or patch. Current source already has the terminal query algebra, descriptors, aggregate pipeline, and exact-session action-bound canary. The implementation therefore uses those current production contracts and records the missing dependency artifact rather than inventing names or blocking the coherent fix. + +## Why this scope is coherent + +The source, Bead comment, architecture decision, and existing tests converge on one end-to-end defect and one extension point: + +1. multi-field aggregation is the known Python memory/work amplification path; +2. SQLite already owns structural predicates and selective action/event/file relations; +3. the existing runner already owns cancellation/admission and the exact connection; +4. surface adapters already create one outer context but did not expose result work to it; +5. SQL grouping plus context-threaded receipts fixes the defect through real storage and public query-unit routes without defining a parallel framework. + +The unimplemented parser and durable spool contracts require broader product decisions and cross-surface migration. They are explicitly separated in `HANDOFF.md` rather than represented by scaffolding. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/HANDOFF.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/HANDOFF.md new file mode 100644 index 000000000..b533bd19b --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/HANDOFF.md @@ -0,0 +1,229 @@ +# testdiet-05 handoff: bounded query work, cancellation, progress, and cleanup + +## Mission and outcome + +This package implements the strongest coherent slice of the bounded-query contract that is supported by the attached Polylogue snapshot. It fixes the concrete unbounded mechanism named by `polylogue-z9gh.1`: multi-field `... | group by a, b | count` queries no longer page every selected terminal row into a growing Python list before aggregation. The selected relation, grouping, exact denominator and quality statistics, stable ordering, and requested aggregate page now execute in one SQLite statement. + +The patch also extends the snapshot's existing execution-control layer rather than creating a parallel framework. One controlled query call now owns an explicit SQLite read transaction, can enforce an optional deterministic VM-work budget through the production progress handler, records actual progress callbacks and result-page facts, distinguishes `work_budget_exceeded`, and records cleanup completion. The Python API, MCP tool, and daemon HTTP query-unit adapters pass the same outer `QueryExecutionContext` into the shared executor. The CLI receives the SQL aggregation fix but is not yet migrated under the execution-control lifecycle. + +This is a rebuilt, substantially improved first valid delivery. The earlier chat artifact was not a usable package: the standalone patch contained only a `FileNotFoundError`, and the linked ZIP did not exist. No part of that artifact was treated as implementation authority. + +## Snapshot identity + +- Repository branch: `master` +- Baseline commit: `f654480cadb7cc4c194704e24dfd483199547b35` +- Baseline commit subject: `chore(beads): file archive-insight benchmark findings wofr/vhjs/r7p6/1wtm` +- Relevant existing execution-control commit: `fd7b3549292927fbd69e0cb07dff9a1205d8e6c8` (`feat(query): interruptible, admission-controlled archive read execution (#2964)`) +- Relevant Beads closure commit: `6610f3b0726a5fda353109d119eaefa8b31ef993` +- Patch base: tracked files at the exact baseline commit above +- Initial tracked delta: clean after reconstructing from the attached repository snapshot + +The archive's generated overview says `dirty=true`, but its branch-delta patch, branch-delta file list, branch-delta log, and branch diff stat are all empty, and the merge base is the baseline commit. I therefore treat the clean tracked tree at `f654480...` as the code authority. The overview's dirty flag is consistent with the archive also reporting more than 1 GiB of ignored local runtime state; no ignored state is included in this package. + +## Inspected authority and evidence + +The implementation was based on, and cross-checked against: + +- root `AGENTS.md` architecture and substrate-first rules; +- the attached mission, `[testdiet 05] Bounded query work, cancellation, and cleanup`; +- `architecture/06-query-cancellation-and-bounds.md`; +- complete attached Beads records for `polylogue-z9gh.1`, `polylogue-z9gh.9.1`, and `polylogue-1xc.14.1`; +- current source in `archive/query/execution_control.py`, `archive/query/unit_results.py`, query expression/metadata definitions, archive-tier SQLite relations, and API/MCP/HTTP/CLI adapters; +- current tests for execution control, aggregate query units, the C-03 action-bound canary, Python API, MCP, daemon HTTP, and CLI aggregation; +- git history around `fd7b35492`, `6610f3b07`, and the baseline commit. + +The attached testsuite archive does not contain the `testdiet-01` query-algebra survivor artifact named as a dependency. Current descriptors, source behavior, tests, Beads, and history were therefore used as authority; this patch does not invent a dependency API or helper. + +See `EVIDENCE.md` for the source-level findings and contradictions. + +## Production mechanism + +### 1. SQL-backed bounded multi-field aggregation + +`ArchiveStore.query_unit_multi_counts(...)` is a new substrate operation for multi-field aggregate pages. It uses a closed field-expression lowerer tied to current query-unit descriptors and supports: + +- messages; +- actions, including the existing selective/bounded action relation and follow-up relation when required; +- blocks; +- de-duplicated file paths at `(session_id, normalized path)` grain; +- durable assertions through the attached read-only user tier and current assertion defaults; +- observed events through the existing source/materialized union CTE; +- delegations through the current delegation view; +- `session.origin` and `session.repo` dimensions where descriptors advertise them. + +The statement composes these CTE stages: + +1. `selected`: applies the real structural predicate and session filters; +2. `grouped`: counts group tuples and per-field missing/explicit-unknown observations; +3. `ranked`: computes stable ordinal order plus exact full-result statistics with window functions; +4. `page`: selects only the requested ordinal window; +5. `stats`: preserves denominator and quality facts even when the requested page is empty or beyond the end. + +Python retains only the returned aggregate page. No semantic row cap was added. The requested limit is still a page size, and `limit + 1` remains the existing `has_next` probe. + +The lowerer preserves the prior observable distinctions among SQL `NULL` (rendered as `[missing]`), the empty string, and the literal token `unknown`. Assertion status/visibility/author defaults match current row semantics. Aggregate order matches the former Python tuple ordering, including deterministic tie-breaking by all group keys. + +### 2. One owned read snapshot + +`InterruptibleSQLiteRead.run(...)` now begins an explicit read transaction before invoking the query work and rolls it back in `finally`. This makes multiple index-tier statements in one controlled call observe the same snapshot after the first read establishes it. Cleanup order is progress-handler removal, transaction release, store close, then receipt completion. + +This snapshot currently governs the main index connection and its attached user tier. A lazily opened separate source-tier connection is not yet folded into the same lifecycle; that is a stated continuation item rather than an unsupported claim. + +### 3. Deterministic SQLite work containment and truthful progress + +`QueryExecutionContext` accepts an optional `sqlite_vm_step_budget`. The runner selects a progress cadence no larger than that budget, counts each actual SQLite progress callback, and aborts when the lower-bound opcode count reaches the declared budget. A budget of zero aborts before query work begins. The terminal state and exception are distinct from cancellation and deadline expiry: + +- receipt state: `work_budget_exceeded`; +- exception: `QueryWorkBudgetExceededError`. + +The receipt now records: + +- actual progress callback count; +- conservative SQLite VM-step lower bound; +- declared VM-step budget; +- exact selected-row denominator where the SQL aggregate executor knows it; +- rows placed in the logical result page; +- logical pages produced; +- cleanup completion. + +These counters are recorded by production execution and result-building code. The tests do not duplicate the aggregation algorithm to manufacture a work count. + +`rows_emitted` means rows built into the logical envelope, not transport acknowledgement. That distinction is intentional and documented as a risk until the shared result-ref/page transaction exists. + +### 4. Shared context propagation + +The existing outer contexts are now passed into `query_unit_envelope(...)` and `query_unit_rows(...)` through: + +- Python API `Polylogue.query_units` route; +- MCP `query_units` tool and archive support; +- daemon HTTP `/api/query-units` route. + +This lets the shared result executor update the same receipt owned by admission/cancellation/deadline control. No adapter-specific counter, timeout, or aggregation implementation was added. + +## Decisions + +1. **Push grouping into SQLite rather than add a hard row cap.** A cap would change exhaustive aggregate semantics and violate the architecture decision. SQL grouping removes result-cardinality-sized Python memory while preserving exact totals. +2. **Retain current public response shapes.** Existing aggregate envelopes, pipeline metadata, offset behavior, ordering, and `has_next` semantics remain intact. +3. **Use actual progress-handler observations.** The receipt reports a lower bound based on callback cadence, not a guessed row scan count. +4. **Do not choose a public work budget without evidence.** The production primitive exists and is deterministically tested, but API/MCP/HTTP do not impose an arbitrary default. Workload-profile evidence is the named authority for policy. +5. **Do not fabricate `QueryResultRef` or spool semantics.** Aggregate pages are lossless on a stable archive, but durable snapshot-bound continuation remains `z9gh.9.1` work. +6. **Keep this patch substrate-first.** The SQL relation and work lifecycle live in storage/query execution; surfaces only pass the shared context. +7. **Do not delete existing tests or helpers.** No dominated deletion is proposed in this package. + +## Changed files + +- `polylogue/storage/sqlite/archive_tiers/archive.py` + - multi-aggregate page/row types; + - closed SQL field lowerer and CTE composition; + - one-statement bounded multi-field aggregation; + - progress-handler cleanup and read-snapshot lifecycle seams. +- `polylogue/archive/query/unit_results.py` + - removes `_all_aggregate_rows` and Python all-row grouping; + - routes multi-field counts to the SQLite aggregate page; + - records result page and exact denominator facts in the shared context. +- `polylogue/archive/query/execution_control.py` + - optional deterministic VM-work budget; + - typed work-budget terminal state/error; + - production work/page/cleanup receipt fields; + - explicit read transaction ownership and cleanup. +- `polylogue/api/archive.py` + - passes the outer execution context to the query-unit executor. +- `polylogue/mcp/archive_support.py` + - accepts and passes the shared context. +- `polylogue/mcp/server_tools.py` + - passes the MCP route's context to archive support. +- `polylogue/daemon/http.py` + - passes the daemon route's context to the query-unit executor. +- `tests/unit/archive/query/test_execution_control.py` + - deterministic work-budget, snapshot, receipt, cleanup, surface-context, and irrelevant-growth tests. +- `tests/unit/archive/test_query_multi_aggregate.py` + - lossless page, exact stats, SQL-only execution, relation coverage, de-duplication, and data-state tests. + +`PATCH.diff` is the complete apply-ready unified diff. `FILES/` is omitted because no complete replacement is necessary to disambiguate the patch. + +## Acceptance matrix + +| Mission property | Status | Evidence in this patch | +| --- | --- | --- | +| Remove result-cardinality-sized Python materialization | PASS | `_all_aggregate_rows` removed; multi-field grouping/count/page/stats are one SQLite statement; mutation-sensitive SQL-only test | +| Irrelevant-growth semantic equivalence | PASS for exact-session action aggregate | Existing C-03 result equivalence retained; new controlled-runner canary compares bounded and global-first mutant | +| Selected-work scaling | PASS for the implemented exact-session action route | Production progress receipt remains below 50,000 VM-step lower bound; global-first mutation reaches at least 50,000 with identical result | +| Lossless aggregate pagination/addressability | PASS for static snapshot page concatenation; PARTIAL globally | Stable page concatenation, exact denominator/quality facts on every page and beyond-end page; no durable opaque cursor/result ref/spool yet | +| Cancellation/deadline propagation | PASS for existing API/MCP/HTTP controlled route | Existing cancellation, deadline, disconnect, heartbeat, and admission survivors remain green | +| Deterministic work-budget termination | PASS at shared execution primitive; PARTIAL policy | Typed budget state/error and exact callback cadence tested; no evidence-derived public default configured | +| Truthful progress | PARTIAL | Actual SQLite callbacks, VM lower bound, exact aggregate denominator, logical emitted rows/pages; no exact scanned-row/relation-expansion/spool-byte counters | +| Cleanup | PASS for owned index reader/snapshot/progress/admission; live envelope unverified | Snapshot consistency, connection closure, cleanup receipt, and admission release tested; no incident-scale RSS/PSS/temp/FD run | +| API/MCP/HTTP shared context | PASS for `query_units` | Production route tests and receipt assertions | +| CLI shared execution context | PARTIAL | CLI receives SQL bounded aggregation and behavior tests pass, but it is not under `InterruptibleSQLiteRead` in this patch | +| All read surfaces and result kinds | NOT IMPLEMENTED | list/search/session/messages/tree/topology and insight paths remain outside one shared transaction | +| Parser token/depth/length budget | NOT IMPLEMENTED | No safe source-backed parser-budget contract was present in this slice | +| Durable result refs/spool/resume | NOT IMPLEMENTED | Remains the central `z9gh.9.1` continuation | +| No semantic hard cap or renderer substitution | PASS | No selected-row cap or response truncation was introduced | + +## Apply order + +Apply `PATCH.diff` once against `master` at `f654480cadb7cc4c194704e24dfd483199547b35`: + +```bash +git checkout master +git reset --hard f654480cadb7cc4c194704e24dfd483199547b35 +git apply --check PATCH.diff +git apply PATCH.diff +``` + +The conceptual dependency order inside the patch is: + +1. archive-store page types, SQL lowerer, and read lifecycle; +2. execution-control receipt/budget/snapshot behavior; +3. shared unit result executor; +4. API/MCP/HTTP context adapters; +5. focused tests. + +## Verification performed + +All verification used the reconstructed attached snapshot, not an operator worktree. + +- 31 execution-control and multi-aggregate tests passed. +- 1 existing storage C-03 canary passed. +- 11 Python API `query_units` tests passed. +- 8 MCP `query_units` tool tests passed. +- 7 daemon HTTP query-unit endpoint tests passed. +- 7 selected real CLI aggregate tests passed. +- Total focused passing tests: **75**. +- Ruff check: passed on all 9 changed files. +- Ruff format check: all 9 changed files already formatted. +- Focused mypy: passed on all 7 changed production files. +- Python bytecode compilation: passed on all 7 changed production files. +- `git diff --check`: passed. +- Fresh-clone `git apply --check`: passed. +- Fresh-clone apply followed by `git diff --check`: passed. + +The exact commands and non-acceptance invocations are in `TESTS.md`. + +## Important limitations and risks + +1. **No durable result spool or query result ref.** Each later aggregate page reruns the grouped statement. Static page concatenation is exact, but a changing archive can move page boundaries without a snapshot-bound cursor. +2. **SQLite temp work is not yet certified.** Python memory is bounded by page size, but a high-cardinality grouping can use SQLite sorter/temp storage. The optional VM budget can terminate work; it does not make a partial aggregate complete or resumable. +3. **Only the main index connection is interrupted and counted.** Separate lazy source-tier reads are outside the progress handler, interrupt, and snapshot seam. +4. **No public work-budget policy.** The primitive is production-ready, but a default requires workload-profile evidence and a typed surface contract. Arbitrary values would turn host protection into accidental semantic refusal. +5. **Progress is conservative and scoped.** VM steps are a lower bound at callback cadence. Selected rows are exact only for the new multi-aggregate executor. Logical page emission is not network delivery acknowledgement. +6. **CLI lifecycle remains separate.** It benefits from the SQL fix but does not yet share admission/cancellation/receipt ownership. +7. **Read snapshots can retain WAL history.** The deadline/cancellation path bounds intended lifetime, but incident-scale WAL effects were not measured. +8. **No full-suite or live-system certification.** The operator daemon, browser, live archive, secrets, NixOS deployment, incident corpus, RSS/PSS/swap, temp directory, and file-descriptor steady state were unavailable and are explicitly unverified. +9. **No parser depth-limit survivor.** Deeply nested input remains outside this coherent patch. +10. **Current terminal vocabulary is retained.** Existing `cancelled`, `timed_out`, and `disconnected` names were not renamed wholesale to the architecture document's longer spellings; only the missing `work_budget_exceeded` state was added. + +## Exact continuation map + +A substantial second pass still has high value. It should not be a small patch around this executor; it should implement the shared query transaction already specified by `polylogue-z9gh.9.1`: + +1. introduce a canonical request/page/result-ref boundary with snapshot identity and stable opaque continuation; +2. use keyset continuation for stable indexed rows and an owned bounded spool for aggregates, recursive graphs, and unstable plans; +3. migrate CLI, list/search, session/message/block/action/file, topology/tree, and insight reads under the same outer context; +4. make source-tier reads read-only, snapshot-owned, progress-counted, and interruptible; +5. add parser token/depth/length preflight with a typed `syntax_budget_exceeded` result; +6. derive public VM/temp/admission budgets from workload profiles rather than constants chosen in tests; +7. certify repeated incident-scale cancellation and completion against RSS/PSS/swap/temp/reader/FD steady-state envelopes; +8. distinguish computation completion, spool completion, page construction, transport delivery, disconnect abandonment, and explicit resumable ownership. + +A small repair could add exact totals to the existing single-field aggregate receipt or map the new budget exception at individual adapters, but that would add limited value without a public policy and result lifecycle. The next high-return iteration is therefore **substantial**, not cosmetic. diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/PATCH.diff b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/PATCH.diff new file mode 100644 index 000000000..401730045 --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/PATCH.diff @@ -0,0 +1,1816 @@ +diff --git a/polylogue/api/archive.py b/polylogue/api/archive.py +index 80d786f1a0793e19087b22938a94c1ebab776532..15ea0db2ca32f057b824dc3ea0272332a666409d 100644 +--- a/polylogue/api/archive.py ++++ b/polylogue/api/archive.py +@@ -2953,7 +2953,7 @@ class PolylogueArchiveMixin: + ) + return await execute_archive_read( + _active_archive_root(self.config), +- lambda archive: query_unit_envelope(archive, request), ++ lambda archive: query_unit_envelope(archive, request, execution_context=ctx), + ctx=ctx, + ) + +diff --git a/polylogue/archive/query/execution_control.py b/polylogue/archive/query/execution_control.py +index 4c044833c02b5f171db27920b18d638fa59ba5ca..ba6df082a81607ddd0c390863a07b81fd2e2b92c 100644 +--- a/polylogue/archive/query/execution_control.py ++++ b/polylogue/archive/query/execution_control.py +@@ -15,13 +15,15 @@ This module supplies the reusable layer the shared query transaction + Admission is backpressure, never a semantic refusal: valid large work is + eventually admitted. + - :class:`InterruptibleSQLiteRead` — runs one unit of archive read work on a +- dedicated read-only connection in a worker thread, with a SQLite progress +- handler enforcing cancellation/deadline and a cross-thread interrupt path. ++ dedicated read-only connection and snapshot in a worker thread, with a SQLite ++ progress handler enforcing cancellation/deadline/work budgets and a ++ cross-thread interrupt path. + - :class:`QueryExecutionReceipt` — the observable outcome record. Receipts + carry safe query refs (hashes), never raw expressions. + +-Paging/spool formats and request-level budget contracts remain z9gh.9.1 +-scope; this slice owns execution and ownership primitives only. ++Paging/spool formats and cross-surface budget policy remain z9gh.9.1 scope. ++This layer owns execution, snapshot/connection lifetime, optional SQLite VM-work ++containment, and production work/cleanup receipts. + """ + + from __future__ import annotations +@@ -77,6 +79,7 @@ QueryExecutionState = Literal[ + "completed", + "cancelled", + "timed_out", ++ "work_budget_exceeded", + "disconnected", + "failed", + ] +@@ -90,6 +93,10 @@ class QueryTimeoutError(Exception): + """The read exceeded its execution deadline.""" + + ++class QueryWorkBudgetExceededError(Exception): ++ """The read exceeded its declared SQLite VM-work budget.""" ++ ++ + @dataclass + class QueryExecutionReceipt: + """Observable outcome of one controlled archive read. +@@ -106,6 +113,13 @@ class QueryExecutionReceipt: + queued_s: float = 0.0 + run_s: float = 0.0 + interrupted: bool = False ++ sqlite_progress_callbacks: int = 0 ++ sqlite_vm_steps_lower_bound: int = 0 ++ sqlite_vm_step_budget: int | None = None ++ selected_rows_exact: int | None = None ++ rows_emitted: int = 0 ++ result_pages_emitted: int = 0 ++ cleanup_complete: bool = False + error: str | None = None + + def to_dict(self) -> dict[str, object]: +@@ -118,6 +132,13 @@ class QueryExecutionReceipt: + "queued_s": round(self.queued_s, 6), + "run_s": round(self.run_s, 6), + "interrupted": self.interrupted, ++ "sqlite_progress_callbacks": self.sqlite_progress_callbacks, ++ "sqlite_vm_steps_lower_bound": self.sqlite_vm_steps_lower_bound, ++ "sqlite_vm_step_budget": self.sqlite_vm_step_budget, ++ "selected_rows_exact": self.selected_rows_exact, ++ "rows_emitted": self.rows_emitted, ++ "result_pages_emitted": self.result_pages_emitted, ++ "cleanup_complete": self.cleanup_complete, + "error": self.error, + } + +@@ -137,15 +158,24 @@ class QueryExecutionContext: + workload_class: WorkloadClass = "interactive" + admission_weight: int = 1 + deadline_monotonic: float | None = None ++ sqlite_vm_step_budget: int | None = None + owner_ref: str | None = None + cancel_event: threading.Event = field(default_factory=threading.Event, compare=False, repr=False) ++ _receipt_lock: threading.Lock = field(default_factory=threading.Lock, compare=False, repr=False) + receipt: QueryExecutionReceipt = field(init=False, compare=False, repr=False) + + def __post_init__(self) -> None: ++ if self.sqlite_vm_step_budget is not None and self.sqlite_vm_step_budget < 0: ++ raise ValueError("sqlite_vm_step_budget must be non-negative") + object.__setattr__( + self, + "receipt", +- QueryExecutionReceipt(call_id=self.call_id, query_ref=self.query_ref, workload_class=self.workload_class), ++ QueryExecutionReceipt( ++ call_id=self.call_id, ++ query_ref=self.query_ref, ++ workload_class=self.workload_class, ++ sqlite_vm_step_budget=self.sqlite_vm_step_budget, ++ ), + ) + + @classmethod +@@ -156,6 +186,7 @@ class QueryExecutionContext: + workload_class: WorkloadClass = "interactive", + admission_weight: int = 1, + timeout_s: float | None = DEFAULT_READ_DEADLINE_S, ++ sqlite_vm_step_budget: int | None = None, + owner_ref: str | None = None, + ) -> QueryExecutionContext: + query_ref = hashlib.sha256((query_text or "").encode("utf-8")).hexdigest()[:16] +@@ -166,6 +197,7 @@ class QueryExecutionContext: + workload_class=workload_class, + admission_weight=max(1, admission_weight), + deadline_monotonic=deadline, ++ sqlite_vm_step_budget=sqlite_vm_step_budget, + owner_ref=owner_ref, + ) + +@@ -179,8 +211,48 @@ class QueryExecutionContext: + def deadline_exceeded(self) -> bool: + return self.deadline_monotonic is not None and time.monotonic() > self.deadline_monotonic + ++ def record_sqlite_progress(self, opcodes: int) -> None: ++ """Record one real SQLite progress callback without estimating rows.""" ++ ++ with self._receipt_lock: ++ self.receipt.sqlite_progress_callbacks += 1 ++ self.receipt.sqlite_vm_steps_lower_bound += max(1, int(opcodes)) ++ ++ def record_result_page(self, *, emitted_rows: int, selected_rows_exact: int | None = None) -> None: ++ """Record a delivered logical page and exact selection evidence when known.""" ++ ++ with self._receipt_lock: ++ self.receipt.result_pages_emitted += 1 ++ self.receipt.rows_emitted += max(0, int(emitted_rows)) ++ if selected_rows_exact is not None: ++ normalized = max(0, int(selected_rows_exact)) ++ existing = self.receipt.selected_rows_exact ++ if existing is not None and existing != normalized: ++ raise RuntimeError("query execution reported contradictory exact selection counts") ++ self.receipt.selected_rows_exact = normalized ++ ++ def mark_cleanup_complete(self) -> None: ++ with self._receipt_lock: ++ self.receipt.cleanup_complete = True ++ ++ def work_budget_exceeded(self) -> bool: ++ budget = self.sqlite_vm_step_budget ++ if budget is None: ++ return False ++ with self._receipt_lock: ++ return self.receipt.sqlite_vm_steps_lower_bound >= budget ++ ++ def abort_reason(self) -> Literal["cancelled", "timed_out", "work_budget_exceeded"] | None: ++ if self.cancelled: ++ return "cancelled" ++ if self.deadline_exceeded(): ++ return "timed_out" ++ if self.work_budget_exceeded(): ++ return "work_budget_exceeded" ++ return None ++ + def should_abort(self) -> bool: +- return self.cancelled or self.deadline_exceeded() ++ return self.abort_reason() is not None + + + class QueryAdmissionController: +@@ -297,10 +369,11 @@ class InterruptibleSQLiteRead: + """One archive read on a dedicated read-only connection in this thread. + + The runner opens its own :class:`ArchiveStore` (never a shared or writer +- connection), installs a progress guard that aborts on cancellation or +- deadline, and publishes the store so :meth:`interrupt` can abort the +- active statement from another thread. Cleanup (progress handler, store, +- published handle) happens exactly once in ``finally``. ++ connection), begins one read snapshot, installs a progress guard that aborts ++ on cancellation, deadline, or a declared VM-work budget, and publishes the ++ store so :meth:`interrupt` can abort the active statement from another ++ thread. Cleanup (progress handler, transaction, store, published handle) ++ happens exactly once in ``finally``. + """ + + def __init__(self, ctx: QueryExecutionContext) -> None: +@@ -325,39 +398,52 @@ class InterruptibleSQLiteRead: + ctx = self._ctx + ctx.receipt.state = "running" + started = time.monotonic() ++ progress_opcodes = PROGRESS_GUARD_OPCODES ++ if ctx.sqlite_vm_step_budget is not None: ++ progress_opcodes = min(progress_opcodes, max(1, ctx.sqlite_vm_step_budget)) + + def _guard() -> int: ++ ctx.record_sqlite_progress(progress_opcodes) + return 1 if ctx.should_abort() else 0 + + store = ArchiveStore.open_existing(archive_root, read_timeout=read_timeout) + with self._store_lock: + self._store = store + try: +- store.set_read_progress_guard(_guard, n_opcodes=PROGRESS_GUARD_OPCODES) +- # A cancel/deadline that landed before the first opcode must not +- # start the statement at all. ++ store.set_read_progress_guard(_guard, n_opcodes=progress_opcodes) ++ # An abort that landed before ownership begins must not start the ++ # caller's statement or acquire a read snapshot. + if ctx.should_abort(): + raise _abort_error(ctx) + try: ++ store.begin_read_snapshot() + result = work(store) + except Exception as exc: + if ctx.should_abort() and _is_interrupt_error(exc): + ctx.receipt.interrupted = True + raise _abort_error(ctx) from exc + raise +- # The progress guard only observes SQLite execution. A cancel or +- # deadline that lands during Python-side post-processing (row +- # grouping, envelope assembly) must still abort here rather than +- # returning a "completed" result nobody is waiting for. +- if ctx.should_abort(): +- raise _abort_error(ctx) +- ctx.receipt.state = "completed" +- return result ++ else: ++ # The progress guard only observes SQLite execution. An abort ++ # that lands during Python-side post-processing (row grouping, ++ # envelope assembly) must still abort here rather than return a ++ # "completed" result nobody is waiting for. ++ if ctx.should_abort(): ++ raise _abort_error(ctx) ++ ctx.receipt.state = "completed" ++ return result + finally: + ctx.receipt.run_s = time.monotonic() - started + with self._store_lock: + self._store = None +- store.close() ++ try: ++ store.clear_read_progress_guard() ++ finally: ++ try: ++ store.end_read_snapshot() ++ finally: ++ store.close() ++ ctx.mark_cleanup_complete() + + + def _is_interrupt_error(exc: Exception) -> bool: +@@ -366,10 +452,16 @@ def _is_interrupt_error(exc: Exception) -> bool: + return isinstance(exc, sqlite3.OperationalError) and "interrupt" in str(exc).lower() + + +-def _abort_error(ctx: QueryExecutionContext) -> QueryCancelledError | QueryTimeoutError: +- if ctx.cancelled: ++def _abort_error( ++ ctx: QueryExecutionContext, ++) -> QueryCancelledError | QueryTimeoutError | QueryWorkBudgetExceededError: ++ reason = ctx.abort_reason() ++ if reason == "cancelled": + ctx.receipt.state = "cancelled" + return QueryCancelledError(f"archive read cancelled (call {ctx.call_id})") ++ if reason == "work_budget_exceeded": ++ ctx.receipt.state = "work_budget_exceeded" ++ return QueryWorkBudgetExceededError(f"archive read exceeded SQLite work budget (call {ctx.call_id})") + ctx.receipt.state = "timed_out" + return QueryTimeoutError(f"archive read exceeded deadline (call {ctx.call_id})") + +@@ -448,7 +540,7 @@ async def execute_archive_read( + # wait on it forever. An undrained worker keeps running in the + # background and still performs its own cleanup on exit. + await asyncio.wait_for(worker, timeout=DISCONNECT_DRAIN_TIMEOUT_S) +- except (QueryCancelledError, QueryTimeoutError, asyncio.CancelledError): ++ except (QueryCancelledError, QueryTimeoutError, QueryWorkBudgetExceededError, asyncio.CancelledError): + pass + except TimeoutError: + logger.warning( +@@ -462,7 +554,7 @@ async def execute_archive_read( + # "cancelled", but the caller-side truth here is a disconnect. + ctx.receipt.state = "disconnected" + raise +- except (QueryCancelledError, QueryTimeoutError): ++ except (QueryCancelledError, QueryTimeoutError, QueryWorkBudgetExceededError): + raise + except Exception as exc: + ctx.receipt.state = "failed" +@@ -493,7 +585,7 @@ def execute_archive_read_sync( + try: + with admission.admit_blocking(ctx): + return reader.run(archive_root, work, read_timeout=read_timeout) +- except (QueryCancelledError, QueryTimeoutError): ++ except (QueryCancelledError, QueryTimeoutError, QueryWorkBudgetExceededError): + raise + except Exception as exc: + ctx.receipt.state = "failed" +@@ -511,6 +603,7 @@ __all__ = [ + "QueryExecutionContext", + "QueryExecutionReceipt", + "QueryTimeoutError", ++ "QueryWorkBudgetExceededError", + "WorkloadClass", + "classify_unit_expression_workload", + "default_admission_controller", +diff --git a/polylogue/archive/query/unit_results.py b/polylogue/archive/query/unit_results.py +index 97b30a99a4a355b3d70c2488b88568386601b7aa..da94b05f84e1fd4c16080af5756689a966f29bef 100644 +--- a/polylogue/archive/query/unit_results.py ++++ b/polylogue/archive/query/unit_results.py +@@ -3,10 +3,9 @@ + from __future__ import annotations + + import json +-from collections import Counter + from collections.abc import Callable, Mapping, Sequence + from dataclasses import dataclass +-from typing import Any, Literal, Protocol, cast ++from typing import TYPE_CHECKING, Any, Literal, Protocol, cast + + from polylogue.archive.query.expression import ( + ExpressionCompileError, +@@ -36,6 +35,9 @@ from polylogue.surfaces.payloads import ( + build_query_unit_envelope, + ) + ++if TYPE_CHECKING: ++ from polylogue.archive.query.execution_control import QueryExecutionContext ++ + + def _pipeline_stage_payloads(pipeline: QueryUnitPipeline) -> tuple[dict[str, object], ...]: + """Return the executed pipeline stages, ending in the terminal-action node. +@@ -204,80 +206,28 @@ class TerminalExecutionContext: + caller_offset: int + fetch_limit: int + session_filters: Mapping[str, object] | None ++ execution_context: QueryExecutionContext | None = None + + + TerminalExecutor = Callable[[TerminalExecutionContext], QueryUnitResultEnvelope] + + +-_AGGREGATE_ROW_FIELDS: dict[str, dict[str, str]] = { +- "message": {"role": "role", "type": "message_type", "session.origin": "origin"}, +- "action": { +- "tool": "tool_name", +- "action": "semantic_type", +- "type": "semantic_type", +- "is_error": "is_error", +- "exit_code": "exit_code", +- "followup_class": "followup_class", +- "session.origin": "origin", +- }, +- "block": {"type": "block_type", "tool": "tool_name", "action": "semantic_type", "session.origin": "origin"}, +- "file": {"path": "path", "session.origin": "origin"}, +- "delegation": { +- "basis": "instruction_tool_use_block_id", +- "mapping_state": "mapping_state", +- "result_status": "result_status", +- "requested_model": "requested_model", +- "dispatch_model": "dispatch_turn_model", +- "child_model": "child_session_dominant_model", +- "session.origin": "parent_origin", +- }, +-} +-_MISSING_GROUP_KEY = "[missing]" +- +- + def _aggregate_group_fields(group_by: str | None) -> tuple[str, ...]: +- return () if group_by is None else tuple(group_by.split(",")) +- +- +-def _aggregate_value(unit: str, field: str, row: Any) -> str | None: +- attribute = _AGGREGATE_ROW_FIELDS.get(unit, {}).get(field) +- if attribute is None: +- supported = ", ".join(sorted(_AGGREGATE_ROW_FIELDS.get(unit, {}))) +- raise ExpressionCompileError( +- f"pipeline `group by {field}` cannot stream {unit} rows; supported fields: {supported}", field=field +- ) +- value = getattr(row, attribute) +- if field == "basis": +- return "action" if value is not None else "edge" +- if value is None: +- return None +- if isinstance(value, bool): +- return str(value).lower() +- return str(value) ++ return () if group_by is None else tuple(field.strip() for field in group_by.split(",") if field.strip()) + + +-def _all_aggregate_rows(ctx: TerminalExecutionContext) -> list[Any]: +- method_name = ctx.descriptor.sql_query_method +- if method_name is None: +- raise ValueError(f"Query unit {ctx.source.unit!r} is not wired to a SQL executor") +- query_method = cast(Any, getattr(ctx.archive, method_name)) +- rows: list[Any] = [] +- page_size = 1000 +- offset = 0 +- while True: +- page = cast( +- Sequence[Any], +- query_method( +- ctx.source.predicate, +- limit=page_size, +- offset=offset, +- session_filters=ctx.session_filters, +- ), ++def _record_result_page( ++ ctx: TerminalExecutionContext, ++ envelope: QueryUnitResultEnvelope, ++ *, ++ selected_rows_exact: int | None = None, ++) -> QueryUnitResultEnvelope: ++ if ctx.execution_context is not None: ++ ctx.execution_context.record_result_page( ++ emitted_rows=len(envelope.items), ++ selected_rows_exact=selected_rows_exact, + ) +- rows.extend(page) +- if len(page) < page_size: +- return rows +- offset += len(page) ++ return envelope + + + def _aggregate_pipeline_payload( +@@ -285,8 +235,8 @@ def _aggregate_pipeline_payload( + *, + group_fields: tuple[str, ...], + denominator: int, +- missing_counts: Counter[str], +- unknown_counts: Counter[str], ++ missing_counts: Mapping[str, int], ++ unknown_counts: Mapping[str, int], + groups: Sequence[tuple[tuple[str, ...], int]], + ) -> dict[str, object]: + payload = pipeline.to_payload() +@@ -338,39 +288,32 @@ def _execute_count_terminal(ctx: TerminalExecutionContext) -> QueryUnitResultEnv + offset=ctx.offset, + session_filters=ctx.session_filters, + ) +- return build_query_unit_aggregate_envelope( +- tuple(QueryUnitAggregateRowPayload.from_row(row) for row in aggregate_rows[: ctx.limit]), +- unit=ctx.source.unit, +- query=ctx.query, +- limit=ctx.limit, +- offset=ctx.caller_offset, +- has_next=len(aggregate_rows) > ctx.limit, +- pipeline=pipeline.to_payload(), +- pipeline_stages=_pipeline_stage_payloads(pipeline), ++ return _record_result_page( ++ ctx, ++ build_query_unit_aggregate_envelope( ++ tuple(QueryUnitAggregateRowPayload.from_row(row) for row in aggregate_rows[: ctx.limit]), ++ unit=ctx.source.unit, ++ query=ctx.query, ++ limit=ctx.limit, ++ offset=ctx.caller_offset, ++ has_next=len(aggregate_rows) > ctx.limit, ++ pipeline=pipeline.to_payload(), ++ pipeline_stages=_pipeline_stage_payloads(pipeline), ++ ), + ) +- rows = _all_aggregate_rows(ctx) +- missing_counts: Counter[str] = Counter() +- unknown_counts: Counter[str] = Counter() +- counts: Counter[tuple[str, ...]] = Counter() +- for row in rows: +- values: list[str] = [] +- for field in group_fields: +- value = _aggregate_value(ctx.source.unit, field, row) +- if value is None: +- missing_counts[field] += 1 +- value = _MISSING_GROUP_KEY +- elif value.lower() == "unknown": +- unknown_counts[field] += 1 +- values.append(value) +- counts[tuple(values)] += 1 +- if not group_fields: +- counts[()] = len(rows) +- ordered_groups = list(counts.items()) +- if aggregate_sort == "key": +- ordered_groups.sort(key=lambda item: item[0], reverse=aggregate_sort_direction == "desc") +- else: +- ordered_groups.sort(key=lambda item: (item[1], item[0]), reverse=aggregate_sort_direction == "desc") +- page_groups = ordered_groups[ctx.offset : ctx.offset + ctx.fetch_limit] ++ aggregate_page = ctx.archive.query_unit_multi_counts( ++ pipeline.source_unit, ++ pipeline.predicate, ++ group_by=group_fields, ++ sort=aggregate_sort, ++ sort_direction=aggregate_sort_direction, ++ limit=ctx.fetch_limit, ++ offset=ctx.offset, ++ session_filters=ctx.session_filters, ++ ) ++ page_groups = [(row.group_values, row.count) for row in aggregate_page.rows] ++ missing_counts = dict(zip(group_fields, aggregate_page.missing_counts, strict=True)) ++ unknown_counts = dict(zip(group_fields, aggregate_page.unknown_counts, strict=True)) + group_by = pipeline.group_by + aggregate_payload_rows = tuple( + QueryUnitAggregateRowPayload( +@@ -385,22 +328,26 @@ def _execute_count_terminal(ctx: TerminalExecutionContext) -> QueryUnitResultEnv + ) + for values, count in page_groups[: ctx.limit] + ) +- return build_query_unit_aggregate_envelope( +- aggregate_payload_rows, +- unit=ctx.source.unit, +- query=ctx.query, +- limit=ctx.limit, +- offset=ctx.caller_offset, +- has_next=len(page_groups) > ctx.limit, +- pipeline=_aggregate_pipeline_payload( +- pipeline, +- group_fields=group_fields, +- denominator=len(rows), +- missing_counts=missing_counts, +- unknown_counts=unknown_counts, +- groups=page_groups[: ctx.limit], ++ return _record_result_page( ++ ctx, ++ build_query_unit_aggregate_envelope( ++ aggregate_payload_rows, ++ unit=ctx.source.unit, ++ query=ctx.query, ++ limit=ctx.limit, ++ offset=ctx.caller_offset, ++ has_next=len(page_groups) > ctx.limit, ++ pipeline=_aggregate_pipeline_payload( ++ pipeline, ++ group_fields=group_fields, ++ denominator=aggregate_page.denominator, ++ missing_counts=missing_counts, ++ unknown_counts=unknown_counts, ++ groups=page_groups[: ctx.limit], ++ ), ++ pipeline_stages=_pipeline_stage_payloads(pipeline), + ), +- pipeline_stages=_pipeline_stage_payloads(pipeline), ++ selected_rows_exact=aggregate_page.denominator, + ) + + +@@ -426,15 +373,18 @@ def _execute_rows_terminal(ctx: TerminalExecutionContext) -> QueryUnitResultEnve + sort_direction=sort_direction, + ), + ) +- return build_query_unit_envelope( +- tuple(payload_model.from_row(row) for row in rows[: ctx.limit]), +- unit=ctx.source.unit, +- query=ctx.query, +- limit=ctx.limit, +- offset=ctx.caller_offset, +- has_next=len(rows) > ctx.limit, +- pipeline=pipeline.to_payload(), +- pipeline_stages=_pipeline_stage_payloads(pipeline), ++ return _record_result_page( ++ ctx, ++ build_query_unit_envelope( ++ tuple(payload_model.from_row(row) for row in rows[: ctx.limit]), ++ unit=ctx.source.unit, ++ query=ctx.query, ++ limit=ctx.limit, ++ offset=ctx.caller_offset, ++ has_next=len(rows) > ctx.limit, ++ pipeline=pipeline.to_payload(), ++ pipeline_stages=_pipeline_stage_payloads(pipeline), ++ ), + ) + + +@@ -459,6 +409,7 @@ def _build_sql_envelope( + caller_offset: int, + fetch_limit: int, + session_filters: Mapping[str, object] | None, ++ execution_context: QueryExecutionContext | None, + ) -> QueryUnitResultEnvelope: + pipeline = source.pipeline + action = pipeline.terminal.action +@@ -480,6 +431,7 @@ def _build_sql_envelope( + caller_offset=caller_offset, + fetch_limit=fetch_limit, + session_filters=session_filters, ++ execution_context=execution_context, + ) + ) + +@@ -492,6 +444,7 @@ def query_unit_rows( + limit: int, + offset: int = 0, + session_filters: Mapping[str, object] | None = None, ++ execution_context: QueryExecutionContext | None = None, + ) -> QueryUnitResultEnvelope: + """Execute an explicit unit-source query.""" + +@@ -515,10 +468,16 @@ def query_unit_rows( + caller_offset=caller_offset, + fetch_limit=fetch_limit, + session_filters=session_filters, ++ execution_context=execution_context, + ) + + +-def query_unit_envelope(archive: ArchiveStore, request: QueryUnitRequest) -> QueryUnitResultEnvelope: ++def query_unit_envelope( ++ archive: ArchiveStore, ++ request: QueryUnitRequest, ++ *, ++ execution_context: QueryExecutionContext | None = None, ++) -> QueryUnitResultEnvelope: + """Execute a compiled terminal query-unit request.""" + + return query_unit_rows( +@@ -528,6 +487,7 @@ def query_unit_envelope(archive: ArchiveStore, request: QueryUnitRequest) -> Que + limit=request.limit, + offset=request.offset, + session_filters=request.session_filters, ++ execution_context=execution_context, + ) + + +diff --git a/polylogue/daemon/http.py b/polylogue/daemon/http.py +index 63da0ce1d2f7af51ecc38669cf3afbe54e5b4f8b..f7780f6238e84a937d3e848eb1496ff6aa4d70b8 100644 +--- a/polylogue/daemon/http.py ++++ b/polylogue/daemon/http.py +@@ -3522,7 +3522,7 @@ class DaemonAPIHandler(BaseHTTPRequestHandler): + try: + payload = execute_archive_read_sync( + archive_root, +- lambda archive: query_unit_envelope(archive, request), ++ lambda archive: query_unit_envelope(archive, request, execution_context=ctx), + ctx=ctx, + read_timeout=_ARCHIVE_READER_BUSY_TIMEOUT_S, + ) +diff --git a/polylogue/mcp/archive_support.py b/polylogue/mcp/archive_support.py +index 87670a3e36bb8a4f18e3806314378d6a1306f675..997120257856b783e25d1671e1dc0676ffc9dca1 100644 +--- a/polylogue/mcp/archive_support.py ++++ b/polylogue/mcp/archive_support.py +@@ -22,6 +22,7 @@ if TYPE_CHECKING: + from collections.abc import Sequence + + from polylogue.archive.blackboard import BlackboardNote ++ from polylogue.archive.query.execution_control import QueryExecutionContext + from polylogue.archive.query.spec import SessionQuerySpec + from polylogue.archive.semantic.content_projection import ContentProjectionSpec + from polylogue.config import Config +@@ -516,6 +517,7 @@ def archive_query_unit_payload( + limit: int, + offset: int, + session_filters: Mapping[str, object] | None = None, ++ execution_context: QueryExecutionContext | None = None, + **filter_params: object, + ) -> QueryUnitResultEnvelope: + """Build the shared terminal query-unit envelope from an archive.""" +@@ -528,7 +530,7 @@ def archive_query_unit_payload( + session_filters=session_filters, + **filter_params, + ) +- return query_unit_envelope(archive, request) ++ return query_unit_envelope(archive, request, execution_context=execution_context) + + + def archive_messages_payload( +diff --git a/polylogue/mcp/server_tools.py b/polylogue/mcp/server_tools.py +index 5c08d927da843bda6c12ca39da18cf748f8440dc..7be9be615eca56e92e4b235e9d6c60235f026d1f 100644 +--- a/polylogue/mcp/server_tools.py ++++ b/polylogue/mcp/server_tools.py +@@ -336,6 +336,7 @@ def register_query_tools(mcp: FastMCP, hooks: ServerCallbacks) -> None: + expression=expression, + limit=hooks.clamp_limit(limit), + offset=max(0, offset), ++ execution_context=ctx, + origin=origin, + exclude_origin=exclude_origin, + tag=tag, +diff --git a/polylogue/storage/sqlite/archive_tiers/archive.py b/polylogue/storage/sqlite/archive_tiers/archive.py +index 1481cf2a84234ee2b591699bb961a557dcb091a8..760cc65f3dc24af2071cad383991483d449cfc1f 100644 +--- a/polylogue/storage/sqlite/archive_tiers/archive.py ++++ b/polylogue/storage/sqlite/archive_tiers/archive.py +@@ -720,6 +720,32 @@ class ArchiveQueryUnitAggregateRow: + count: int + + ++@dataclass(frozen=True, slots=True) ++class ArchiveQueryUnitMultiAggregateRow: ++ """One losslessly addressable row from a multi-field aggregate page.""" ++ ++ unit: str ++ group_by: tuple[str, ...] ++ group_values: tuple[str, ...] ++ count: int ++ ++ ++@dataclass(frozen=True, slots=True) ++class ArchiveQueryUnitMultiAggregatePage: ++ """A bounded multi-field aggregate page plus exact full-result facts. ++ ++ ``rows`` is bounded by the caller's page limit. ``denominator`` and the ++ per-field quality counters describe the complete matching row set rather ++ than merely the returned page, so page boundaries never change aggregate ++ meaning. ++ """ ++ ++ rows: tuple[ArchiveQueryUnitMultiAggregateRow, ...] ++ denominator: int ++ missing_counts: tuple[int, ...] ++ unknown_counts: tuple[int, ...] ++ ++ + def _sql_string_literal(value: str) -> str: + """Return a SQL single-quoted literal for a static in-repo token.""" + +@@ -1006,6 +1032,122 @@ def _query_unit_group_expression(unit: str, row_alias: str, group_by: str | None + raise ValueError(f"unsupported {unit} aggregate group field: {group_by}") from exc + + ++@dataclass(frozen=True, slots=True) ++class _MultiAggregateFieldSQL: ++ """Closed SQL expressions for one multi-field aggregate dimension.""" ++ ++ value: str ++ missing: str ++ unknown: str ++ ++ ++def _query_unit_multi_group_field_sql(unit: str, row_alias: str, field: str) -> _MultiAggregateFieldSQL: ++ """Return lossless value and quality expressions for one group field. ++ ++ Multi-field aggregation historically converted terminal rows back into ++ Python objects, where ``None`` became ``[missing]`` and literal ``unknown`` ++ values remained distinguishable. Keep that contract while moving the work ++ into SQLite; do not conflate an empty string, a missing value, and an ++ explicit unknown token. ++ """ ++ ++ if field == "session.origin": ++ raw = "s.origin" ++ elif field == "session.repo": ++ raw = "s.git_repository_url" ++ else: ++ raw_fields = { ++ "message": { ++ "role": f"{row_alias}.role", ++ "type": f"{row_alias}.message_type", ++ }, ++ "action": { ++ "tool": f"{row_alias}.tool_name", ++ "action": f"{row_alias}.semantic_type", ++ "type": f"{row_alias}.semantic_type", ++ "is_error": f"{row_alias}.is_error", ++ "exit_code": f"{row_alias}.exit_code", ++ "followup_class": f"{row_alias}.followup_class", ++ }, ++ "block": { ++ "type": f"{row_alias}.block_type", ++ "tool": f"{row_alias}.tool_name", ++ "action": f"{row_alias}.semantic_type", ++ }, ++ "file": {"path": f"{row_alias}.path"}, ++ "assertion": { ++ "kind": f"{row_alias}.kind", ++ "status": f"{row_alias}.status", ++ "visibility": f"{row_alias}.visibility", ++ "author_kind": f"{row_alias}.author_kind", ++ }, ++ "observed-event": { ++ "kind": f"{row_alias}.kind", ++ "delivery_state": f"{row_alias}.delivery_state", ++ "tool": f"json_extract({row_alias}.payload_json, '$.tool_name')", ++ "handler": f"json_extract({row_alias}.payload_json, '$.handler_kind')", ++ "status": f"json_extract({row_alias}.payload_json, '$.status')", ++ }, ++ "delegation": { ++ "mapping_state": f"{row_alias}.mapping_state", ++ "result_status": f"{row_alias}.result_status", ++ "requested_model": f"{row_alias}.requested_model", ++ "dispatch_model": f"{row_alias}.dispatch_turn_model", ++ "child_model": f"{row_alias}.child_session_dominant_model", ++ }, ++ } ++ if unit == "delegation" and field == "basis": ++ return _MultiAggregateFieldSQL( ++ value=(f"CASE WHEN {row_alias}.instruction_tool_use_block_id IS NULL THEN 'edge' ELSE 'action' END"), ++ missing="0", ++ unknown="0", ++ ) ++ try: ++ raw = raw_fields[unit][field] ++ except KeyError as exc: ++ raise ValueError(f"unsupported {unit} multi-aggregate group field: {field}") from exc ++ ++ assertion_defaults = { ++ "status": ASSERTION_DEFAULT_STATUS, ++ "visibility": ASSERTION_DEFAULT_VISIBILITY, ++ "author_kind": ASSERTION_DEFAULT_AUTHOR_KIND, ++ } ++ if unit == "assertion" and field in assertion_defaults: ++ value = f"CAST(COALESCE(NULLIF({raw}, ''), {_sql_string_literal(assertion_defaults[field])}) AS TEXT)" ++ missing = "0" ++ else: ++ value = f"CASE WHEN {raw} IS NULL THEN '[missing]' ELSE CAST({raw} AS TEXT) END" ++ missing = f"CASE WHEN {raw} IS NULL THEN 1 ELSE 0 END" ++ unknown = f"CASE WHEN {raw} IS NOT NULL AND LOWER(CAST({raw} AS TEXT)) = 'unknown' THEN 1 ELSE 0 END" ++ return _MultiAggregateFieldSQL(value=value, missing=missing, unknown=unknown) ++ ++ ++def _query_unit_multi_aggregate_order( ++ group_columns: Sequence[str], ++ sort: Literal["count", "key"] | None, ++ direction: Literal["asc", "desc"], ++) -> str: ++ """Return the stable ordering used by the former Python tuple sort.""" ++ ++ sql_direction = _query_unit_order_direction(direction) ++ key_order = ", ".join(f"{column} {sql_direction}" for column in group_columns) ++ if sort == "key": ++ return key_order ++ return f"count {sql_direction}, {key_order}" ++ ++ ++def _append_query_ctes(prefix_sql: str, *ctes: str) -> str: ++ """Append CTEs to an optional relation prefix that already starts WITH.""" ++ ++ body = ",\n".join(cte.strip() for cte in ctes) ++ prefix = prefix_sql.strip() ++ if not prefix: ++ return f"WITH {body}" ++ if not prefix.upper().startswith("WITH "): ++ raise ValueError("query relation prefix must start with WITH") ++ return f"{prefix},\n{body}" ++ ++ + def _predicate_uses_unit_field(predicate: QueryPredicate, field_name: str, *, unit: str | None = None) -> bool: + """Return whether a predicate subtree targets a unit-scoped field.""" + +@@ -1197,6 +1339,22 @@ class ArchiveStore: + """ + self._conn.set_progress_handler(guard, n_opcodes) + ++ def clear_read_progress_guard(self) -> None: ++ """Remove the index connection's progress handler before ownership ends.""" ++ ++ self._conn.set_progress_handler(None, 0) ++ ++ def begin_read_snapshot(self) -> None: ++ """Begin the owned read transaction used by one controlled query call.""" ++ ++ self._conn.execute("BEGIN") ++ ++ def end_read_snapshot(self) -> None: ++ """Release the owned read snapshot without ever committing read work.""" ++ ++ if self._conn.in_transaction: ++ self._conn.rollback() ++ + def interrupt_reads(self) -> None: + """Interrupt any statement active on the index read connection. + +@@ -7386,6 +7544,265 @@ class ArchiveStore: + for row in rows + ] + ++ def query_unit_multi_counts( ++ self, ++ unit: str, ++ predicate: QueryPredicate, ++ *, ++ group_by: Sequence[str], ++ sort: Literal["count", "key"] | None = None, ++ sort_direction: Literal["asc", "desc"] = "desc", ++ limit: int = 50, ++ offset: int = 0, ++ session_filters: Mapping[str, object] | None = None, ++ ) -> ArchiveQueryUnitMultiAggregatePage: ++ """Return one bounded multi-field count page with exact full-set facts. ++ ++ The source relation is filtered and grouped inside one SQLite statement. ++ Python retains at most the requested aggregate page; it never pages the ++ selected row relation with OFFSET and never reconstructs the query ++ algorithm in application memory. ++ """ ++ ++ fields = tuple(str(field).strip() for field in group_by if str(field).strip()) ++ if len(fields) < 2: ++ raise ValueError("multi-field aggregate counts require at least two group fields") ++ normalized_limit = max(int(limit), 0) ++ normalized_offset = max(int(offset), 0) ++ empty_page = ArchiveQueryUnitMultiAggregatePage( ++ rows=(), ++ denominator=0, ++ missing_counts=tuple(0 for _ in fields), ++ unknown_counts=tuple(0 for _ in fields), ++ ) ++ if unit == "assertion" and not self.user_db_path.exists(): ++ return empty_page ++ if unit == "assertion": ++ self._attach_user_tier_if_present() ++ ++ row_alias = { ++ "message": "m", ++ "action": "a", ++ "block": "b", ++ "file": "f", ++ "assertion": "a", ++ "observed-event": "e", ++ "delegation": "d", ++ }.get(unit) ++ if row_alias is None: ++ raise ValueError(f"Query unit {unit!r} is not wired to SQL multi-aggregate counts") ++ ++ active_session_filters = _session_filter_is_active(session_filters) ++ needs_session = ( ++ unit != "observed-event" ++ or active_session_filters ++ or any(_query_unit_group_uses_session(field) for field in fields) ++ or _predicate_uses_session_scope(predicate) ++ ) ++ session_alias = "s" if needs_session else None ++ clause, predicate_params = _structural_predicate_clause( ++ unit, ++ "a" if unit == "file" else row_alias, ++ predicate, ++ session_alias=session_alias, ++ ) ++ where_clause = clause or "1=1" ++ session_clause = "" ++ session_params: list[object] = [] ++ if needs_session and active_session_filters and session_filters is not None: ++ session_clause, session_params = cast(Any, _session_filter_clause)("s", prefix="AND", **session_filters) ++ ++ relation_params: list[object] = [] ++ source_params: list[object] = [] ++ prefix_sql = "" ++ selected_where_clause = where_clause ++ selected_session_clause = session_clause ++ if unit == "file": ++ file_rows_cte = f""" ++ file_rows AS ( ++ SELECT ++ a.session_id, ++ REPLACE(a.tool_path, char(92), '/') AS path ++ FROM actions a ++ JOIN sessions s ON s.session_id = a.session_id ++ JOIN messages m ON m.message_id = a.message_id ++ WHERE a.tool_path IS NOT NULL ++ AND a.tool_path != '' ++ AND {where_clause} ++ {session_clause} ++ GROUP BY a.session_id, path ++ ) ++ """ ++ from_sql = "file_rows f JOIN sessions s ON s.session_id = f.session_id" ++ source_ctes = [file_rows_cte] ++ selected_where_clause = "1=1" ++ selected_session_clause = "" ++ else: ++ source_ctes = [] ++ action_needs_followup = unit == "action" and ( ++ "followup_class" in fields or _action_query_needs_followup_relation(predicate) ++ ) ++ action_relation_name = "actions" ++ if unit == "action": ++ prefix_sql, action_relation_name, relation_params = _action_relation_for_query( ++ predicate=predicate, ++ include_followup=action_needs_followup, ++ ) ++ from_sql_by_unit = { ++ "message": "messages m JOIN sessions s ON s.session_id = m.session_id", ++ "action": f"{action_relation_name} a JOIN sessions s ON s.session_id = a.session_id", ++ "block": "blocks b JOIN sessions s ON s.session_id = b.session_id", ++ "assertion": "user_tier.assertions a LEFT JOIN sessions s ON a.target_ref = 'session:' || s.session_id", ++ "observed-event": "observed_events e JOIN sessions s ON s.session_id = e.session_id", ++ "delegation": "delegations d JOIN sessions s ON s.session_id = d.parent_session_id", ++ } ++ from_sql = "observed_events e" if unit == "observed-event" and not needs_session else from_sql_by_unit[unit] ++ if unit == "observed-event": ++ source_where, source_params = observed_event_source_pushdown(predicate) ++ prefix_sql = observed_event_relation_sql(source_where=source_where) ++ ++ field_sql = tuple(_query_unit_multi_group_field_sql(unit, row_alias, field) for field in fields) ++ group_columns = tuple(f"group_{index}" for index in range(len(fields))) ++ selected_columns = ",\n".join( ++ expression ++ for index, spec in enumerate(field_sql) ++ for expression in ( ++ f"{spec.value} AS group_{index}", ++ f"{spec.missing} AS missing_{index}", ++ f"{spec.unknown} AS unknown_{index}", ++ ) ++ ) ++ grouped_quality_columns = ",\n".join( ++ expression ++ for index in range(len(fields)) ++ for expression in ( ++ f"SUM(missing_{index}) AS group_missing_{index}", ++ f"SUM(unknown_{index}) AS group_unknown_{index}", ++ ) ++ ) ++ ranked_quality_columns = ",\n".join( ++ expression ++ for index in range(len(fields)) ++ for expression in ( ++ f"SUM(group_missing_{index}) OVER () AS total_missing_{index}", ++ f"SUM(group_unknown_{index}) OVER () AS total_unknown_{index}", ++ ) ++ ) ++ stats_columns = ",\n".join( ++ expression ++ for index in range(len(fields)) ++ for expression in ( ++ f"COALESCE(MAX(total_missing_{index}), 0) AS missing_{index}", ++ f"COALESCE(MAX(total_unknown_{index}), 0) AS unknown_{index}", ++ ) ++ ) ++ final_stats_columns = ",\n".join( ++ expression ++ for index in range(len(fields)) ++ for expression in ( ++ f"stats.missing_{index}", ++ f"stats.unknown_{index}", ++ ) ++ ) ++ final_group_columns = ",\n".join(f"page.group_{index}" for index in range(len(fields))) ++ group_column_list = ", ".join(group_columns) ++ order_clause = _query_unit_multi_aggregate_order(group_columns, sort, sort_direction) ++ ++ selected_cte = f""" ++ selected AS ( ++ SELECT ++ {selected_columns} ++ FROM {from_sql} ++ WHERE {selected_where_clause} ++ {selected_session_clause} ++ ) ++ """ ++ grouped_cte = f""" ++ grouped AS ( ++ SELECT ++ {group_column_list}, ++ COUNT(*) AS count, ++ {grouped_quality_columns} ++ FROM selected ++ GROUP BY {group_column_list} ++ ) ++ """ ++ ranked_cte = f""" ++ ranked AS ( ++ SELECT ++ grouped.*, ++ SUM(count) OVER () AS denominator, ++ {ranked_quality_columns}, ++ ROW_NUMBER() OVER (ORDER BY {order_clause}) AS ordinal ++ FROM grouped ++ ) ++ """ ++ page_cte = """ ++ page AS ( ++ SELECT * ++ FROM ranked ++ WHERE ordinal > ? AND ordinal <= ? ++ ) ++ """ ++ stats_cte = f""" ++ stats AS ( ++ SELECT ++ COALESCE(MAX(denominator), 0) AS denominator, ++ {stats_columns} ++ FROM ranked ++ ) ++ """ ++ cte_sql = _append_query_ctes( ++ prefix_sql, ++ *source_ctes, ++ selected_cte, ++ grouped_cte, ++ ranked_cte, ++ page_cte, ++ stats_cte, ++ ) ++ rows = self._conn.execute( ++ f""" ++ {cte_sql} ++ SELECT ++ stats.denominator, ++ {final_stats_columns}, ++ {final_group_columns}, ++ page.count, ++ page.ordinal ++ FROM stats ++ LEFT JOIN page ON 1 = 1 ++ ORDER BY page.ordinal ++ """, ++ [ ++ *relation_params, ++ *source_params, ++ *predicate_params, ++ *session_params, ++ normalized_offset, ++ normalized_offset + normalized_limit, ++ ], ++ ).fetchall() ++ if not rows: ++ return empty_page ++ stats_row = rows[0] ++ aggregate_rows = tuple( ++ ArchiveQueryUnitMultiAggregateRow( ++ unit=unit, ++ group_by=fields, ++ group_values=tuple(str(row[f"group_{index}"]) for index in range(len(fields))), ++ count=int(row["count"]), ++ ) ++ for row in rows ++ if row["count"] is not None ++ ) ++ return ArchiveQueryUnitMultiAggregatePage( ++ rows=aggregate_rows, ++ denominator=int(stats_row["denominator"]), ++ missing_counts=tuple(int(stats_row[f"missing_{index}"]) for index in range(len(fields))), ++ unknown_counts=tuple(int(stats_row[f"unknown_{index}"]) for index in range(len(fields))), ++ ) ++ + def query_actions( + self, + predicate: QueryPredicate, +diff --git a/tests/unit/archive/query/test_execution_control.py b/tests/unit/archive/query/test_execution_control.py +index 96ffa1fb598353bbbf18ad1637c6722af68fe937..30ba5c47a65d36609139d27d79628b00df5504f3 100644 +--- a/tests/unit/archive/query/test_execution_control.py ++++ b/tests/unit/archive/query/test_execution_control.py +@@ -26,6 +26,7 @@ from polylogue.archive.query.execution_control import ( + QueryCancelledError, + QueryExecutionContext, + QueryTimeoutError, ++ QueryWorkBudgetExceededError, + execute_archive_read, + execute_archive_read_sync, + ) +@@ -356,6 +357,100 @@ def test_sync_deadline_aborts_for_http_route(tmp_path: Path) -> None: + assert ctx.receipt.state == "timed_out" + + ++def test_runner_holds_one_read_snapshot_until_owned_cleanup(tmp_path: Path) -> None: ++ """All statements in one controlled call observe one SQLite snapshot. ++ ++ Removing ``begin_read_snapshot`` makes the second SELECT observe the ++ concurrently committed row, while retaining the same connection alone is ++ insufficient under autocommit. ++ """ ++ ++ import sqlite3 ++ from hashlib import sha256 ++ ++ root = _bootstrap_archive(tmp_path) ++ with ArchiveStore(root) as store: ++ store._conn.execute( ++ "INSERT INTO sessions (native_id, origin, content_hash) VALUES (?, ?, ?)", ++ ("seed", "codex-session", sha256(b"seed").digest()), ++ ) ++ store._conn.commit() ++ ++ first_read = threading.Event() ++ writer_done = threading.Event() ++ writer_errors: list[BaseException] = [] ++ owned_stores: list[ArchiveStore] = [] ++ ++ def _writer() -> None: ++ try: ++ assert first_read.wait(timeout=5) ++ with sqlite3.connect(root / "index.db", timeout=5.0) as conn: ++ conn.execute( ++ "INSERT INTO sessions (native_id, origin, content_hash) VALUES (?, ?, ?)", ++ ("concurrent", "codex-session", sha256(b"concurrent").digest()), ++ ) ++ except BaseException as exc: # propagate worker failures to the test thread ++ writer_errors.append(exc) ++ finally: ++ writer_done.set() ++ ++ def _read_twice(store: ArchiveStore) -> tuple[int, int]: ++ owned_stores.append(store) ++ first = int(store._conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]) ++ first_read.set() ++ assert writer_done.wait(timeout=5) ++ second = int(store._conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]) ++ return first, second ++ ++ writer = threading.Thread(target=_writer) ++ writer.start() ++ ctx = QueryExecutionContext.create(query_text="stable-snapshot", timeout_s=10.0) ++ observed = execute_archive_read_sync( ++ root, ++ _read_twice, ++ ctx=ctx, ++ controller=QueryAdmissionController(), ++ ) ++ writer.join(timeout=5) ++ ++ assert not writer_errors ++ assert observed == (1, 1) ++ with sqlite3.connect(root / "index.db") as conn: ++ assert int(conn.execute("SELECT COUNT(*) FROM sessions").fetchone()[0]) == 2 ++ assert ctx.receipt.state == "completed" ++ assert ctx.receipt.cleanup_complete is True ++ with pytest.raises(sqlite3.ProgrammingError, match="closed"): ++ owned_stores[0]._conn.execute("SELECT 1") ++ ++ ++def test_sqlite_vm_work_budget_interrupts_deterministically_and_cleans_up(tmp_path: Path) -> None: ++ """Production dependencies: progress accounting, budget guard, typed abort. ++ ++ Removing ``record_sqlite_progress`` or the budget branch in ``_abort_error`` ++ changes this into a deadline timeout; removing cleanup leaves the receipt ++ false and the admission weight held. ++ """ ++ ++ root = _bootstrap_archive(tmp_path) ++ ctx = QueryExecutionContext.create( ++ query_text="budgeted-expensive", ++ timeout_s=5.0, ++ sqlite_vm_step_budget=10_000, ++ ) ++ controller = QueryAdmissionController() ++ ++ with pytest.raises(QueryWorkBudgetExceededError): ++ execute_archive_read_sync(root, _expensive_work, ctx=ctx, controller=controller) ++ ++ assert ctx.receipt.state == "work_budget_exceeded" ++ assert ctx.receipt.interrupted is True ++ assert ctx.receipt.sqlite_progress_callbacks == 5 ++ assert ctx.receipt.sqlite_vm_steps_lower_bound == 10_000 ++ assert ctx.receipt.sqlite_vm_step_budget == 10_000 ++ assert ctx.receipt.cleanup_complete is True ++ assert controller.in_flight_weight == 0 ++ ++ + def test_interrupt_before_first_opcode_never_starts_statement(tmp_path: Path) -> None: + root = _bootstrap_archive(tmp_path) + ctx = QueryExecutionContext.create(query_text="pre-cancelled", timeout_s=None) +@@ -402,6 +497,65 @@ async def test_api_query_units_routes_through_execution_control( + assert seen == ["api.query_units"] + + ++async def test_api_multi_aggregate_receipt_reports_real_work_selection_and_delivery( ++ tmp_path: Path, ++ monkeypatch: pytest.MonkeyPatch, ++) -> None: ++ """The public route passes its one outer context into the SQL executor. ++ ++ Removing surface-to-executor context propagation leaves page/selection ++ counters at zero; replacing the progress handler with a synthetic test ++ counter leaves the production VM-work fields at zero. ++ """ ++ ++ from polylogue import Polylogue ++ from polylogue.archive.query import execution_control as ec ++ from polylogue.surfaces.payloads import QueryUnitAggregateEnvelope ++ from tests.infra.storage_records import SessionBuilder ++ ++ _bootstrap_archive(tmp_path) ++ ( ++ SessionBuilder(tmp_path / "index.db", "receipt") ++ .provider("claude-code") ++ .git_repository_url("polylogue") ++ .add_message("receipt-user", role="user", text="receipt aggregate") ++ .add_message("receipt-assistant-1", role="assistant", text="receipt aggregate") ++ .add_message("receipt-assistant-2", role="assistant", text="receipt aggregate") ++ .save() ++ ) ++ monkeypatch.setattr(ec, "PROGRESS_GUARD_OPCODES", 50) ++ seen: list[QueryExecutionContext] = [] ++ original_run = InterruptibleSQLiteRead.run ++ ++ def _recording_run(self: InterruptibleSQLiteRead, *args: object, **kwargs: object) -> object: ++ seen.append(self._ctx) ++ return original_run(self, *args, **kwargs) # type: ignore[arg-type] ++ ++ monkeypatch.setattr(InterruptibleSQLiteRead, "run", _recording_run) ++ ++ archive = Polylogue(archive_root=tmp_path, db_path=tmp_path / "index.db") ++ try: ++ envelope = await archive.query_units( ++ "messages where text:receipt | group by role, session.repo | count | sort by count desc" ++ ) ++ finally: ++ await archive.close() ++ ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(row.count, row.group_key) for row in envelope.items] ++ assert len(seen) == 1 ++ ctx = seen[0] ++ assert ctx.owner_ref == "api.query_units" ++ assert ctx.workload_class == "scan" ++ assert ctx.receipt.state == "completed" ++ assert ctx.receipt.selected_rows_exact == 3 ++ assert ctx.receipt.rows_emitted == 2 ++ assert ctx.receipt.result_pages_emitted == 1 ++ assert ctx.receipt.sqlite_progress_callbacks > 0 ++ assert ctx.receipt.sqlite_vm_steps_lower_bound == ctx.receipt.sqlite_progress_callbacks * 50 ++ assert ctx.receipt.cleanup_complete is True ++ ++ + def test_queued_deadline_expiry_reports_timeout_not_cancellation() -> None: + """A deadline that expires while waiting for capacity is a timeout — + surfaces map QueryTimeoutError to 503, not a generic failure.""" +@@ -519,3 +673,108 @@ async def test_api_query_units_classifies_aggregate_as_scan(tmp_path: Path, monk + await archive.close() + + assert seen == ["scan"] ++ ++ ++def test_exact_session_multi_aggregate_work_is_not_amplified_by_irrelevant_growth( ++ tmp_path: Path, ++ monkeypatch: pytest.MonkeyPatch, ++) -> None: ++ """C-03 through the shared runner and multi-aggregate production route. ++ ++ The semantic result remains identical when the exact-session action bound is ++ removed, but the real SQLite VM-work receipt crosses the canary budget. This ++ kills a global-first relation mutation without duplicating query logic in a ++ test-side counter. ++ """ ++ ++ from hashlib import sha256 ++ ++ from polylogue.archive.message.roles import Role ++ from polylogue.archive.query import execution_control as ec ++ from polylogue.archive.query.expression import parse_unit_source_expression ++ from polylogue.archive.query.unit_results import query_unit_rows ++ from polylogue.core.enums import BlockType, Origin ++ from polylogue.surfaces.payloads import QueryUnitAggregateEnvelope ++ ++ root = tmp_path / "archive" ++ target_session_id = "codex-session:target" ++ session_rows: list[tuple[str, str, bytes]] = [] ++ message_rows: list[tuple[str, str, int, str, str, bytes]] = [] ++ block_rows: list[tuple[str, str, int, str, str | None, str, str | None, str | None]] = [] ++ for index in range(512): ++ native_id = "target" if index == 0 else f"irrelevant-{index:04d}" ++ session_id = f"codex-session:{native_id}" ++ message_id = f"{session_id}:m1" ++ tool_id = f"tool-{index:04d}" ++ session_rows.append((native_id, Origin.CODEX_SESSION.value, sha256(session_id.encode()).digest())) ++ message_rows.append( ++ (session_id, "m1", 0, Role.ASSISTANT.value, "message", sha256(message_id.encode()).digest()) ++ ) ++ block_rows.extend( ++ ( ++ (message_id, session_id, 0, BlockType.TOOL_USE.value, "Bash", tool_id, "{}", "shell"), ++ (message_id, session_id, 1, BlockType.TOOL_RESULT.value, None, tool_id, None, None), ++ ) ++ ) ++ ++ with ArchiveStore(root) as facade: ++ facade._conn.executemany( ++ "INSERT INTO sessions (native_id, origin, content_hash) VALUES (?, ?, ?)", ++ session_rows, ++ ) ++ facade._conn.executemany( ++ """ ++ INSERT INTO messages (session_id, native_id, position, role, message_type, content_hash) ++ VALUES (?, ?, ?, ?, ?, ?) ++ """, ++ message_rows, ++ ) ++ facade._conn.executemany( ++ """ ++ INSERT INTO blocks ( ++ message_id, session_id, position, block_type, tool_name, tool_id, tool_input, semantic_type ++ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ++ """, ++ block_rows, ++ ) ++ facade._conn.commit() ++ ++ source = parse_unit_source_expression( ++ f"actions where session.id:{target_session_id} | group by tool, session.origin | count" ++ ) ++ assert source is not None ++ monkeypatch.setattr(ec, "PROGRESS_GUARD_OPCODES", 100) ++ ++ def execute(ctx: QueryExecutionContext) -> QueryUnitAggregateEnvelope: ++ result = execute_archive_read_sync( ++ root, ++ lambda archive: query_unit_rows( ++ archive, ++ source, ++ query="c03-multi-aggregate", ++ limit=10, ++ execution_context=ctx, ++ ), ++ ctx=ctx, ++ controller=QueryAdmissionController(), ++ ) ++ assert isinstance(result, QueryUnitAggregateEnvelope) ++ return result ++ ++ bounded_ctx = QueryExecutionContext.create(query_text="c03-bounded", workload_class="scan", timeout_s=10.0) ++ bounded = execute(bounded_ctx) ++ ++ monkeypatch.setattr( ++ "polylogue.storage.sqlite.archive_tiers.archive._action_relation_for_query", ++ lambda **_kwargs: ("", "actions", []), ++ ) ++ mutant_ctx = QueryExecutionContext.create(query_text="c03-global-first", workload_class="scan", timeout_s=10.0) ++ mutant = execute(mutant_ctx) ++ ++ assert mutant.model_dump(mode="json") == bounded.model_dump(mode="json") ++ assert bounded_ctx.receipt.selected_rows_exact == 1 ++ assert bounded_ctx.receipt.rows_emitted == 1 ++ assert bounded_ctx.receipt.result_pages_emitted == 1 ++ assert bounded_ctx.receipt.cleanup_complete is True ++ assert bounded_ctx.receipt.sqlite_vm_steps_lower_bound < 50_000 ++ assert mutant_ctx.receipt.sqlite_vm_steps_lower_bound >= 50_000 +diff --git a/tests/unit/archive/test_query_multi_aggregate.py b/tests/unit/archive/test_query_multi_aggregate.py +index 37e533b460f1355465df75dee8f4f13e915c117a..c694880c18a1b628186cf59e2aeb34a5578e4ae5 100644 +--- a/tests/unit/archive/test_query_multi_aggregate.py ++++ b/tests/unit/archive/test_query_multi_aggregate.py +@@ -83,3 +83,393 @@ def test_multi_field_group_count_reports_proportions_and_denominator(workspace_e + def test_multi_field_group_rejects_each_unsupported_field() -> None: + with pytest.raises(ExpressionCompileError, match=r"group by nope.*action rows"): + parse_unit_source_expression("actions where tool:bash | group by tool, nope | count") ++ ++ ++def _aggregate_page_rows(envelope: QueryUnitAggregateEnvelope) -> list[tuple[tuple[str, str], int]]: ++ return [ ++ ( ++ ( ++ cast(dict[str, str], json.loads(row.group_key))["role"], ++ cast(dict[str, str], json.loads(row.group_key))["session.repo"], ++ ), ++ row.count, ++ ) ++ for row in envelope.items ++ if row.group_key is not None ++ ] ++ ++ ++def test_multi_field_group_pages_concatenate_to_exact_stable_result( ++ workspace_env: dict[str, Path], ++) -> None: ++ """Production dependency: ArchiveStore.query_unit_multi_counts. ++ ++ Replacing the SQL page with the former all-row Python materializer loses the ++ out-of-range page's exact denominator/quality facts and restores unbounded ++ selected-row retention. ++ """ ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ ( ++ SessionBuilder(index_db, "repo-a") ++ .provider("claude-code") ++ .git_repository_url("repo-a") ++ .add_message("a-user-1", role="user", text="aggregate") ++ .add_message("a-user-2", role="user", text="aggregate") ++ .add_message("a-assistant", role="assistant", text="aggregate") ++ .save() ++ ) ++ ( ++ SessionBuilder(index_db, "repo-b") ++ .provider("codex") ++ .git_repository_url("repo-b") ++ .add_message("b-assistant-1", role="assistant", text="aggregate") ++ .add_message("b-assistant-2", role="assistant", text="aggregate") ++ .add_message("b-system", role="system", text="aggregate") ++ .save() ++ ) ++ ( ++ SessionBuilder(index_db, "missing-repo") ++ .provider("chatgpt") ++ .git_repository_url(None) ++ .add_message("missing-user", role="user", text="aggregate") ++ .save() ++ ) ++ ( ++ SessionBuilder(index_db, "unknown-repo") ++ .provider("claude-code") ++ .git_repository_url("unknown") ++ .add_message("unknown-assistant", role="assistant", text="aggregate") ++ .save() ++ ) ++ source = parse_unit_source_expression( ++ "messages where text:aggregate | group by role, session.repo | count | sort by count desc" ++ ) ++ assert source is not None ++ ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ full = query_unit_rows(archive, source, query="multi-aggregate-full", limit=100) ++ assert isinstance(full, QueryUnitAggregateEnvelope) ++ expected = _aggregate_page_rows(full) ++ ++ concatenated: list[tuple[tuple[str, str], int]] = [] ++ offset = 0 ++ while True: ++ page = query_unit_rows( ++ archive, ++ source, ++ query="multi-aggregate-page", ++ limit=2, ++ offset=offset, ++ ) ++ assert isinstance(page, QueryUnitAggregateEnvelope) ++ assert page.pipeline is not None ++ result = cast(dict[str, object], page.pipeline["result"]) ++ assert result["denominator"] == {"kind": "all_matching_rows", "n": 8} ++ assert result["missing_counts"] == {"role": 0, "session.repo": 1} ++ assert result["unknown_counts"] == {"role": 0, "session.repo": 1} ++ concatenated.extend(_aggregate_page_rows(page)) ++ if page.next_offset is None: ++ break ++ offset = page.next_offset ++ ++ beyond = query_unit_rows( ++ archive, ++ source, ++ query="multi-aggregate-beyond", ++ limit=2, ++ offset=100, ++ ) ++ ++ assert concatenated == expected ++ assert sum(count for _, count in concatenated) == 8 ++ assert isinstance(beyond, QueryUnitAggregateEnvelope) ++ assert beyond.items == () ++ assert beyond.next_offset is None ++ assert beyond.pipeline is not None ++ beyond_result = cast(dict[str, object], beyond.pipeline["result"]) ++ assert beyond_result["denominator"] == {"kind": "all_matching_rows", "n": 8} ++ assert beyond_result["missing_counts"] == {"role": 0, "session.repo": 1} ++ assert beyond_result["unknown_counts"] == {"role": 0, "session.repo": 1} ++ ++ ++def test_multi_field_group_does_not_call_terminal_row_query( ++ workspace_env: dict[str, Path], ++ monkeypatch: pytest.MonkeyPatch, ++) -> None: ++ """Restoring ``_all_aggregate_rows`` makes this fail on ``query_messages``.""" ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ ( ++ SessionBuilder(index_db, "sql-only") ++ .provider("claude-code") ++ .git_repository_url("repo") ++ .add_message("sql-only-message", role="assistant", text="aggregate") ++ .save() ++ ) ++ source = parse_unit_source_expression("messages where text:aggregate | group by role, session.repo | count") ++ assert source is not None ++ ++ def _row_materialization_mutant(*_args: object, **_kwargs: object) -> object: ++ raise AssertionError("multi-field aggregation called the terminal row-page method") ++ ++ monkeypatch.setattr(ArchiveStore, "query_messages", _row_materialization_mutant) ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ envelope = query_unit_rows(archive, source, query="multi-aggregate-sql-only", limit=10) ++ ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(json.loads(row.group_key or "{}"), row.count) for row in envelope.items] == [ ++ ({"role": "assistant", "session.repo": "repo"}, 1) ++ ] ++ ++ ++def test_file_multi_field_group_uses_lossless_file_relation(workspace_env: dict[str, Path]) -> None: ++ """The file unit keeps its de-duplicated session/path grain inside SQLite.""" ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ ( ++ SessionBuilder(index_db, "file-multi") ++ .provider("claude-code") ++ .git_repository_url("polylogue") ++ .add_message( ++ "file-edit-1", ++ role="assistant", ++ text="aggregate file", ++ blocks=[ ++ { ++ "type": "tool_use", ++ "tool_name": "Edit", ++ "tool_id": "edit-1", ++ "input": {"file_path": "polylogue/archive/query/unit_results.py"}, ++ "semantic_type": "file_edit", ++ } ++ ], ++ ) ++ .add_message( ++ "file-edit-2", ++ role="assistant", ++ text="aggregate file again", ++ blocks=[ ++ { ++ "type": "tool_use", ++ "tool_name": "Edit", ++ "tool_id": "edit-2", ++ "input": {"file_path": "polylogue/archive/query/unit_results.py"}, ++ "semantic_type": "file_edit", ++ } ++ ], ++ ) ++ .save() ++ ) ++ source = parse_unit_source_expression( ++ "files where action:file_edit | group by path, session.repo | count | sort by key asc" ++ ) ++ assert source is not None ++ ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ envelope = query_unit_rows(archive, source, query="file-multi", limit=10) ++ ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(json.loads(row.group_key or "{}"), row.count) for row in envelope.items] == [ ++ ( ++ { ++ "path": "polylogue/archive/query/unit_results.py", ++ "session.repo": "polylogue", ++ }, ++ 1, ++ ) ++ ] ++ assert envelope.pipeline is not None ++ result = cast(dict[str, object], envelope.pipeline["result"]) ++ assert result["denominator"] == {"kind": "all_matching_rows", "n": 1} ++ ++ ++def test_multi_field_group_distinguishes_missing_empty_and_explicit_unknown( ++ workspace_env: dict[str, Path], ++) -> None: ++ """Lossless group keys must not collapse three different data states. ++ ++ Replacing the closed field expressions with ``COALESCE(NULLIF(...), ++ 'unknown')`` makes the three rows merge and falsifies both quality counters. ++ """ ++ ++ import sqlite3 ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ for native_id, repository_url in ( ++ ("missing", None), ++ ("empty", "temporary"), ++ ("explicit-unknown", "unknown"), ++ ): ++ ( ++ SessionBuilder(index_db, native_id) ++ .provider("claude-code") ++ .git_repository_url(repository_url) ++ .add_message(f"{native_id}-message", role="assistant", text="quality-state") ++ .save() ++ ) ++ with sqlite3.connect(index_db) as conn: ++ conn.execute( ++ "UPDATE sessions SET git_repository_url = '' WHERE native_id = ? AND origin = ?", ++ ("ext-empty", "claude-code-session"), ++ ) ++ ++ source = parse_unit_source_expression( ++ "messages where text:quality-state | group by role, session.repo | count | sort by key asc" ++ ) ++ assert source is not None ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ envelope = query_unit_rows(archive, source, query="quality-states", limit=10) ++ ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(json.loads(row.group_key or "{}"), row.count) for row in envelope.items] == [ ++ ({"role": "assistant", "session.repo": ""}, 1), ++ ({"role": "assistant", "session.repo": "[missing]"}, 1), ++ ({"role": "assistant", "session.repo": "unknown"}, 1), ++ ] ++ assert envelope.pipeline is not None ++ result = cast(dict[str, object], envelope.pipeline["result"]) ++ assert result["denominator"] == {"kind": "all_matching_rows", "n": 3} ++ assert result["missing_counts"] == {"role": 0, "session.repo": 1} ++ assert result["unknown_counts"] == {"role": 0, "session.repo": 1} ++ ++ ++def test_multi_field_sql_lowerer_covers_action_block_event_and_delegation_relations( ++ workspace_env: dict[str, Path], ++) -> None: ++ """Each non-file derived relation stays inside the shared SQL lowerer. ++ ++ Removing the action-bound CTE, observed-event source CTE composition, or ++ delegation-view field mapping makes the corresponding real query fail. ++ """ ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ ( ++ SessionBuilder(index_db, "derived-relations") ++ .provider("claude-code") ++ .git_repository_url("polylogue") ++ .add_message( ++ "derived-actions", ++ role="assistant", ++ text="derived relation evidence", ++ blocks=[ ++ { ++ "type": "tool_use", ++ "tool_name": "Bash", ++ "tool_id": "bash-1", ++ "input": {"command": "pytest -q"}, ++ "semantic_type": "shell", ++ }, ++ { ++ "type": "tool_result", ++ "tool_id": "bash-1", ++ "text": "passed", ++ "is_error": False, ++ "exit_code": 0, ++ }, ++ { ++ "type": "tool_use", ++ "tool_name": "Task", ++ "tool_id": "task-1", ++ "input": {"prompt": "review", "model": "sonnet"}, ++ "semantic_type": "subagent", ++ }, ++ { ++ "type": "tool_result", ++ "tool_id": "task-1", ++ "text": "done", ++ "is_error": False, ++ "exit_code": 0, ++ }, ++ ], ++ ) ++ .save() ++ ) ++ session_id = "claude-code-session:ext-derived-relations" ++ cases = ( ++ ( ++ f"actions where session.id:{session_id} AND tool:Bash " ++ "| group by tool, session.repo | count | sort by key asc", ++ [({"tool": "Bash", "session.repo": "polylogue"}, 1)], ++ ), ++ ( ++ f"blocks where session.id:{session_id} AND type:tool_use " ++ "| group by type, session.repo | count | sort by key asc", ++ [({"type": "tool_use", "session.repo": "polylogue"}, 2)], ++ ), ++ ( ++ f"observed-events where session.id:{session_id} AND kind:tool_finished " ++ "| group by tool, session.repo | count | sort by key asc", ++ [ ++ ({"tool": "Bash", "session.repo": "polylogue"}, 1), ++ ({"tool": "Task", "session.repo": "polylogue"}, 1), ++ ], ++ ), ++ ( ++ "delegations where mapping_state:unresolved | group by basis, session.repo | count | sort by key asc", ++ [({"basis": "action", "session.repo": "polylogue"}, 1)], ++ ), ++ ) ++ ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ for expression, expected in cases: ++ source = parse_unit_source_expression(expression) ++ assert source is not None ++ envelope = query_unit_rows(archive, source, query=expression, limit=10) ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(json.loads(row.group_key or "{}"), row.count) for row in envelope.items] == expected ++ assert envelope.pipeline is not None ++ result = cast(dict[str, object], envelope.pipeline["result"]) ++ assert result["denominator"] == { ++ "kind": "all_matching_rows", ++ "n": sum(count for _, count in expected), ++ } ++ ++ ++def test_multi_field_sql_lowerer_covers_assertion_defaults_and_session_join( ++ workspace_env: dict[str, Path], ++) -> None: ++ """Assertion defaults and owning-session metadata are grouped in SQLite.""" ++ ++ import sqlite3 ++ ++ from polylogue.core.enums import AssertionKind ++ from polylogue.storage.sqlite.archive_tiers.user_write import upsert_assertion ++ ++ index_db = workspace_env["archive_root"] / "index.db" ++ ( ++ SessionBuilder(index_db, "assertion-multi") ++ .provider("claude-code") ++ .git_repository_url("polylogue") ++ .add_message("assertion-source", role="user", text="record caveat") ++ .save() ++ ) ++ session_id = "claude-code-session:ext-assertion-multi" ++ with sqlite3.connect(index_db.parent / "user.db") as conn: ++ upsert_assertion( ++ conn, ++ assertion_id="assertion-multi-1", ++ target_ref=f"session:{session_id}", ++ kind=AssertionKind.CAVEAT, ++ body_text="review this caveat", ++ author_ref="user:test", ++ author_kind="user", ++ evidence_refs=[session_id], ++ now_ms=1_700_000_000_000, ++ ) ++ ++ source = parse_unit_source_expression( ++ "assertions where kind:caveat | group by status, session.repo | count | sort by key asc" ++ ) ++ assert source is not None ++ with ArchiveStore.open_existing(index_db.parent) as archive: ++ envelope = query_unit_rows(archive, source, query="assertion-multi", limit=10) ++ ++ assert isinstance(envelope, QueryUnitAggregateEnvelope) ++ assert [(json.loads(row.group_key or "{}"), row.count) for row in envelope.items] == [ ++ ({"status": "active", "session.repo": "polylogue"}, 1) ++ ] ++ assert envelope.pipeline is not None ++ result = cast(dict[str, object], envelope.pipeline["result"]) ++ assert result["missing_counts"] == {"status": 0, "session.repo": 0} ++ assert result["unknown_counts"] == {"status": 0, "session.repo": 0} diff --git a/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/TESTS.md b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/TESTS.md new file mode 100644 index 000000000..c8fadd82f --- /dev/null +++ b/.agent/handoffs/external-agent-campaigns/2026-07-16-gpt-pro-wave/testdiet/results/testdiet-05/r01/extracted/TESTS.md @@ -0,0 +1,276 @@ +# testdiet-05 test design and execution record + +## Test strategy + +The tests use real `ArchiveStore` SQLite files, current query parsing/lowering, current repository/facade adapters, and current public API/MCP/HTTP/CLI routes. They avoid a test-only aggregate implementation or a wall-clock-only work proxy. + +The strongest new laws are: + +1. multi-field aggregates never invoke the terminal row method and therefore cannot restore `_all_aggregate_rows` unnoticed; +2. aggregate pages concatenate to the exact stable group sequence and retain exact full-set facts on every page, including an out-of-range page; +3. exact-session action aggregation has identical semantics under irrelevant archive growth while production VM-work counters remain bounded; +4. a global-first action-relation mutation preserves output but crosses the declared work threshold; +5. a VM-work budget terminates at a deterministic progress-callback boundary with a typed state and complete cleanup; +6. two statements in one controlled call observe one read snapshot while a concurrent writer commits; +7. the shared API route records actual SQLite progress, exact selected rows, one logical page, emitted rows, and cleanup; +8. lowerer tests exercise all supported SQL relation families and the distinct states `NULL`, empty string, and literal `unknown`. + +## Production dependencies and anti-vacuity mutations + +### Work amplification + +**Production dependency:** `ArchiveStore.query_unit_multi_counts`, `_action_relation_for_query`, and the shared `unit_results` count executor. + +**Mutation A:** restore `_all_aggregate_rows` and page all selected terminal rows into Python before grouping. + +**Expected failure:** `test_multi_field_group_does_not_materialize_terminal_rows` replaces the descriptor's row query method with a function that raises. The SQL aggregate must still succeed; the restored loop raises immediately. + +**Mutation B:** change the exact-session action relation back to a global-first action ranking/pairing relation. + +**Expected failure:** `test_exact_session_multi_aggregate_work_is_not_amplified_by_irrelevant_growth` still sees the same one-row aggregate but the production receipt's VM-step lower bound rises from below 50,000 to at least 50,000. The pre-existing C-03 storage canary fails on the same mutation. + +### Lossless page semantics + +**Production dependency:** grouped/ranked/page/stats CTEs, stable aggregate ordering, existing `limit + 1` lookahead, and pipeline result metadata. + +**Mutation:** apply `LIMIT/OFFSET` before grouping, omit the full-ranked stats CTE, sort ties without all group keys, or return page-local denominator/quality counts. + +**Expected failure:** `test_multi_field_group_pages_concatenate_losslessly_with_exact_facts` fails because page concatenation differs, `has_next` is wrong, or denominator/missing/unknown facts change across pages or disappear beyond the final page. + +### Data-state conservation + +**Production dependency:** closed SQL field expressions in `_query_unit_multi_group_field_sql`. + +**Mutation:** replace the expressions with `COALESCE(NULLIF(value, ''), 'unknown')`. + +**Expected failure:** `test_multi_field_group_distinguishes_missing_empty_and_explicit_unknown` merges three semantic states and reports false missing/unknown counts. + +### Relation-specific semantics + +**Production dependencies:** file de-duplication CTE, selective action CTE, observed-event source/materialized union, delegation view, assertion defaults, and session join. + +**Mutations and failures:** + +- remove file `(session_id, path)` grouping: duplicate edits count as two in `test_file_multi_field_group_uses_lossless_file_relation`; +- bypass the selective action relation: exact-session work canary crosses its threshold; +- omit observed-event CTE composition: observed-event aggregate query fails or returns no source-derived events; +- map delegation `basis` directly from a nullable block id: action/edge grouping differs; +- omit assertion defaults or session join: `test_multi_field_sql_lowerer_covers_assertion_defaults_and_session_join` returns missing/default mismatch or no repository group. + +### Ignored cancellation or work budget + +**Production dependencies:** SQLite progress handler, `QueryExecutionContext.record_sqlite_progress`, `abort_reason`, `_abort_error`, and exact connection interruption. + +**Mutation:** make the progress guard always return zero, stop recording callbacks, remove the work-budget branch, or interrupt a different/shared connection. + +**Expected failure:** `test_sqlite_vm_work_budget_interrupts_deterministically_and_cleans_up` no longer raises `QueryWorkBudgetExceededError` at exactly five 2,000-opcode callbacks, reports the wrong terminal state, or misses cleanup. Existing cancellation/deadline/heartbeat/disconnect tests exceed their SLO or report a wrong state when cancellation/interrupt wiring is removed. + +### Snapshot and leaked state + +**Production dependencies:** `begin_read_snapshot`, `end_read_snapshot`, progress-handler removal, `ArchiveStore.close`, and admission release. + +**Mutation A:** remove `begin_read_snapshot` while retaining the same connection. + +**Expected failure:** `test_runner_holds_one_read_snapshot_until_owned_cleanup` observes `(1, 2)` instead of `(1, 1)` after the concurrent writer commits. + +**Mutation B:** skip rollback, progress-handler removal, connection close, cleanup receipt, or admission release. + +**Expected failure:** the retained store remains queryable instead of raising `sqlite3.ProgrammingError`; `cleanup_complete` remains false; or `in_flight_weight` remains nonzero. Existing exact-once release tests also catch permit underflow/double release. + +### Context propagation and double-counted progress + +**Production dependencies:** API/MCP/HTTP adapters pass the same outer context to `query_unit_envelope`, and the shared executor calls `_record_result_page` once. + +**Mutation A:** omit the context argument at the facade or adapter. + +**Expected failure:** `test_api_multi_aggregate_receipt_reports_actual_work_and_delivery` returns correct rows but has zero progress/pages/emitted rows and no exact selected count. + +**Mutation B:** call `_record_result_page` twice or record both fetch-limit rows and response rows. + +**Expected failure:** the same test reports two pages or four emitted rows instead of one page and two rows. This is the explicit double-counted-progress survivor. + +### Depth-limit mutation + +The architecture's parser token/depth/length budget is not implemented in this patch, so there is no honest passing depth-limit survivor to claim. The required next test should construct a deeply nested but lexically valid expression and prove a streaming pre-parser rejects it with `syntax_budget_exceeded` before Lark transformation or recursive allocation. Removing or moving that preflight after parsing must make the test recurse/allocate or return the wrong terminal state. This remains a decision-complete continuation item, not hidden coverage. + +## Exact successful commands + +Environment used for every `uv` command: + +```bash +export GIT_CONFIG_COUNT=1 +export GIT_CONFIG_KEY_0=safe.directory +export GIT_CONFIG_VALUE_0=/tmp/testdiet05/repo +``` + +### Core execution and aggregate suite + +```bash +uv run --frozen pytest -q \ + tests/unit/archive/query/test_execution_control.py \ + tests/unit/archive/test_query_multi_aggregate.py +``` + +Result: + +```text +31 passed in 11.32s +``` + +### Existing C-03 storage canary + +```bash +uv run --frozen pytest -q \ + tests/unit/storage/test_archive_tiers_archive.py::test_exact_session_action_count_bounds_pairing_before_global_ranking +``` + +Result: + +```text +1 passed in 1.43s +``` + +### Python API route + +```bash +uv run --frozen pytest -q tests/unit/api/test_facade_contracts.py -k query_units +``` + +Result: + +```text +11 passed, 264 deselected in 3.87s +``` + +### MCP route + +```bash +uv run --frozen pytest -q tests/unit/mcp/test_server_surfaces.py -k query_units_tool +``` + +Result: + +```text +8 passed, 74 deselected in 7.32s +``` + +### Daemon HTTP route + +```bash +uv run --frozen pytest -q tests/unit/daemon/test_web_reader.py -k query_units_endpoint +``` + +Result: + +```text +7 passed, 141 deselected in 5.75s +``` + +### CLI aggregate routes + +```bash +uv run --frozen pytest -q tests/unit/cli/test_query_expression.py -k \ + 'session_to_message_pipeline_group_by_count_executes_exact_counts or terminal_aggregate_sort_count_asc_executes_before_limit or cli_json_reports_terminal_aggregate_counts or observed_event_tool_outcomes_are_terminal_aggregate_query_units or file_unit_aggregate_counts_distinct_paths or terminal_action_source_exposes_followup_class_rows_and_aggregates or terminal_observed_event_tool_finished_aggregate_reads_blocks_without_materialization' +``` + +Result: + +```text +7 passed, 418 deselected in 2.26s +``` + +### Static, type, and syntax checks + +```bash +uv run --frozen ruff check \ + polylogue/api/archive.py \ + polylogue/archive/query/execution_control.py \ + polylogue/archive/query/unit_results.py \ + polylogue/daemon/http.py \ + polylogue/mcp/archive_support.py \ + polylogue/mcp/server_tools.py \ + polylogue/storage/sqlite/archive_tiers/archive.py \ + tests/unit/archive/query/test_execution_control.py \ + tests/unit/archive/test_query_multi_aggregate.py +``` + +Result: `All checks passed!` + +```bash +uv run --frozen ruff format --check +``` + +Result: `9 files already formatted` + +```bash +uv run --frozen mypy \ + polylogue/api/archive.py \ + polylogue/archive/query/execution_control.py \ + polylogue/archive/query/unit_results.py \ + polylogue/daemon/http.py \ + polylogue/mcp/archive_support.py \ + polylogue/mcp/server_tools.py \ + polylogue/storage/sqlite/archive_tiers/archive.py +``` + +Result: `Success: no issues found in 7 source files` + +```bash +uv run --frozen python -m compileall -q +git diff --check +``` + +Result: both passed with no output. + +### Patch application + +`PATCH.diff` was generated with: + +```bash +git diff --binary --full-index --no-ext-diff > PATCH.diff +``` + +It was then checked and applied to a fresh clone of the baseline repository: + +```bash +git apply --check PATCH.diff +git apply PATCH.diff +git diff --check +``` + +Result: all commands passed. The fresh clone showed exactly the 9 expected modified files and the same diff stat: 1,278 insertions and 156 deletions. + +## Non-acceptance invocations encountered + +These commands are not counted as verification passes: + +1. An initial combined surface command referenced stale/nonexistent paths such as `tests/unit/api/test_api.py`; pytest exited with status 4 and ran no tests. The repository paths were resolved with `rg`, then the corrected commands above passed. +2. A second combined command used the stale C-03 node name `test_query_unit_rows_exact_session_action_count_bounds_pairing_before_global_ranking`; pytest exited with status 4 and ran no tests. The current node is `test_exact_session_action_count_bounds_pairing_before_global_ranking`, which passed. +3. A long combined shell reached 100% for the seven daemon HTTP tests but hit the outer command timeout before the CLI leg and before printing the daemon summary. The daemon and CLI commands were rerun independently; both passed with the results above. + +These were command-selection/aggregation issues, not product test failures. + +## Unverified work + +The following were not run and are not claimed: + +- full repository pytest suite; +- `devtools verify --quick` or full generated-topology/render checks (no production files were added, so topology regeneration was not triggered); +- live daemon, MCP client, browser, or operator archive; +- incident-scale memory/RSS/PSS/swap/temp/FD/reader steady-state certification; +- NixOS/deployment checks; +- query-result spool/resume tests; +- parser depth/token budget tests; +- concurrent page resume against a mutating archive. + +## Proposed slower certification survivors + +These are retained for a later certification lane, not substituted for the focused tests: + +1. Generate the C-03 workload through the promoted workload-profile mechanism, then compare semantic output and production receipt thresholds against the global-first mutation at small/medium/live tiers. +2. Repeatedly run high-cardinality aggregates to completion, budget abort, deadline abort, explicit cancellation, and disconnect while sampling RSS, PSS, swap, SQLite temp bytes, WAL size, open readers, file descriptors, and admission weight until steady state returns. +3. Add owned aggregate spooling, disconnect/resume, expiry, abandonment, and cleanup tests that concatenate every page exactly once under a captured snapshot. +4. Add a parser-depth mutation survivor before Lark transformation. +5. Exercise source-tier-backed reads after source connections join the same read-only progress/interrupt/snapshot lifecycle. + +No existing tests or helpers are proposed for deletion.