feat(sdk): harden tool manifest projection for indexing#63
Conversation
Sync stale connection catalogs before reading tool manifests so indexing observes the latest projection. Let plugins provide explicit source revisions for produced tool catalogs, with OpenAPI using spec hashes and MCP using discovered manifest hashes.
Greptile SummaryThis PR hardens the
Confidence Score: 4/5Safe to merge; the changes are additive and best-effort, and the existing stale-sync logic was already proven in the tools.list path. The logic is a straightforward mirror of an already-working pattern applied to a second call site. The sourceRevision field is optional with a safe fallback, so no existing integrations break. The only rough edge is the pre-existing packages/plugins/mcp/src/sdk/plugin.ts — the stale type assertion on line 973 should be removed or updated to reflect the new conditional sourceRevision field. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant tools.manifest
participant syncStaleConnectionTools
participant DB
participant plugin.resolveTools
Caller->>tools.manifest: manifest(filter)
tools.manifest->>syncStaleConnectionTools: "yield*"
syncStaleConnectionTools->>DB: findMany(integration, config_revised_at IS NOT NULL)
DB-->>syncStaleConnectionTools: revised integrations
syncStaleConnectionTools->>DB: findMany(connection, integration IN revised)
DB-->>syncStaleConnectionTools: connections
loop for each stale connection
syncStaleConnectionTools->>plugin.resolveTools: resolveTools(...)
plugin.resolveTools-->>syncStaleConnectionTools: "{ tools, definitions, sourceRevision? }"
syncStaleConnectionTools->>DB: upsert tool_schema_manifest rows
end
tools.manifest->>DB: findMany(tool_schema_manifest, filter)
DB-->>tools.manifest: fresh manifest rows
tools.manifest-->>Caller: ToolSchemaManifest[]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant tools.manifest
participant syncStaleConnectionTools
participant DB
participant plugin.resolveTools
Caller->>tools.manifest: manifest(filter)
tools.manifest->>syncStaleConnectionTools: "yield*"
syncStaleConnectionTools->>DB: findMany(integration, config_revised_at IS NOT NULL)
DB-->>syncStaleConnectionTools: revised integrations
syncStaleConnectionTools->>DB: findMany(connection, integration IN revised)
DB-->>syncStaleConnectionTools: connections
loop for each stale connection
syncStaleConnectionTools->>plugin.resolveTools: resolveTools(...)
plugin.resolveTools-->>syncStaleConnectionTools: "{ tools, definitions, sourceRevision? }"
syncStaleConnectionTools->>DB: upsert tool_schema_manifest rows
end
tools.manifest->>DB: findMany(tool_schema_manifest, filter)
DB-->>tools.manifest: fresh manifest rows
tools.manifest-->>Caller: ToolSchemaManifest[]
|
## Summary Carries the tool manifest projection into semantic-search scan jobs by retaining each manifest source revision alongside the fingerprint. This makes index work traceable to the source catalog version that produced it without adding schema reads to scan. ## Changes - Add optional `sourceRevision` to semantic-search index jobs. - Copy `ToolSchemaManifest.sourceRevision` into both skipped and changed scan jobs. - Add regression coverage that manifest source revisions survive scan materialization. ## Call Stack ```text ToolSearchIndex.create() -> executor.tools.manifest() -> write manifest snapshot ToolSearchIndex.scan() -> read manifest projection -> compare manifest.indexFingerprint with stored fingerprint -> write IndexJob(sourceRevision, fingerprint, path, status) ``` ## Tests - `bun run --cwd packages/plugins/semantic-search test -- src/sdk/tool-search-index.test.ts` - `bun run --cwd packages/plugins/semantic-search typecheck` - `bunx oxfmt --check packages/plugins/semantic-search/src/sdk/collections.ts packages/plugins/semantic-search/src/sdk/tool-search-index.ts packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts` - `bunx oxlint -c .oxlintrc.jsonc --deny-warnings packages/plugins/semantic-search/src/sdk/collections.ts packages/plugins/semantic-search/src/sdk/tool-search-index.ts packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts` ## Stack Base: #63 <!-- stack:links:start --> ### [Stack](https://github.com/aryasaatvik/stack) 1. #63 2. **#64** 👈 current 3. #65 <!-- stack:links:end -->
## Summary
Hardens the per-run manifest snapshot layer so scans are pinned to the
manifest captured at index creation time. This protects the KV-only scan
path from accidentally falling back to a later live catalog read.
## Changes
- Add regression coverage for create-time manifest snapshot stability.
- Prove `scan` does not reread a changed live manifest after `create`.
- Assert the scanned job keeps the original snapshot fingerprint and
source revision.
## Call Stack
```text
ToolSearchIndex.create()
-> listToolManifests() once
-> partition manifests
-> write index-manifest/v1/{runId}/{partition} to executor.cache
ToolSearchIndex.scan()
-> read partition snapshot from executor.cache
-> never call executor.tools.manifest()
-> materialize jobs from the snapshot
```
## Tests
- `bun run --cwd packages/plugins/semantic-search test --
src/sdk/tool-search-index.test.ts`
- `bun run --cwd packages/plugins/semantic-search typecheck`
- `bunx oxfmt --check
packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts`
- `bunx oxlint -c .oxlintrc.jsonc --deny-warnings
packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts`
## Stack
Base: #64
<!-- stack:links:start -->
### [Stack](https://github.com/aryasaatvik/stack)
1. #63
2. #64
3. **#65** 👈 current
<!-- stack:links:end -->
Summary
Hardens the tool manifest projection used by indexing so manifest reads observe the same stale-catalog freshness behavior as
tools.list, and lets plugins persist explicit source revisions for produced tool catalogs.This keeps the existing separate
tool_schema_manifesttable and column shape. The change is a greenfield semantic cleanup of the manifest contract, not a table rewrite.Changes
executor.tools.manifest()reads manifest rows.ResolveToolsResult.sourceRevisionso plugins can describe the source revision that produced a catalog.specHash.Call Stack
Tests
bun run --cwd packages/core/sdk test -- src/executor.test.tsbun run --cwd packages/core/sdk typecheckbun run --cwd packages/plugins/openapi test -- src/sdk/spec-blob.test.ts src/sdk/plugin.test.tsbun run --cwd packages/plugins/openapi typecheckbun run --cwd packages/plugins/mcp test -- src/sdk/plugin.test.tsbun run --cwd packages/plugins/mcp typecheckbun run --cwd packages/plugins/semantic-search test -- src/sdk/tool-search-index.test.tsbun run --cwd packages/plugins/semantic-search typecheckbunx oxfmt --check packages/core/sdk/src/plugin.ts packages/core/sdk/src/executor.ts packages/core/sdk/src/executor.test.ts packages/plugins/openapi/src/sdk/backing.ts packages/plugins/mcp/src/sdk/plugin.tsbunx oxlint -c .oxlintrc.jsonc --deny-warnings packages/core/sdk/src/plugin.ts packages/core/sdk/src/executor.ts packages/core/sdk/src/executor.test.ts packages/plugins/openapi/src/sdk/backing.ts packages/plugins/mcp/src/sdk/plugin.tsgit diff --checkNotes
No deploy was performed. This is one milestone PR rather than a stack because the existing semantic-search indexing code already consumes
tools.manifest()and passed its targeted tests against this change.Stack