Skip to the content.

Changelog

All notable changes to TENET are recorded here. Format is loosely based on Keep a Changelog; versioning follows Semantic Versioning.

Pre-1.0 the API surface and behavior may change without major bumps. We will pin breaking changes by minor version (0.X.Y).

[Unreleased]

Changed — inventory:check now also gates the “N vector stores” count against stores/vector/ (2026-07-18)

The inventory gate locked the surface- and model-adapter counts, but “2 vector stores” (README status line) was an ungated structural count — exactly the drift class the gate exists to catch (a 3rd store would silently leave the doc stale). Extended scripts/check-inventory.mjs: it now computes stores/vector/ (= pgvector + qdrant = 2) and fails any doc claiming a different N vector stores. The pattern is digit-anchored ((\d+)\s+vector stores?), so the many prose mentions (“Default vector store: pgvector”, “Qdrant vector store”) do NOT false-positive — verified the gate stays green with them present. Mutation probe non-vacuous: 2 vector stores3 in README reddens the gate (vector store count 3 ≠ live 2). Now 20 inventory claims checked (was 19). Verified in the same pass that the other README status-line capability claims are accurate (a2a/ag-ui/acp packages exist, @tenet/durable has interrupt() HITL, 2 state stores). Tooling-only; no TypeScript source or test changed, so pnpm -r build/pnpm test are unaffected (green at parent 5632ff4, 1325/98); counts/jsonld/links green.

Fixed — reconcile SECURITY enforcement claims (template-injection + deserialization) to the code (2026-07-18)

Follow-up to the supply-chain reconciliation: three more enforcement claims in the security tables didn’t match the code (§9 — no capability that isn’t in the code; a security doc that overclaims a control is worse than a stale count). Trust-code review:

Fixed — reconcile supply-chain SECURITY claims to what CI actually runs (removed SBOM/Dependabot/Snyk/provenance) (2026-07-18)

A security doc that overclaims controls is worse than a stale feature count — an operator could rely on protections that aren’t there (§9: no capability that isn’t in the code). Trust-code review of .github/ workflows/ci.yml + .pre-commit-config.yaml found the docs claimed a supply-chain posture that does not exist: SBOM (CycloneDX), a Dependabot config, Snyk, and npm --provenance/publish are nowhere in CI; Trivy runs report-only (exit-code: '0', continue-on-error), not as a gate; pnpm audit is informational (continue-on-error), not “fails CI”. Corrected every occurrence to the verified reality across SECURITY.md (supply-chain row + threat-model item), docs/ARCHITECTURE.md (§3 + the security table), docs/BENCHMARKS.md, and README.md: no production dependencies (adapters inject their own SDKs, so there is no prod-dep surface to compromise); a Trivy fs scan + pnpm audit --prod run report-only in CI (to be tightened to a hard gate at 1.0; Trivy SARIF → the GitHub Security tab); gitleaks + detect-private-key block secrets and actionlint

Added — lock the verifier’s FINAL-ness in the PERMISSIVE branch (a fabrication survives even a rescue) (2026-07-18)

The load-bearing anti-hallucination property: a deterministic fabrication (currency/percent/URL/email absent from sources) is FINAL — the permissive re-judge can never rescue it, because deterministicVerdicts merge AFTER the rescue loop (verifier.ts:151). The existing “mixed” integration test covered this only in the strict-PASS branch (strictFails.length === 0); the PERMISSIVE branch (a claim strict-fails → permissive rescues it) coexisting with a deterministic fabrication was untested — a regression dropping that post-rescue merge would ship the fabrication, uncaught. Added the missing test: the permissive judge rescues the judged claim, but a percent-marked fabrication keeps the draft pass: false. Mutation probe non-vacuous: dropping deterministicVerdicts from the permissive-branch merge reddens THIS test while the strict-pass “mixed” test stays green — confirming a real coverage gap. Test-only — verifyDraft unchanged (already correct). pnpm -r build/pnpm test green, suite 1324 → 1325 (+1). Completes the verifier audit (extract/judge/deterministic/ strict-permissive/duplicate-claim FIX#10/failClosed all sound + tested).

Added — lock withTimeout’s error classification (parent-abort ≠ timeout; normal errors pass through) (2026-07-18)

withTimeout (which the orchestrator wraps every pre-reasoning node in) has non-obvious catch logic: only if the wall-clock controller fired does it throw WorkflowError('timeout') — a caller (parent-signal) abort re-throws the abort, and any other step error passes through UNCHANGED (so consumers that inspect the error type — retryable? auth? abort? — still can). The block tested only the timeout-fires + fast-pass cases; a regression that wrapped every catch as 'timeout' would pass both. Added two regression tests: a normal step error is re-thrown unchanged (not masked as timeout), and a PARENT abort surfaces as the caller’s abort (not 'timeout'). Mutation probe non-vacuous: collapsing the catch to always-timeout reddens both new tests while the existing timeout/fast tests stay green. Test-only — withTimeout is unchanged (already correct); pnpm -r build/pnpm test green, suite 1322 → 1324 (+2). Audited the rest of @tenet/workflow (sequential/parallel/branch/retry abort + error propagation) in the same pass — all sound (parallel already distinguishes parent-abort from child-failure, retry cleans up its abort listener per attempt).

Added — lock the approval-key canonicalisation (the nested-reorder bypass fix) with real tests (2026-07-18)

stableApprovalKey canonicalises tool-call args RECURSIVELY (a 2026-06-04 security fix) so an attacker who controls JSON key order cannot reorder args to force a FRESH approval prompt that bypasses a prior rejection — same operation ⇒ same key ⇒ the cached reject is returned. But the only test reordered TOP-LEVEL keys: a partial regression (sort the top level, forget to recurse) would pass it while re-opening the nested-reorder bypass. Added two regression tests:

Added — lock the abnormal-stop → refusal mapping in every model adapter’s tests (2026-07-18)

A content-filtered / refused generation MUST map to canonical refusal (not end_turn), because the reasoner abstains on refusal/max_tokens before parsing — mapping it to end_turn would let a filtered draft be parsed and shipped (the same failure class as truncation-masking). The mappings were correct in code, but audit found the refusal case was untested in 4 of 6 adapters (only Google tested it): a regression flipping content_filter/refusalend_turn would have shipped a filtered response uncaught. Added the missing regression test to each:

Fixed — the outbound system-prompt-leak filter now matches TENET’s ACTUAL prompt (it didn’t) (2026-07-18)

systemPromptLeakFilter’s default patterns are supposed to block a reply that quotes the agent’s system prompt back at the user (defense in depth behind the verifier’s grounded-citation guard). But trust-code review found they matched NONE of buildSystem — the reasoner’s real agent prompt (packages/agent/src/reasoner.ts): simulating a full leak against the old defaults returned “no match”, so a genuine prompt leak would slip through the filter uncaught. Two of the old patterns matched nothing anywhere in TENET at all — leftovers from a different system’s prompt (a hard-rule violation: TENET ships nothing that isn’t its own). Fixed:

Fixed — @tenet/verifier now re-exports ALL composable deterministic pre-checks (public-API completeness) (2026-07-18)

The package entry exported numericFabricationCheck + quoteGroundingCheck (plus ClaimPreCheck + runPreChecks), so composing a CUSTOM deterministic tier is clearly a supported use case — but urlFabricationCheck and emailFabricationCheck (added later, in 3.2 and this loop) were never added to the index.ts re-export, so a caller literally could not import them from @tenet/verifier. An oversight, not a design choice: each check was added to deterministic.ts and to defaultPreChecks but the public export list wasn’t updated. Added urlFabricationCheck, emailFabricationCheck, and the two options types (NumericFabricationOptions, QuoteGroundingOptions) to the entry — the composable surface is now complete and consistent. Added publicApi.test.ts that imports every check factory + options type from ./index.js (the public entry, not the internal module) and builds + runs a hand-rolled tier — this LOCKS the surface: mutation probe non-vacuous (dropping an export from index.ts reddens the test — and nothing else would catch it, since url/email have no in-repo consumer outside defaultPreChecks). Pure additive re-export (§7: tsc-green is the proof) + a lock test; no behavior change. pnpm -r build green; suite 1309/97 → 1312/98 (+3 tests, +1 suite — the new test file).

Added — deterministic verifier catches a fabricated CONTACT EMAIL (emailFabricationCheck, in defaultPreChecks) (2026-07-18)

A support agent that invents support@wrong.com sends the user to the wrong — possibly hostile — address; that’s a real, harmful hallucination the deterministic tier did not catch. emailFabricationCheck fails a claim citing an email that appears NOWHERE in the sources, enforced before any LLM judge and FINAL (no permissive rescue), exactly like urlFabricationCheck. It is now in defaultPreChecks, so the recommended tier catches it by default. Design mirrors the URL check and stays inside the tier’s soundness rules:

A markdown link to a doc that isn’t there is the repo referencing something that doesn’t exist — a stale reference in its own voice, the same §9 defect class as a stale number. scripts/check-doc-links.mjs (pnpm links:check, now a CI step) scans every tracked .md, extracts relative links, and fails on any whose target is missing. Correctness: it strips fenced (```) and inline (`) code BEFORE extraction, so an illustrative [label](url) inside code isn’t mistaken for a real link (this was a real false positive in surfaces/discord/README.md); external (http(s):/mailto:) and anchor-only (#…) links are skipped, a trailing #anchor/?query is stripped, and a Jekyll .html target also resolves via its sibling .md. Audited the tree first — 61 relative links across 59 md files all resolve (0 broken; SKILL.mddocs/design/agent-turn.md confirmed present). Mutation probe non-vacuous in both directions: a broken link in prose fails the gate (exit 1), the SAME broken link inside inline code passes (exit 0, correctly skipped). Wired into package.json + ci.yml + AGENTS.md. Tooling/docs only — no TypeScript source or test changed, so pnpm -r build/pnpm test are unaffected (green at parent 3e9d5c0, suite unchanged 1302/97); counts/ inventory/jsonld gates all still green.

Added — regression test for runTools batch atomicity (a denied tool must not let allowed siblings run first) (2026-07-18)

runTools is two-phase by design — gate ALL calls, THEN execute ANY — so a forbidden call in a batch never lets its allowed siblings cause side effects first (e.g. a batch [read-secret, exfiltrate] where only exfiltrate is denied must run NOTHING). The existing “deny executes nothing” test used a rule that denied every call, so the FIRST call was denied and the batch stopped there — it never exercised the load-bearing case: an ALLOWED first call followed by a DENIED second. A single-loop refactor (gate-then-execute per call) would pass every existing test yet run the allowed sibling before reaching the deny — a real fail-open the suite did not catch. Added the missing test (allow kb.lookup, deny kb.write via allowListRule; assert exec.ran === []). Mutation probe proves the gap was real: under a single-loop runTools, the new test fails (exec.ran === ["kb.lookup"]) while the other 6 tests still pass — so without this test the atomicity invariant was unprotected. Test-only; pnpm -r build green, suite 1301 → 1302 (+1). No source change — runTools was already correct; this makes its most important property regression-proof.

Fixed — Open Graph image is now a real PNG (og:image unfurls; SVG never did) (2026-07-18)

og:image pointed at docs/assets/og-image.svg, but X / Slack / Discord / LinkedIn / Facebook unfurlers do NOT render SVG — so every shared link showed a blank preview, a silent AEO/discoverability defect. Rasterized the SVG to a 1280×640 PNG (docs/assets/og-image.png, ~82 KB, verified valid + non-blank + visually correct — wordmark, tagline, thesis with the green “abstains”, footer chips, and the verification-seal hexagon all legible in a fallback sans-serif) and repointed og:image at it in both docs/_config.yml (Jekyll defaults) and docs/index.md front matter. The SVG stays as the editable SOURCE; scripts/render-og.mjs regenerates the PNG from it (2× density → fit 1280×640), so an SVG edit is reproducible — sharp is an optional dev tool, not a gate or runtime dep. Docs/asset only — no TypeScript source or test changed, so pnpm -r build/ pnpm test are unaffected (green at parent 5d7f13f); counts/jsonld/inventory/readme gates all still green. Closes the 7.3 “OG image SVG→PNG + og:image” item.

Changed — the turn driver’s reasoner-decision switch is now exhaustiveness-guarded + fails closed on an unmappable decision (2026-07-18)

criticLoop (the central turn driver) switches on ReasonerOutput.kind (abstain | handoff | tool | answer) — the point where a model decision becomes an outcome. It had no default, so an unexpected variant fell through to the next loop iteration: today that incidentally fails closed via the maxAttempts abstain, but it’s not enforced — a loop refactor could turn it fail-OPEN, and meanwhile it burns maxAttempts model calls on a decision it can’t map. Added an exhaustiveness default:

Added — pnpm inventory:check: docs’ surface/model adapter counts are gated against the filesystem (2026-07-18)

A structural count in the repo’s own voice (“9 surface adapters”, “6 model adapters”) is held to §1 like the test count — the only witness is the tree. This drift is REAL: AGENTS.md had said “Surfaces (12)” while ls surfaces/ was 9 (fixed 349940a). scripts/check-inventory.mjs (pnpm inventory:check, now a CI step) computes the live counts from surfaces/ (minus core, which is shared infra, not an adapter) and models/, then FAILS on any tracked doc whose adapter-count claim disagrees — across the digit phrasings the docs use (N surface adapters, N surfaces, Surfaces (N adapters), N model adapters, Models (N)). docs/CHANGELOG.md is excluded (it records old counts verbatim, e.g. this entry). Mutation probe non-vacuous across all five pattern branches: bumping a doc’s count in each phrasing (e.g. 9 surface adapters→8, Surfaces (9 adapters)→12, Models (6)→7) reddens the gate; files restored byte-identical. Currently 19 claims across 8 docs all match live 9 surface adapters / 6 model adapters. Tooling/docs only — no TypeScript source or test changed, so pnpm -r build/pnpm test are unaffected (green at parent f26e491); suite unchanged at 1300/97. Closes the flagged “surface/model-count gate” follow-up.

Added — long-history compaction is now a fail-closed pre-step in the agent turn (2.4, 2026-07-18)

@tenet/harness shipped a ContextCompactor (summarize the middle of an overflowing conversation to fit a token budget) but nothing consumed it — git grep ContextCompactor outside packages/harness/ returned nothing, so a long conversation history was sent to the model verbatim, risking a silently truncated prompt that drops the grounding sources. Wired it into the turn as an OPT-IN pre-step:

6 new tests (4 compactNode: opt-in passthrough / replaces history / error→abstain / signal-threaded; 2 runAgent E2E: the compactor runs on the full 40-message history then the turn still emits, and a compaction error abstains the WHOLE turn before the reasoner is reached). Mutation probe non-vacuous: flipping the catch to fail-open reddens both fail-closed tests — the E2E even distinguishes it (fail-open reaches the exploding reasoner → "reasoner error", not "compaction error"). Docs synced (ARCHITECTURE pipeline snippet + node list, ROADMAP + PENDING graph strings). pnpm -r build green; suite 1294 → 1300 (+6). Composition note: the concrete ContextCompactorHistoryCompactor adapter (with a model summarizer) is app-level wiring, same as the other ports; this ships the CAPABILITY, fake-proven end to end.

Changed — purge cross-project references + a fabricated star-count from tracked files (2026-07-18)

TENET ships nothing that isn’t its own: every lesson is generalized, no other project’s brand/words are imported (a hard rule), and no number lives in the repo unless it traces to a source (§1/§9). A tree sweep (git grep -i openclaw) found 6 tracked files attributing TENET’s patterns to an external “OpenClaw” project — and the Dockerfile decorated it “(376k★)”, a star count no repository on GitHub has (the largest is ~400k; the actual OpenClaw game project has ~6k), so the figure is fabricated regardless of what OpenClaw is. Generalized every one to describe the pattern on its own merit, preserving all technical substance:

Fixed — AGENTS.md reconciled against the code (removed ungrounded + stale claims, 2026-07-18)

The public operating manual is held to §1/§9 like any other doc — a claim contributors rely on that isn’t backed by the code is a hallucination in the repo’s own voice. Re-verified every factual claim in AGENTS.md against the repo this session (ls/grep/reading ci.yml + .pre-commit-config.yaml + package.json) and corrected the ones that were wrong:

Left untouched what verified TRUE: Node 22 (.nvmrc=22, engines>=22, consistent across README/CONTRIBUTING/ ARCHITECTURE), Models (6), HHEM-2.1 (Vectara hhem-2.1-open), the stores/{vector,state} convention, and the 15-package “already shipped” table (all dirs confirmed present). Docs-only; no TypeScript source or test changed, so pnpm -r build/pnpm test are unaffected (green at parent 49a726c). counts:check / readme:check / jsonld:check all green, no drift. Suite unchanged at 1294/97.

Added — FAQ page emits FAQPage JSON-LD, now gated by pnpm jsonld:check (2026-07-18)

Structured data is machine-readable content the repo emits in its own voice, so it is held to §1 like any other claim: a malformed or empty block is a broken promise to crawlers shipped silently. Two parts:

Also purged a residual unsourced comparative the owner-session audit missed on this page: the “Does TENET run the model” answer claimed the framework makes a model “more grounded, cheaper per resolution, and harder to jailbreak” — an unsourced performance/security claim (§9). Rewritten to the guarantee it can actually keep: “answer only with a source-grounded, verified citation or abstain.” Docs-only + a new standalone gate script; no TypeScript source or test changed, so pnpm -r build/pnpm test are unaffected (green at parent 8472666). Suite unchanged at 1294/97.

Security — MistralApiError now scrubs secrets from its message + body (2026-07-18)

MistralApiError stored and echoed the RAW Mistral error-response body (.body unredacted, message = body.slice(0,200)), so a Mistral error that echoed the request’s Bearer token / authorization header / api_key could surface a live credential — the one adapter still missing the scrub the openai + matrix adapters already had. Added scrubMistralSecrets (Bearer / authorization / api_key JSON field → [redacted]) and applied it to BOTH the message and the stored .body, mirroring OpenAiApiError exactly. A model-adapter error must never surface a credential. 3 regression tests (Bearer + authorization scrubbed from message and .body; api_key JSON scrubbed; body bounded to 200 chars); mutation probe non-vacuous (reverting to the raw body reddens both scrub tests). Closes the “MistralApiError does not scrub secrets” security follow-up (found by the 1.2b-i review). Suite 1291 → 1294 (+3).

Added — Matrix surface is a supervised, deployable /sync loop (5.2 COMPLETE → Phase 5 done, 2026-07-18)

The Matrix surface already had an injected-fetch transport + a /sync long-poll, but the loop threw on any non-2xx — a single 503/429 killed the bot — and it yielded the bot’s OWN messages (a reply-loop). Made it genuinely deployable:

3 new tests (transient-5xx-recovers, 401→MatrixTokenExpiredError, 400→fatal-no-retry, self-filter) + 2 non-vacuous mutation probes (5xx-throws-instead-of-backoff reddens the recovery test; removing the self-filter reddens the self-filter test). Suite 1288 → 1291 (+3). 5.2 COMPLETE → PHASE 5 DONE (5.1 resilience family, 5.2 deployable surface, 5.3 surface hardening all landed; the Phase-5 gate — a surface refuses an ungrounded reply + a failover test passes — is met).

Changed — de-fork the two HS256 JWT verifiers into @tenet/surface-core (5.3 COMPLETE, 2026-07-18)

The REST and web-widget surfaces each shipped their OWN copy of an HS256 JWT verifier — and the copies had already drifted once (the web-widget copy silently lost its alg + nbf checks; fixed in 981be3c). One shared, hardened implementation now lives in @tenet/surface-core (jwt.ts: hs256Verifier + JwtClaims / JwtVerifier / JwtError, with the alg-confusion + nbf + timing-safe hardening), and both surfaces re-export it so there is a single source of truth that cannot drift again. Public APIs are unchanged: web-widget re-exports hs256Verifier / JwtError / JwtClaims / JwtVerifier 1:1; REST re-exports hs256Verifier as hs256RestVerifier and aliases RestJwtClaims / RestJwtVerifier to the shared types — so both surfaces’ existing tests pass unchanged, now exercising the shared impl through their public API. Added a canonical JWT test suite in surface-core (valid, alg:none/RS256 confusion with a valid HMAC, nbf future/past, exp, malformed, bad-sig, missing-claims). MUTATION PROBE proves the de-fork’s value: removing the alg check in the ONE shared impl reddens the alg-confusion tests of BOTH surfaces at once. Suite 1278 → 1288 (+10 canonical tests), 96 → 97 suites. 5.3 COMPLETE.

Added — REST surface enforces the grounded-render gate (5.3, Phase-5 gate MET, 2026-07-18)

RestSurface gains an opt-in groundedFallback?: string. When set, the surface REFUSES to render a non-stream /v1/converse reply that carries no grounded citation — it returns the safe fallback (citations zeroed) INSTEAD of the possibly-ungrounded reply text, so no ungrounded fact reaches a client even if a reply reached the wire bypassing the agent’s emit edge. Opt-in on purpose (an echo/passthrough deployment isn’t making grounded claims; a grounded-or-abstain deployment turns it on), so existing behavior is unchanged when unset. This satisfies the Phase-5 gate: “a surface refuses to render an ungrounded factual reply.” Streaming (/v1/converse/stream) is NOT gated — grounding a token stream before its citations are known is a distinct problem (buffer or post-verify), flagged for later. 3 E2E tests through the HTTP handle (refuses an uncited fact-bearing reply → fallback; renders a grounded reply as-is; opt-in when unset); mutation probe non-vacuous (bypassing the gate leaks the fact and reddens the refusal test).

Relocated @tenet/surface-coresurfaces/core/ (from packages/surface-core/): the @tenet/surface-* name maps to surfaces/* in jest’s moduleNameMapper, so the package could not be imported from packages/. The move makes it convention-correct (resolves automatically, no config hack). Same name, same 63-package count. Suite 1275 → 1278 (+3).

Added — @tenet/surface-core grounded-render gate (5.3 partial, 2026-07-18)

A new package with groundedRenderGate(reply, { fallback }) — a fail-closed, last-mile grounding guard for the surface send path. The agent’s single emit edge already refuses to ship an uncited fact, but a surface can be handed a reply from a path that bypassed it (a cache, a fast-path, a bug); this gate is the belt-and- suspenders: a reply renders as-is only if it carries a grounded citation (non-blank source id AND quote — the same bar the emit edge applies); a fact-bearing reply with no grounded citation is refused, rendering the operator’s safe fallback INSTEAD of the ungrounded text; a blank reply carries no fact and passes through. Citation-based on purpose: it does not try to tell a legitimate abstention from a fabricated fact that lost its citation — it treats both as not-renderable-as-an-answer and substitutes the fallback, which is strictly safe. Pure, no provider deps (@tenet/core Citation only). 6 tests (the refusal of an ungrounded fact-bearing reply front and centre); mutation probe non-vacuous (rendering the ungrounded text reddens it). Also establishes the shared @tenet/surface-core home the JWT de-fork (5.3a) will move into. STILL OPEN in 5.3: wire the gate into a surface send path (E2E “a surface refuses”), and the JWT de-fork. Workspace now 63 packages; suite 1269 → 1275 (+6), 95 → 96 suites.

Added — cross-provider failover chain, fail-closed (5.1 complete, 2026-07-18)

failover(providers, opts) in @tenet/rate-limit runs providers in order and moves to the next ONLY on a transient failure — the fail-over decision is the isRetryable taxonomy, so it fails over on a 5xx/429/network error (the current provider is unhealthy) but NEVER on a fatal class: an AbortError (a cancelled turn must stay cancelled, not silently re-issued elsewhere) or a 4xx (a bad request/auth fails identically on every provider). This is the “router MUST NOT fail over on AbortError/4xx” rule as a one-line predicate. Orthogonal to retryWithBackoff — wrap each provider callable in it to retry transient errors on that provider first, then let failover move on once its retries exhaust; the two compose without either knowing about the other. On exhaustion or a fatal error it re-throws the ORIGINAL error, never a shipped fallback. 9 tests (fail-closed AbortError/4xx no-failover front and centre; a real composition test with retryWithBackoff); mutation probe non-vacuous (a blind-failover default reddens both fail-closed tests). This completes 5.1’s resilience family (taxonomy → retry-with-backoff → circuit breaker → failover). Suite 1260 → 1269 (+9), 94 → 95 suites.

Hardened — counts:check now also gates the landing page + llms-full.txt

Added docs/index.md and llms-full.txt to the tracked-docs list in scripts/check-counts.mjs — both carry the live test-count claim now, and neither was gated, so the public landing page could silently go stale (the exact hallucination-in-the-repo’s-voice the gate exists to catch). Verified non-vacuous: a planted drift in docs/index.md’s count now fails pnpm counts:check.

Added — wire the CircuitBreaker into retryWithBackoff (5.1 partial, 2026-07-18)

retryWithBackoff gains an optional breaker?: CircuitBreakerLike. When the breaker is OPEN the call is short-circuited with a CircuitOpenError before fn runs — the retry layer never hammers a failing upstream (a blind loop on 401/403 is the documented Cloudflare egress-IP-ban risk the breaker exists to prevent). A success resets the breaker; an AUTH failure (401/403 — via the new isAuthFailure predicate) counts toward opening it, while a 429 (rate-limit, not auth) and every other error deliberately do NOT. The breaker surface is a structural CircuitBreakerLike { allow / recordSuccess / recordAuthFailure } (the real CircuitBreaker satisfies it — testable with a fake, no forced instance). CircuitOpenError classifies as fatal, so it is never itself retried. 7 tests incl. an integration test with the REAL CircuitBreaker (consecutive 401s open it → the next call short-circuits, fn never called); 2 mutation probes non-vacuous (removing the short-circuit or the auth-failure recording each redden 2 tests, incl. the real-breaker case). Suite 1254 → 1260 (+6). STILL OPEN in 5.1: the cross-provider failover chain (keyed on retryClass).

Fixed — refresh the stale Phase-3 status in ARCHITECTURE §0 (docs-sync, 2026-07-18)

docs/ARCHITECTURE.md §0 “Implementation status” claimed Phase-3 “truncation/maxClaims hardening remain” — a stale status that shipped fires ago (a stale repo fact is a hallucination in the repo’s own voice). Corrected against the CODE, re-verified this fire: the Reasoner abstains on a max_tokens/refusal/aborted stop (reasoner.ts), an over-cap claim extraction throws ClaimExtractionError under failClosed (claimExtractor.ts), and defaultPreChecks fails a fabricated number/spelled-amount/URL before any judge (deterministic.ts: numericFabricationCheck/parseSpelledNumbers/urlFabricationCheck); groundedOrAbstain (3.4) ships in @tenet/refine. §0 now states Phase 3 effectively-complete (only 3.2’s named-entity + negation guards deferred until a real judge) and Phases 4–5 in progress. Doc-only; suite unchanged (1254/94).

Added — retryWithBackoff fail-closed retry wrapper in @tenet/rate-limit (5.1 partial, 2026-07-18)

retryWithBackoff(fn, opts) retries an outbound call with jittered exponential backoff, and it fails CLOSED by construction: the default retry decision is the isRetryable taxonomy, so a fatal error (AbortError, 4xx, or anything unrecognized) is re-thrown on the FIRST attempt with no backoff — only known-transient errors (429/408, 5xx, transient socket codes) are retried. This is the deliberate opposite of @tenet/workflow’s generic retry, whose default shouldRetry is () => true (retries any throw, including a cancelled call); the model-call wrapper lives in rate-limit precisely so it can consume the taxonomy without inverting the workflow→rate-limit layering. Backoff is exponential with FULL JITTER by default (delay ∈ (0, base], capped at maxDelayMs) to de-correlate concurrent clients off a recovering upstream; sleep, random, and shouldRetry are injectable so the schedule is deterministic under test, and an abort during a backoff wait rejects immediately. On exhaustion it surfaces the ORIGINAL error, never a shipped fallback. 11 tests (the fail-closed no-retry cases for AbortError / 4xx / unknown are front and centre; plus the exponential+cap schedule and the jitter formula); 2 mutation probes non-vacuous (blind-retry default reddens all 3 fail-closed tests; removing jitter reddens the jitter test). Suite 1243 → 1254 (+11), 93 → 94 suites. STILL OPEN in 5.1: wire the CircuitBreaker into this wrapper, and the cross-provider failover chain.

Added — retryable-vs-fatal error taxonomy in @tenet/rate-limit (5.1 partial, 2026-07-18)

classifyError(err) → { retryClass: 'retryable' | 'fatal', reason } + isRetryable(err) — the foundation a resilience layer (retry / backoff / cross-provider failover) must consult BEFORE any retry. Encodes 5.1’s fail-closed rules: an AbortError is fatal (a cancelled request must stay cancelled — retrying defeats cancellation and can duplicate a side effect); 4xx is fatal (a client error fails identically on retry, and 401/403/429 blind-retries risk an egress-IP ban — the existing CircuitBreaker’s own concern); 429/408 and 5xx are retryable (transient — with backoff); known transient socket/DNS codes (ECONNRESET, ETIMEDOUT, …) are retryable. Everything unrecognized is fatal — a non-idempotent call is never blindly re-sent on an error we cannot classify. Pure + duck-typed on name/status/code, so it works across model adapters and fetch impls without importing them. 9 tests across every branch (incl. AbortError-wins-over-503 and the fail-closed unknown/null/string cases); 3 mutation probes non-vacuous (abort→retryable, unknown→retryable, 4xx→retryable each redden). Suite 1234 → 1243 (+9), 92 → 93 suites. STILL OPEN in 5.1: the retry-with-jittered- backoff wrapper, wiring the CircuitBreaker, and the cross-provider failover chain (all consume this taxonomy).

Security — harden the web-widget HS256 JWT verifier to REST parity (5.3 partial, 2026-07-18)

The web-widget surface’s hs256Verifier was the weaker of the repo’s two HS256 verifiers: it verified the HMAC signature but never checked the header’s alg, and never checked nbf. Two gaps closed, mirroring the already-hardened REST hs256RestVerifier:

4 regression tests, all DISCRIMINATING: the alg-confusion tokens carry a VALID HMAC (signed with the real secret), so a signature-only verifier would accept them — mutation-removing the alg check reddens both; removing the nbf check reddens the not-yet-valid test. Suite 1230 → 1234 (+4). STILL OPEN in 5.3: the DRY de-fork of the two now-parallel verifiers into a shared @tenet/surface-core (a public-API refactor across both surfaces — its own increment), and moving the grounding gate into the surface send path.

Added — README fenced-TypeScript gate + fixed the measure-real usage example (4.gate, 2026-07-18)

pnpm readme:check (scripts/check-readme-ts.mjs, now a CI step + in the repo’s gate list) extracts every ` ```ts ` block from a tracked README, writes it into the owning package’s examples/ dir (so workspace @tenet/* imports and ESM top-level await resolve as an operator’s copy would), and typechecks it with tsc --noEmit. A usage example that no longer compiles is stale code in the repo’s own voice; the gate fails CI the moment one drifts from the API it documents. Caught + fixed REAL drift in apps/measure-real/README.md: its ts example imported a non-existent REAL_TARGETS export and constructed AnthropicChatModel with ONE argument (the real constructor is two — an injected http transport, then options), and used the stale model id claude-sonnet-4-5-20250929. Rewrote it to a complete, compilable snippet (two-arg constructor with a real http shape, claude-sonnet-4-6); synced the same stale id + the phantom REAL_TARGETS out of cli.ts’s usage comment, the canonical examples/anthropic-sonnet.ts, and .github/workflows/measure-real.yml. Non-vacuous: re-introducing the one-arg constructor makes the gate exit 1. (Also newly typechecks the canonical example, which the app build’s include: ["src/**/*"] had never covered.) Suite unchanged (1230/92).

Fixed — community-bot no longer hardcodes costUsd: 0 (4.3 partial, 2026-07-18)

The community-bot reference recorded costUsd: 0 on every outcome (a fabricated “free” measurement, commented “cost meter integration is Phase 2”). Fixed with the same structural CostMeter wiring as enterprise-support: a costMeter?: CostMeterLike ({ total(): number }, satisfied by @tenet/telemetry’s CostMeter — no new dependency) is read at outcome time; unmetered defaults to 0 explicitly, never a fabricated figure. Cost is recorded on all three outcome paths (resolved / verifier-rejected / model-error). TRUST-CODE correction to the PLAN: community-bot’s verifierPassed was NOT the enterprise-support fabrication — it already runs verifier.verify() and reports the real verdict (true on pass, false on reject, null on model error), so only costUsd needed fixing. 3 new tests (metered resolved, metered disqualified, unmetered 0); mutation-verified non-vacuous — and the probe caught a toBeCloseTo(0.0031) that was vacuous under the default 2-digit precision (0 ≈ 0.0031), now exact toBe. Suite 1227 → 1230 (+3).

Fixed — enterprise-support telemetry no longer fabricates verifierPassed / costUsd (4.3 partial, 2026-07-18)

The reference dispatcher recorded a FALSE verifierPassed = true on every successful dispatch — set unconditionally right after agent.handle(), without observing any verification (and, if send then threw, it recorded true alongside a disqualified outcome). A telemetry field asserting a verification that never happened is a hallucination in the repo’s own voice. It also hardcoded costUsd: 0, a fabricated “free” measurement. Fixed: verifierPassed is now derived from what the dispatcher can HONESTLY observe — a non-blank reply with ≥1 verifier-approved citation → true; an emitted-but-uncited reply (abstention) → false; a dispatch error (no reply) → null — it never claims a verification it didn’t see. costUsd now comes from an injected costMeter (structural { total(): number }, satisfied by @tenet/telemetry’s CostMeter with no new dependency); unmetered defaults to 0 explicitly, never a fabricated figure. 5 new tests (grounded→true, uncited→false, error→null, metered cost, unmetered 0); the uncited→false and metered tests RED on the old fabricating code (mutation-verified). Suite 1222 → 1227 (+5). STILL OPEN in 4.3: community-bot has the identical costUsd:0 / verifierPassed pattern (same fix, separate app); the broader “genuinely wired real verifier + retrieval + console surface” remains a larger follow-up.

Changed — de-tautologize the eval’s QA grounding metric (4.2 partial, 2026-07-18)

The measurement plane’s Dim-1 QA path was an identity pass: stubAnswer cited exactly relevantSourceIds and stubVerify checked cited ⊆ relevant, so hallucination_rate (0.0) and groundedness_rate (1.0) were true by construction — no possible input could fail them, yet they are gated in hermetic CI. A vacuous number reported as a measurement is a hallucination in the repo’s own voice. Fixed: every QAFixture now carries a distractor-bearing corpus (the relevant chunk plus topically-adjacent distractors missing the answer’s distinctive term), and stubAnswer selects its citation by real term-overlap retrieval over that corpus instead of echoing ground truth. The stub still scores 0.0 / 1.0 — but now as a MEASURED result: a distractor out-overlapping the relevant chunk cites an ungrounded id and fails verification. Proven non-vacuous: a distractor-dominant fixture makes stubVerify return false (impossible under the old identity), and mutation-reverting stubAnswer to the echo reddens the new tests. Fail-closed: no overlap / empty corpus → no cite → unsupported (never a spurious pass). All 20 bundled fixtures verified to ground correctly (relevant chunk strictly wins overlap). BENCHMARKS Dim 1/1b annotated. Suite 1219 → 1222 (+3). STILL OPEN in 4.2: real independent judge, true-TTFT via chatStream, temp>0 determinism split (real-model measurement concerns, deferred).

Added — quickstart README + network-gated real-model smoke test (4.1, 2026-07-18)

apps/quickstart gains a real README.md: the true three steps (pnpm installpnpm quickstart zero-key → ANTHROPIC_API_KEY=… pnpm quickstart) and real captured output (not fabricated — produced by running the built agent), plus an honest note that the offline StubChatModel always grounds its draft in the top chunk and always judges SUPPORTED, so genuine abstention needs a real model (or the liar-model test). Added a network-gated real-model smoke test to index.test.ts that runs the same retrieve→draft→verify pipeline against a real Anthropic model and asserts a verified, cited (non-abstained) answer. It is registered ONLY when ANTHROPIC_API_KEY is set, so the hermetic suite is unchanged (1219/92, still all green) and its count does not drift; verified BOTH directions (absent with no key, registers + attempts the real call with a key). Its real-model assertion is un-run in CI (no key) — flagged in code + PLAN; only its gating + compilation are checked here. TRUST-CODE note: the item’s “invalid default model id” was stale — main.ts already defaults to the valid claude-sonnet-4-6 (TENET_MODEL overridable).

Added — groundedOrAbstain reference orchestrator wires @tenet/refine fail-closed (3.4, 2026-07-18)

@tenet/refine gains groundedOrAbstain(input, opts) — the canonical chain of its levers into one fail-closed grounded-or-abstain turn: knowledge-boundary gate → draft → verify + bounded repairDraft → answer or abstain. It returns a discriminated GroundedDecision (answered | abstained with a boundary / verify / error stage), takes injected functions only (no provider, no @tenet/verifier import — a caller backs verify with verifyDraft), and fails CLOSED at every edge: thin retrieval abstains BEFORE drafting (no draft, no cost), an unsupported draft that repair cannot rescue abstains, and any draft/verify/rewrite throw resolves to abstain — status: 'answered' is returned ONLY past a passing verifier. This gives repairDraft its first in-repo consumer and satisfies the Phase-3 gate: an unsupported “we do not offer X” scope claim ABSTAINS, never ships (absence of X in sources ≠ positive support for the negative). 6 E2E tests with fakes (thin-retrieval, first-pass, repair-then-answer, scope-claim-abstain, verify-throws-fail-closed, aborted-before-draft); 4 mutation probes non-vacuous (gut the emit-only-on-pass guard, re-throw instead of abstain, drop the boundary/abort early-returns). Suite 1213 → 1219 (+6); no live model needed (deterministic ChatResponse→decision mapping). refine README + llms.txt updated.

Added — deterministic check for spelled-out fabricated amounts (3.3, 2026-07-18)

The numeric fabrication pre-check now catches numbers spelled out in words, not just digits: a claim asserting a currency- or percent-marked spelled amount absent from the sources (“the fee is five hundred dollars” when the price is “$299”, “twenty percent uptime” when it is “99.9%”) FAILS deterministically, closing the paraphrase-to-words evasion of the digit check. The parser (parseSpelledNumbers) feeds the SAME extractNumericTokens used for both claim and sources, so grounding stays symmetric — “five hundred dollars” matches a source’s “$500” and vice-versa, never a false-fail. Conservative by construction: a run needs a spelled leaf word (a lone “hundred”/”million” is not a number), “and” is absorbed only between number words (“one hundred and fifty thousand”), and — like the digit path — only currency/percent-marked spelled numbers fail by default; a bare spelled count (“five reasons”) defers to the judge. Mutation-probed the “and” absorption and marker detection are load-bearing. Suite 1205 → 1213 (+8); all prior verifier tests unchanged.

Added — deterministic URL-fabrication check (3.2 partial, 2026-07-18)

urlFabricationCheck() joins the verifier’s deterministic tier (defaultPreChecks): a claim that cites an http(s):// URL appearing NOWHERE in the sources FAILS deterministically — an invented link is a classic, dangerous hallucination, and it’s caught without spending a judge. Enforces TENET’s source-grounded-URL thesis. Conservative by construction to avoid false-fails: only scheme-bearing URLs are checked (so index.ts, Node.js, package.json are never mistaken for URLs), trailing sentence punctuation is stripped, and grounding is a scheme/www-insensitive substring match (a parent/child path counts as grounded; lookalike-domain misses defer to the judge rather than false-fail). A URL being present never PASSES a claim. Mutation-probed that the scheme requirement is what prevents the non-URL false-fail. Suite 1198 → 1205 (+7); 127 prior verifier tests unchanged (backward compatible). STILL open in 3.2: named-entity coverage + the negation/scope guard.

Added — coverage gate that protects the actual coverage level (6.2, 2026-07-18)

pnpm test:coverage is now a CI step, and the jest.config.cjs coverageThreshold was raised from the loose 60/70/70/70 floor to 75 branches / 90 functions / 90 lines / 88 statements — just below the actual 90.3/78.5/94.4/93.0 (small margin). The old floor silently allowed coverage to regress to 60%; the new gate fails CI on a real drop. Verified: coverage passes at the new thresholds (exit 0); mutation-probed that it ENFORCES (setting branches to 90 > actual 78.46 failed with “threshold for branches (90%) not met”).

Changed — replace competitor ✓/✗ grids with TENET-only capability tables (7.2, 2026-07-18)

Both README comparison tables (“Where TENET focuses” and “Quick comparison”) asserted specific, unverified capabilities about ~7 named competitors — including “maintenance” (implying AutoGen is abandoned), dated release claims (“✅ (2026-04)”), and per-vendor pricing (“$0.99 / resolution”) that cannot be sourced from this repo. For an anti-hallucination framework that is the exact unsourced-claim class it exists to prevent. Replaced both with TENET-ONLY tables that assert only this repo’s own capabilities, each backed by a named package verified to exist in the tree (@tenet/governance, @tenet/verifier, @tenet/refine, @tenet/durable, @tenet/a2a, @tenet/ag-ui, @tenet/harness, @tenet/judge-hhem, @tenet/telemetry, @tenet/tools-mcp-gateway, @tenet/tools-wasm-sandbox, infra/helm/tenet, …), with a clear note on WHY. Also fixed the stale “Multi-surface (10)” / “10 shipped” → 9 surface adapters. No per-competitor grid remains.

Added — mermaid architecture diagram of the fail-closed turn (7.3, 2026-07-18)

A “How a turn works” section in the README with a mermaid flowchart of runAgent’s graph: injection gate → retrieve + knowledge boundary → cache re-verify → reason → (tool via governance | answer) → verify + citation guard + leak gate → the single emit edge, with every non-emit edge resolving to abstain or handoff. Traced against the actual orchestrator.ts + criticLoop; mermaid syntax structurally validated (bracket balance, edge count). Renders natively on GitHub.

Fixed — accurate site SEO meta + robots.txt (7.3, 2026-07-18)

docs/_config.yml’s description (the Google snippet + og:description that jekyll-seo-tag emits) was stale/inaccurate: “10 surfaces” but listed 8, and the bare “0 high-severity CVEs” boast. Corrected to the real “9 surface adapters (Discord, Slack, Telegram, Teams, web widget, REST, gRPC, Matrix, voice)”, led with the grounded-or-abstain guarantee, and replaced the CVE boast with “hardened by construction” (consistent with the earlier index.md fix; the MEASURED Dim-6 posture stays in BENCHMARKS where it states its method). Added docs/FAQ.md to the site include list so the answer-shaped FAQ is crawlable, and added docs/robots.txt (allow all + a Jekyll-rendered Sitemap: pointing at the jekyll-sitemap output). YAML validated.

Added — docs/FAQ.md answer-shaped FAQ for AEO/SEO (7.3, 2026-07-18)

A structured FAQ with answer-shaped questions (how TENET prevents hallucination, what grounded-or-abstain means, is-it-a-LangChain-alternative, supported models/surfaces, how the verifier works, is-it-production-ready, licensing, getting started). Every answer is measured — capabilities named are code in the tree (6 model adapters, 9 surface adapters, 4 ticketing connectors, infra/helm/tenet), no unsourced competitor claims, and the production-readiness answer is honest (pre-1.0; adapters transport-injected; in-repo numbers are the hermetic stub). Linked from llms.txt; sets up the JSON-LD FAQPage (remaining 7.3).

Added — skillToolPolicyRule: allowed-tools is now ENFORCED, not decorative (8.2, 2026-07-18)

@tenet/skills gains skillToolPolicyRule(frontmatter) → a @tenet/governance PolicyRule that allows ONLY the tools a skill declared. Dropped into a PolicyEvaluator, a skill physically cannot call a tool it did not declare (the agent’s runTools gates every call before execution). FAIL-CLOSED: a skill that declares no allowed-tools may call none. This turns the header’s long-standing “governance-aware” promise (and the package description’s “@tenet/governance allowed-tools enforcement”) into a shipped, tested helper — @tenet/skills now actually depends on @tenet/governance. Tests prove allow-declared / deny-undeclared (with reason) / deny-all-on-empty. Suite 1195 → 1198.

Added — flagship SKILL.md: build a verification-first, grounded-or-abstain agent (8.1, 2026-07-18)

A repo-root SKILL.md (parseable by @tenet/skills’ own SkillRegistry, frontmatter name: verification-first-agent) distilling how to build an agent whose worst outcome is a human handoff, never an ungrounded fact: the one rule, the fail-closed turn shape, 8 techniques (no default answer, one emit edge, grounded-citation guard, fail-closed on infra failure, verify-against-sources, gate-tools-before-run, knowledge-boundary + cache re-verify, required AbortSignal), the anti-patterns that cause hallucination, a build checklist, and TENET as the reference implementation. Generic and zero cross-project content. A regression test in @tenet/skills reads the shipped SKILL.md and asserts it parses + registers, so the flagship skill can never silently become malformed. Suite 1193 → 1195.

Added — llms.txt AEO index for AI agents / crawlers (7.3, 2026-07-18)

A repo-root llms.txt (llmstxt.org format): one-line guarantee, an honest-read context paragraph, and linked sections (Documentation, Core concepts, What ships today) so an LLM or agent can navigate TENET without scraping. Every fact is measured — the test count is the live-suite number, and llms.txt was added to scripts/check-counts.mjs’s source-of-truth list so its “N tests across M suites” claim is now gated by pnpm counts:check too (proven: a wrong count fails CI). No cross-project content, no unsourced claims. Remaining 7.3: llms-full.txt, JSON-LD (SoftwareSourceCode + FAQPage), docs/FAQ.md, OG image + og:image, robots.txt, Pages deploy workflow.

Fixed — reconcile the ROADMAP phase tables to the as-built monorepo (7.2, 2026-07-18)

The ROADMAP’s Phase 2/3 tables still said “not started” for surfaces, connectors, stores, the MCP gateway, WASM sandbox, enterprise-support app, and the Helm chart — all of which actually ship in the tree (verified this change: 9 surface adapters, connectors/ticketing with zendesk/intercom/freshdesk/ servicenow modules, stores/vector/qdrant, @tenet/memory-adapters with mem0/Letta/Zep providers, @tenet/tools-mcp-gateway, @tenet/tools-wasm-sandbox(-wasmtime), apps/enterprise-support, infra/helm/tenet). A “not started” claim for shipped code is a stale-status hallucination in the repo’s own voice. Flipped the stale cells to ✓ with an as-built note (surfaces take an injected transport; the current-state banner is canonical); kept the genuinely-open items honest (eval-set 1k growth, SOC2 prep).

Changed — remove unsourced competitor security claims (measured, not marketed; 7.2, 2026-07-18)

An anti-hallucination framework must not ship unsourced attacks on named competitors — that is the exact self-contradiction it exists to prevent. Generalized every named-competitor CVE/vulnerability claim to threat-model framing that attributes nothing to any project:

Added — counts:check: the live suite is the single source of truth for doc counts (2026-07-18)

scripts/check-counts.mjs (pnpm counts:check, now a CI step) runs the suite, reads the real numTotalTests/numTotalTestSuites, and fails if any tracked doc’s count-claim disagrees — so a hard-coded “N tests across M suites” in a README can never silently fall behind the suite again (a stale number is a hallucination in the repo’s own voice). Fixed the current drift in the same change: the README badge/status and several docs still said 1174; the live suite is 1193 / 92.

Fixed — Reasoner abstains on max_tokens (truncation → never ship, 2026-07-18)

The decides-and-drafts Reasoner (packages/agent/src/reasoner.ts) handled refusal and aborted stop reasons but fell through to envelope-parsing on max_tokens, so a truncated response with a parseable-looking answer envelope could ship a cut-off draft. It now abstains on max_tokens alongside refusal/aborted — a truncated turn cannot be trusted. Regression test asserts a VALID answer envelope with stopReason:'max_tokens' → abstain. Suite 1189 → 1190.

Added — verifier failClosed mode: close the infra fail-open (Phase 3.1, 2026-07-18)

Branch upgrade/top-1pct. Test suite: 1181 → 1189 tests across 92 suites, all green. VerifierConfig.failClosed?: boolean (default false, preserving existing callers). Closes a real ship hole: a broken/down JUDGE marked all claims supported:trueverifyDraft returned pass:true WITH citations → the orchestrator’s grounded-citation guard emitted an unverified answer.

Added — @tenet/agent orchestrator SHIPPED: the driven, fail-closed turn (2026-07-18)

Branch upgrade/top-1pct. Test suite: 1117 → 1174 tests across 85 → 92 suites, all green. 62 workspace packages. This completes Phase 2’s agent turn — AgentState was a state machine nothing drove; it is now driven end-to-end.

Added — canonical ChatModel contract + @tenet/agent Phase 2 start (2026-07-18)

Branch upgrade/top-1pct. Test suite: 1051 → 1117 tests across 83 → 85 suites, all green. 62 workspace packages.

One canonical ChatModel (Phase 1 complete).

Fixed — canonical migration (2026-07-18)

Added — @tenet/agent (Phase 2, in progress, 2026-07-18)

Pending at the time of this entry (SINCE SHIPPED — see the top entry):

Added — Phase 16 (answer-quality engine, 2026-06-11)

Model-agnostic hallucination reduction + smarter/faster responses from ANY ChatModel:

Added — Phase 15 (top-0.01% market gaps, 2026-06-10)

A fresh A-to-Z repo audit (3 parallel reviewers) + live market research over the June-2026 framework landscape (LangGraph 1.0, OpenAI Agents SDK 2026-04, Vercel AI SDK 6 + WDK, Mastra 1.x, Pydantic-AI v1.107, MS Agent Framework 1.0, Google ADK 2.x, A2A v1.0, AG-UI, MCP 2026-07-28 RC) identified the three TypeScript open lanes. All three shipped, plus the audit’s DX + truth findings:

Fixed — Phase 15

Added — Phase 7 (competitor weakness exploit, 2026-06-04)

Web-verified competitor research (LangChain, LangGraph, AutoGen, CrewAI, LlamaIndex, Semantic Kernel, Mastra, Pydantic-AI, OpenAI Agents, mem0, Letta, Zep) surfaced specific weaknesses. We shipped fixes for the ones we can exploit.

Added — Phase 6 (close 3 OSS gaps, 2026-06-04)

Added — Phase 5 (research-led residuals, 2026-06-04)

Added — Phase 4 (BENCHMARKS gated in CI, 2026-06-04)

Added — Phase 1 MVP packages — all landed:

Changed

Security