Skip to content

v1.7.0: Mount composition, MCPMock, A2AMock, VectorMock, services - #56

Closed
jpr5 wants to merge 14 commits into
mainfrom
feat/v1.7.0-subproject1
Closed

v1.7.0: Mount composition, MCPMock, A2AMock, VectorMock, services#56
jpr5 wants to merge 14 commits into
mainfrom
feat/v1.7.0-subproject1

Conversation

@jpr5

@jpr5 jpr5 commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Summary

v1.7.0 adds mountable protocol mocking, AI service mocking, and supporting infrastructure. Zero new runtime dependencies.

Sub-project 1: Core Infrastructure

  • Mountable composition — multiple protocol handlers on a single server via llmock.mount(path, handler)
  • JSON-RPC 2.0 transport — batch, notifications, error codes, spec-compliant id handling
  • Config loader — start a full multi-mock stack from a single JSON/YAML file
  • Suite runnercreateMockSuite() for one-line multi-mock setup
  • aimock CLIaimock --config config.json with port validation and graceful shutdown
  • Subpath exports@copilotkit/llmock/mcp, ./a2a, ./vector

Sub-project 2: Protocol Mocking

  • MCPMock — Model Context Protocol: tools, resources, prompts with session lifecycle and initialization enforcement
  • A2AMock — Agent-to-Agent: agent cards, message/task pattern matching, SSE streaming with terminal state respect

Sub-project 3: AI Service Mocking

  • VectorMock — Pinecone, Qdrant, ChromaDB compatible endpoints with in-memory vector store
  • Search (Tavily-compatible), Rerank (Cohere v2), Moderation (OpenAI) service handlers

Quality

  • 1,896 tests across 49 files (was ~1,400)
  • Coverage: ≥90% lines, ≥85% branches across all modules
  • Pre-existing modules (bedrock, cohere, ollama, WebSocket, etc.) also brought to ≥85% branches
  • Two full CR rounds (MSAL workflow): module-scoped + cross-cutting + final gate
  • Zero as any in production source

Bug fixes included

  • Server catches unhandled promise rejections on HTTP and WebSocket paths
  • JSON-RPC passes null id for notifications (not fabricated 0)
  • A2A streaming respects terminal states from events
  • Vector journal only records handled requests
  • MCP enforces session initialization before method calls
  • CLI validates port arguments and handles shutdown errors
  • Extracted duplicated readBody/matchesPattern into shared helpers

Test plan

  • pnpm run test — 1,896 tests pass
  • pnpm run lint — clean
  • pnpm run format:check — clean
  • pnpm run build — clean (175 files, 973 kB)
  • Coverage thresholds enforced (90/85/90)

🤖 Generated with Claude Code

@pkg-pr-new

pkg-pr-new Bot commented Mar 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/CopilotKit/llmock/@copilotkit/llmock@56

commit: 4bdf324

@jpr5
jpr5 force-pushed the feat/v1.7.0-subproject1 branch from 2ddbb64 to 5f3d5a8 Compare March 24, 2026 06:05
@jpr5 jpr5 changed the title v1.7.0: Mount Composition, MCPMock, A2AMock, VectorMock, Search, Rerank, Moderation v1.7.0: Mount composition, MCPMock, A2AMock, VectorMock, services Mar 24, 2026
@jpr5
jpr5 force-pushed the feat/v1.7.0-subproject1 branch from 5f3d5a8 to 8346ba6 Compare March 24, 2026 06:07
jpr5 added 8 commits March 31, 2026 15:57
…g, CLI

Mountable interface for composing protocol handlers on a single server.
JSON-RPC 2.0 transport with batch, notifications (null id), error codes.
Config loader for multi-mock YAML/JSON configuration files.
Suite runner for one-line multi-mock setup with reset delegation.
aimock CLI with port validation, graceful shutdown, dependency injection.
Late-mount wiring for journal and baseUrl on already-running servers.
Server catches unhandled rejections on HTTP and WebSocket paths.
Extracted readBody and matchesPattern into shared helpers.
Tools, resources, and prompts with JSON-RPC dispatch. Session lifecycle
with initialize/notifications/initialized handshake and enforcement.
Standalone and mounted operation modes. inputSchema typed as
Record<string, unknown>. Subpath export at ./mcp.
Agent cards, message/task pattern matching, SSE streaming responses.
Streaming respects terminal states (COMPLETED/FAILED/CANCELED).
Types use proper unions (A2ATaskState, A2ARole). Standalone and
mounted modes. Subpath export at ./a2a.
In-memory vector store with upsert, query, delete, list, fetch, describe.
Three vendor-compatible endpoint sets. Auto-collection creation on upsert.
Conditional journaling (only handled requests). Custom query handlers.
Standalone and mounted modes. Subpath export at ./vector.
Tavily-compatible search, Cohere v2 rerank, OpenAI moderation endpoints.
Pattern matching with case-insensitive string and regex support.
Journal integration with service tags.
Expand tests for bedrock, bedrock-converse, cohere, ollama, responses,
messages, embeddings, gemini, ws-framing, ws-realtime, ws-gemini-live,
and recorder to meet >= 85% branch coverage threshold.
Add mcp-mock.html, a2a-mock.html, vector-mock.html, services.html,
aimock-cli.html, mount.html. Restructure docs site for use-case-first
navigation. Update all existing pages and README.
Add ./mcp, ./a2a, ./vector subpath exports and aimock bin entry.
Add new entry points to tsdown config. Configure vitest coverage-v8
with 90/85/90 thresholds. Add coverage/ to ignore files.
@jpr5
jpr5 force-pushed the feat/v1.7.0-subproject1 branch from 8346ba6 to b65fad0 Compare March 31, 2026 23:00
jpr5 added 6 commits March 31, 2026 21:18
- Logger prefix: [llmock] → [aimock]
- CLI startup: "llmock server listening" → "aimock server listening"
- Chaos headers: x-llmock-chaos-* → x-aimock-chaos-*
- Models endpoint: owned_by "llmock" → "aimock"
- All tests updated to match
- aimock_requests_total, aimock_request_duration_seconds, etc.
- aimock_mcp_requests_total{method} for JSON-RPC methods
- aimock_a2a_requests_total{method} for A2A operations
- aimock_vector_requests_total{operation,provider} for vector DB ops
- Mountable interface gains optional setRegistry() method
- 12 new tests for service metrics
POST /__aimock/fixtures, DELETE /__aimock/fixtures, POST /__aimock/reset,
GET /__aimock/journal, POST /__aimock/error, GET /__aimock/health.

Foundation for aimock-pytest pip package. LLMock gains
addFixturesFromJSON() method. entryToFixture() exported from
fixture-loader for reuse. 12 tests.
fix-drift.ts now includes write-fixtures skill in remediation PRs.
BUILDER_TO_SKILL_SECTION maps source files to skill doc sections.
affectedSkillSections() identifies which sections need review.
35 new tests.
- Treatment 3 Progressive Disclosure homepage with animated terminal demo
- Docs overview with section bar (LLM, MCP, A2A, Vector, Search, Chaos)
- sidebar.js: single source of truth for navigation across 30+ pages
- cli-tabs.js: CLI/Docker tab switching with localStorage persistence
- All page titles, nav, footer, prose, imports, Docker refs → aimock
- Font ligatures disabled, sticky table header, Record & Replay elevated
- "Ready to switch?" and "Built for production" sections
- migrate-from-msw.html: streaming comparison, side-by-side usage
- migrate-from-vidaimock.html: binary → programmatic API
- migrate-from-mock-llm.html: OpenAI-only → multi-provider, Helm migration
- migrate-from-piyook.html: JSON templates → declarative fixtures
- migrate-from-python-mocks.html: Docker + npx paths, validated examples
- migrate-from-mokksy.html: JVM/Kotlin with TestContainers integration
- docs-overview.html: suite overview with section cards
@jpr5 jpr5 closed this Apr 1, 2026
@jpr5
jpr5 deleted the feat/v1.7.0-subproject1 branch April 1, 2026 15:15
jpr5 added a commit that referenced this pull request Aug 6, 2026
…artifact is pinned (#359)

Two follow-ups on `main` from the adversarial review of #357.

## 1. The rejection/dedup query is already saturated — silently

`gh pr list --limit N` is a window, not a ceiling: it returns the newest
N rows and drops the rest with no flag, no warning and no count. The
marker self-heal — the thing that makes a human's rejection survive a
body edit — was a plain `--state all --limit 200` whose state filter is
applied *client-side*, so every PR in the repo competed for those 200
slots and the merged ones won.

Measured live on this repo, 2026-08-05:

```
gh pr list --state all    --limit 200 -> 200 [MERGED=184 CLOSED=14 OPEN=2]  #125..#358
gh pr list --state closed --limit 200 -> 200 [MERGED=186 CLOSED=14]
gh pr list --state all    --limit 200 --search "is:unmerged"
                                      -> 28  [CLOSED=26 OPEN=2]             #1..#358
```

Two things the review did not have:

- **`--state closed` is not a fix.** gh maps it to CLOSED-**or**-MERGED
— 186 merged against 14 closed, just as full.
- **Twelve closed PRs are already invisible.** The repo has 26
unmerged-closed PRs; the current listing sees 14. The lost set is `#1,
30, 50, 56, 59, 61, 63, 94, 95, 96, 119, 120`, each confirmed CLOSED by
`gh pr view`. None is a drift PR, so no rejection is lost *today* — but
the mechanism is running now, and MERGED is the population that grows
daily.

**Fix.** Both self-heal listings additionally query `--search
"is:unmerged"` and union the two views. That excludes merged PRs
server-side: 200/200 slots consumed becomes 28/200, and 28 is the
*complete* unmerged population back to #1. `is:unmerged` is a STATE
predicate, so unlike `<key> in:body` it cannot be defeated by the very
body edit the self-heal repairs, and a just-closed PR matches under
either indexed state. The plain listing is unioned rather than replaced,
so the index-free view stays and plain entries win on collision.

Headroom is not a proof, so saturation is also made loud:
`assert_listing_complete` refuses any listing that comes back full at
its `--limit` rather than deciding on it. `--limit 200` also stopped
being a magic number — each step binds `PR_LIST_LIMIT` once and the
audit reads the same variable.

## 2. "the one unpinned executable in the job" was false

`89e5a07` pinned `ollama.com/install.sh`. That script then streams
`ollama.com/download/ollama-linux-<arch>.tar.zst` — mutable,
unversioned, no digest — through `zstd -d` into `sudo tar -x` under
`/usr/local`.

Run verbatim out of the pinned script's own bytes, with curl serving
attacker content:

```
STEP_EXIT=0
SUDO_RAN argv=tar -xf - -C ./dest
TAR_RAN  argv=-xf - -C ./dest
what tar was handed: ATTACKER-CONTROLLED TARBALL BYTES
```

A compromised tarball plants a root-owned `git`/`gh`/`node` on PATH
exactly as well as a compromised script would, and every later step
holds a `contents:write` + `pull-requests:write` app token those
binaries can reach.

It cannot be fixed in place — install.sh pipes the download straight
into `tar`, so it never holds the file and has nothing to verify;
`OLLAMA_VERSION` only appends `?version=`, a version pin rather than a
byte pin.

**Fix.** install.sh is not used. The release artifact is fetched from an
immutable release tag and sha256-checked before anything unpacks it.
Digest agreed by three independent sources: the v0.32.6 release's
`sha256sum.txt`, the GitHub release API's asset `digest` field, and
sha256 of the 1,420,686,963 downloaded bytes. Archive layout confirmed
by listing it (`bin/ollama` + `lib/ollama/*`, so `-C /usr/local` puts
the binary on PATH). Dropping install.sh also drops its NVIDIA CUDA repo
adds and `$PACKAGE_MANAGER -y install`; the step already ran `ollama
serve` itself rather than using its systemd unit.

The false claim is corrected in the workflow comment, the suite's header
comment, its describe name and its failure messages. Two further stale
claims about the self-heal listing `--state open` (false since #357) are
corrected too. The commit message that first asserted it is in history
and cannot be rewritten.

## Red-green

The `gh` stub could not previously express item 1's bug — it ignored
`--limit` and returned the whole population, so a saturated window
looked exactly like a healthy one. It now models what real gh does.

**RED (watched).** The existing rejection scenario plus 200 newer MERGED
PRs — both PR-open steps re-proposed the rejected changeset:

```
FAIL  a rejection is still found once MERGED PRs have filled the listing window
  stdio: Pushed branch main-1111222233334444 … Matched PR #56
FAIL  needs_human_pr: a rejection survives a full window there too
  stdio: Pushed needs-human branch … Opened needs-human PR #57
```

**GREEN.** 154 passed, exit 0.

**RE-BREAK (watched), each mutation restored and md5-verified:**

| mutation | tests red |
|---|---|
| neuter the union so unmerged rows are dropped | 2 |
| delete all four `assert_listing_complete` calls | 2 |
| raise the audit threshold out of reach | 1 |
| replace the tarball digest comparison with `if false` | 1 |
| point the fetch back at `ollama.com/install.sh` | 1 |

Item 2's guard now watches `tar`, not `sh`, and the positive control
asserts `tar` was handed exactly the verified bytes rather than merely
that it ran.

## Gates

`pnpm build` 0 · `pnpm test` 0 (**5048 passed / 171 files**) · `pnpm
test:drift` 0 · `tsc --noEmit` 0 · `pnpm lint` 0 · `pnpm test:exports` 0
· prettier clean · `actionlint` 0 · `zizmor` 0 findings · `bash -n` on
all 16 `run:` bodies 0 · commitlint 0.

## Not fixed here

`.github/workflows/test-drift.yml:177` does the same `sh install.sh`
with **no pin at all**, in a job holding five provider API keys, with
provisioning ahead of the steps that use them. Same defect, different
file — flagged, not touched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant