Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/program-execution/TASK-001.md
Original file line number Diff line number Diff line change
@@ -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.
36 changes: 36 additions & 0 deletions docs/program-execution/TASK-002.md
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions docs/program-execution/TASK-003.md
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions docs/program-execution/TASK-004.md
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions docs/program-execution/TASK-005.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading