diff --git a/docs/program-execution/TASK-001.md b/docs/program-execution/TASK-001.md new file mode 100644 index 0000000..d1ac8b6 --- /dev/null +++ b/docs/program-execution/TASK-001.md @@ -0,0 +1,32 @@ +# TASK-001 — Canonical capability resolver + +Campaign: `pe-router-capability-safety` · Base SHA `14c7b83` · Branch `pec/w0/task-001` + +## What changed + +- `src/types.ts` — added `ResolvedCapabilities` (searchRequired, searchPolicySource, + visionRequired) next to `SearchPolicyResolution`, so the resolver's output is a + first-class shared type. +- `src/matrices/capabilities.ts` (new) — `VISION_TASKS` exported as the single + authority for vision task types (VISUAL_QA, SCREENSHOT_ANALYSIS, + LAYOUT_VALIDATION) and the canonical `resolveCapabilities(task)` resolver. + Search truth delegates to `resolveSearchPolicy` — exactly one implementation of + the search rule; `searchPolicySource` preserves the existing EXPLICIT / + TASK_DEFAULT enum shape. +- `src/index.ts` — `resolveRoute` now consumes `resolveCapabilities` instead of + re-deriving the search policy locally; the private `VISION_TASKS` constant was + removed in favor of the exported one. The fail-closed search+vision guard and + the dispatch behavior are unchanged. +- `tests/capabilities.test.ts` (new) — focused coverage: explicit true/false + flags report EXPLICIT source, undefined flags report TASK_DEFAULT with the + TaskType default, exactly the vision types are vision-required, and a vision + task with an explicit search=false keeps both truths distinct. + +## Validation (run on the finished tree) + +- `npm run verify:types` — PASS (tsc --noEmit) +- `npm test` — PASS (23 files, 165 tests) +- `npm run lint` — PASS (eslint src/) + +Existing search-policy tests and routing behavior are unchanged: the resolver +reuses `resolveSearchPolicy` and the same guard fires in the same cases. diff --git a/docs/program-execution/TASK-002.md b/docs/program-execution/TASK-002.md new file mode 100644 index 0000000..a1ccc40 --- /dev/null +++ b/docs/program-execution/TASK-002.md @@ -0,0 +1,36 @@ +# TASK-002 — Fail unsupported capability combinations + +Campaign: `pe-router-capability-safety-v4` · Stacked on TASK-001 + +## What changed + +- `src/matrices/capabilities.ts` — added `VisionInputRequiredError` + (code `VISION_INPUT_REQUIRED`) and `assertSupportedCapabilities(task, + capabilities)`, which refuses before any reservation, circuit permit, or + provider dispatch: + - visionRequired + searchRequired → `UNSUPPORTED_CAPABILITY_COMBINATION` + (no provider serves search and vision together); + - images on a non-vision task type → `UNSUPPORTED_CAPABILITY_COMBINATION` + (images are only consumed by the vision branch); + - a vision task with no images → `VISION_INPUT_REQUIRED`. +- `src/index.ts` — `resolveRoute` now calls `assertSupportedCapabilities` + instead of the narrower images-present guard; `dispatchProvider` gained the + vision dispatch invariant (a vision task must dispatch on the OpenRouter + vision plane, never Perplexity or the general path). +- `tests/routing-matrix.test.ts` — matrix rows added: screenshot analysis + without images fails closed, content generation with images fails closed, + visual QA with images routes to vision; FAIL_CLOSED assertions now check the + failure code (`UNSUPPORTED_CAPABILITY_COMBINATION` or + `VISION_INPUT_REQUIRED`). +- `tests/search-policy-dispatch.test.ts` — updated the two legacy cases the + contract supersedes: a visual task with no images is now refused + (`VISION_INPUT_REQUIRED`) instead of routing with a defaulted image count, + and a visual task with no images plus explicit search is now an + `UNSUPPORTED_CAPABILITY_COMBINATION` instead of a plain search request; the + image-count regression guard now asserts the no-image refusal explicitly. + +## Validation (run on the finished tree) + +- `npm run verify:types` — PASS +- `npm test` — PASS (23 files, 169 tests) +- `npm run lint` — PASS diff --git a/docs/program-execution/TASK-003.md b/docs/program-execution/TASK-003.md new file mode 100644 index 0000000..46d861e --- /dev/null +++ b/docs/program-execution/TASK-003.md @@ -0,0 +1,25 @@ +# TASK-003 — Capability truth in RoutingDecision + +Campaign: `pe-router-capability-safety-v4` · Stacked on TASK-002 + +## What changed + +- `src/types.ts` — `RoutingResolution` gained `visionRequired: boolean` + (documented), inherited by `RoutingDecision`, alongside the existing + `searchRequired` and `searchPolicySource` audit fields from the merged + search-policy work. The `SearchPolicySource` enum shape is unchanged. +- `src/index.ts` — `resolveRoute` populates `visionRequired` from the + canonical `resolveCapabilities` output, so every decision reports the full + capability truth without re-deriving it at the call site. +- `tests/routing-matrix.test.ts` — added a regression test asserting + capability truth on every routing decision: a vision route reports + `visionRequired: true` with `searchRequired: false` and EXPLICIT source + (proving the SEO_CONTENT_BLUEPRINT-style audit shape without inferring + policy from the model name), a general route reports `visionRequired: + false`, and a search route reports `visionRequired: false` on Perplexity. + +## Validation (run on the finished tree) + +- `npm run verify:types` — PASS +- `npm test` — PASS +- `npm run lint` — PASS diff --git a/docs/program-execution/TASK-004.md b/docs/program-execution/TASK-004.md new file mode 100644 index 0000000..f827e90 --- /dev/null +++ b/docs/program-execution/TASK-004.md @@ -0,0 +1,22 @@ +# TASK-004 — Required routing matrix regression tests + +Campaign: `pe-router-capability-safety-v4` · Stacked on TASK-003 + +## What changed + +- `tests/routing-matrix.test.ts` — completed the required routing matrix + from the Website Contract: added the VISUAL_QA with **multiple** images + case (row U) routing to vision with requiresSearch=false. Together with + the rows landed in TASK-002, the matrix now covers every required row: + STRATEGIC_REASONING with false → general and true → search; + COMPETITOR_RESEARCH undefined → search and false → general; + SCREENSHOT_ANALYSIS with image+false → vision, image+true → unsupported + combination, no images → vision input required; CONTENT_GENERATION with + images → unsupported combination; VISUAL_QA with multiple images+false → + vision. All prior matrix rows stay green. + +## Validation (run on the finished tree) + +- `npm run verify:types` — PASS +- `npm test` — PASS +- `npm run lint` — PASS diff --git a/docs/program-execution/TASK-005.md b/docs/program-execution/TASK-005.md new file mode 100644 index 0000000..6373b1f --- /dev/null +++ b/docs/program-execution/TASK-005.md @@ -0,0 +1,28 @@ +# TASK-005 — Bump version to the 1.3.0 release line + +Campaign: `pe-router-capability-safety-v4` · Stacked on TASK-004 + +## What changed + +- `package.json` and `package-lock.json` (root + packages entry) bumped from + 1.1.3 to **1.3.0**. + +Operator decision (2026-08-18, mid-campaign): the release line is 1.3, not +1.1 — the search-policy audit work plus this campaign's capability-safety +work ship as 1.3.0, tagged by the operator after this campaign's delivery PR +merges. The intermediate 1.2.0 bump from PR #46 and the interim 1.1.3 pin +(PR #56) are superseded by this single forward jump; a separate version-fix +PR would have conflicted with this change on the same lines, so the campaign +itself is the 1.3.0 vehicle. Registry-state verification happens at the +operator's tag step (GitHub Packages authentication is operator-held). + +## Validation (run on the finished tree) + +- `npm run verify:all` — build, types, declarations, lint, boundary lint, + 171+ tests, audit, package smoke +- `npm run verify:package` — packed-tarball smoke install passes + +The packed artifact (npm pack) and the declaration-consumer fixture inside +verify:all stand as the local release proof; full disposable Website-Bot and +SEO-Bot consumer installs run at the operator's release step before the +1.3.0 tag push, per the contract's release sequence. diff --git a/package-lock.json b/package-lock.json index f6c34f1..07157ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@quantum-l9/llm-router", - "version": "1.1.3", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@quantum-l9/llm-router", - "version": "1.1.3", + "version": "1.3.0", "license": "PROPRIETARY", "dependencies": { "@quantum-l9/graphiti-memory-client": "^2.0.0", diff --git a/package.json b/package.json index 8ee331f..d475487 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@quantum-l9/llm-router", - "version": "1.1.3", + "version": "1.3.0", "type": "module", "description": "Reusable multi-provider LLM routing module with governed l9-graphiti-memory hydration, task-to-model routing, budgets, search, vision, and provider resilience.", "main": "dist/index.js",