Skip to content

v0.0.34

Choose a tag to compare

@savant0x savant0x released this 29 Sep 01:43
· 555 commits to main since this release

0.0.34 — 2026-09-28

/provider free — free-options discovery, agent-answerable (FID-2026-0928-012) — closed 2026-09-28

Savant-Code is BYOK — there is no Savant backend or credit balance, so
"free" means free tiers/models of the gateways the user wires with their own
keys, and nothing told the user what those were. New /provider free
subcommand: on invocation (never at boot) it live-fetches the keyless
custats free-models catalog (free-llm-models.custats.info, agent-facing by
design — robots Policy A; 24h TTL cache, 30s timeout, soft-degrade to the
last-good cache with a staleness note on failure), splits free lanes by the
user's OWN wired keys (readStoredProviderKeys ∪ non-empty env vars — the
same truth applyPersistedProviderApiKeys consumes), and lists wired &
free
(models usable today), free but not wired (with the exact
/provider <name> onboarding command), and local & free (ollama, no
key). The same refresh writes ~/.savant-code/free-options.md — the
agent-answerable reference — so the user and the agent share one canonical
surface: ask the agent "what free options do I have?" and it reads that
file. free joins the /provider grammar words (wizard custom-id
reservation picks it up automatically). Content is untrusted third-party
text rendered as read-only information; nothing mutates configuration.

custats feeds wired in as the discovery pipeline's second source (FID-2026-0928-011) — closed 2026-09-28

The free-compute discovery pipeline (providers:harvest) was single-source
(freeairouter.com). New providers:custats leg adds
freetokens.custats.info/feed (RSS, 196 offers — the 87 api_provider
offers parse to an offers ledger with expiry dates; offers are TIME EVENTS
and never enter the host candidate queue) and free-llm-models.custats.info
(OpenRouter-schema catalog, 161 rows — enrichment + retirement signal via
newModelIds/archivedModels/expiration_date; all 6 of its providers are
already tracked). Host attribution is conservative two-mechanism (host
literals + ≥3-char brand-label tokens; "Vercel AI Gateway" prose now
attributes, "Z AI" deliberately lands in the operator-review bucket); new
discovery lanes still enter only through SEED_HOSTS review. Freshness gates
(RSS ≤72h, catalog ≤48h) soft-skip stale cycles instead of failing
automation. LIVE: 43 attributed / 44 unattributed / 1 expiring soon
(Vercel Ling 3.0 window, 6 days out). RED-first: 18/18 pins; the pins caught
two real defects pre-live (fixture lying about the malformed-row contract;
matcher blind to brand-label prose).

Law 3 credit + FID receipt deadlock closed: the write-boundary residual accepted as final (FID-2026-0927-003) — closed 2026-09-28

Closed by operator ruling (2026-09-28): asked to choose between running the
declared gates at write time or accepting the repository-only stale-receipt
demotion, the operator ruled "T90-F3 residual needs no code" — the
repository-only demotion is the final disposition. The F1 fix (Law 3 credit
is outcome-aware; a red bun test can no longer be credited as a pass),
the F2 fix (failure-attributed exemption + self_correct fallback end the
deadlock without opening a masking vector), and the F3 repository-gate half
(a stale receipt demotes to a warning when every declared gate passes the
C3 live re-run) all stand as shipped and mutation-proven. The write boundary
(pre-write-gates-fid.ts:105, scripts/fid-check.ts:40) intentionally
keeps treating a stale receipt as fatal: it cannot run the C3 re-run at
write time, and the operator declines the weaker write-time guarantee. The
FID's Open Remediation section now records accepted behavior, not
outstanding work.

CommandCode GLM ids re-keyed to the live roster; window-truth probe repaired (FID-2026-0928-010) — closed 2026-09-28

The T62-followup(1) register item, grounded LIVE on 2026-09-28. The static
commandcode catalog pinned zai-org/glm-5.2/glm-5.3 lowercase while the
live roster serves zai-org/GLM-5.2/GLM-5.3 — the realign FID's own
CHANGELOG says uppercase, so the lowercase spellings shipped as a
transcription defect that its test pins then embalmed; the wire path sends
catalog ids verbatim after the prefix strip. Catalog, protocol map, and
fallback-table rows are re-keyed to the roster casing, and the two rows'
windows are re-sourced to the gateway's own publication (the commandcode
roster publishes context_length per id — 1,000,000 for both rows, a
strictly conservative correction from 1,048,576 / 1,310,720; upstream votes
kept in the comment trail). The provider's fail-open /models boundary
(keyless GET → HTTP 200 with the full 82-id roster, re-measured 2026-09-28;
TokenRouter/TokenHarbor 401 for the same shape) is vendor-side posture and
is now documented on the registry entry with a pin, not patched. Bonus
repair: verify-window-truth.ts had been structurally red since the
realign — 4 sample rows referenced removed/pre-realign table keys. Samples
re-pointed (incl. tokenharbor/deepseek-v4-flash to orcarouter after a
2-of-3 vote upheld the table row against a bazaarlink drift), two re-keyed
commandcode samples added, and a new in-process pin makes stranded probe
samples a gate failure instead of silent rot (LIVE: probe exits 0, 14/14
rows match). RED-first pin suite observed 1 pass/4 fail pre-change.

Every scripts/** file is now type-gated: 36 errors fixed, boundary widened (FID-2026-0928-007) — closed 2026-09-28

The T84-C triage reproduced the unreproducible 36 errors across 14 files
figure (the earlier TS5102 failures were a cwd/config-resolution artifact)
and categorized every error. Under the operator's Option 1 ruling:
scripts/tsconfig.json now compiles the entire tree (include: ["**/*.ts"]) in the standing typecheck:scripts hard gate — previously a
closed include list covered only the release engine, and not even every test
in its own directory.

The 36 errors fell in four classes, all fixed: 8 real production defects
(audit-evidence.ts wrote a durationMs field its own type denied;
fid-verify.ts normalized Bun's signalCode: undefined to null at the
boundary rather than widening the contract; candidates-io.ts,
harvest-phases.ts/health.ts, propose-provider.ts type-lag repairs),
8 stale buildReceipt calls in fid-verify.test.ts that had silently
verified receipts built with verifiedAt: undefined (bun does not
arity-check), 18 test-fixture friction errors (typed fixtures against
CustomProviderConfig/ReportInput/NudgeCandidate, explicit
as unknown as typeof fetch doubles), and 2 errors in a production-dead
file
(provider-stamp.ts, rewritten as the canonical-typed seam; its
drifted DiscoveryStamp import and hand-rolled stamp fields are gone).

Cloudflare is now setup-surfaced: the dual-credential seam (FID-2026-0928-008) — closed 2026-09-28

Under the operator's Option A ruling, ProviderSetupInfo gained optional
extraEnvVars populated by deriveSetupConfig from credentials.extra, and
Cloudflare flipped setupAvailable: true — the last registered gateway that
was fully routed yet invisible in /provider. The wizard now prompts for the
account id after the token (staged per session in beginProviderSetup,
chained through routeKeySetup's new post-save getNextInputMode hook, with
an emptyValueMessage override so an account id is never called an "API
key"); saveProviderApiKeyExtra persists extras under their own env-var
names in the shared 0600 store; applyPersistedProviderApiKeys applies them
at run start. The provider-exception manifest dropped the stale
setup-exclusion entry — the audit gate flagged it live. Registry test's
setup-provider count pin moved 18 → 19; README (EN + zh-CN) provider
surfaces updated.

Utility-tier duplication is now mechanically enforced (FID-2026-0928-009) — closed 2026-09-28

The shelved idea-shelf report became a filed record, was activated by the
operator, and shipped in three sequenced parts. Part 4 (sweep): canonical
homes created (isRecord in common/util/object.ts, sha256/sha256Hex in
the new common/util/hash.ts, the asOptionalString/asOptionalNumber
pair in the new cli/src/utils/openrouter-models/wire-guards.ts); all 9+8
per-shim guard copies deleted in favor of the shared import; withTimeout
deduped to common/util/promise.ts (the OSC resolve-value variant renamed
withTimeoutValue — different behavior, different name); the four
genuinely-different isRecord predicates renamed for what they are. Final
counts: one body per primitive. Parts 1+2+3 (the gate): the new
no-duplicate-shared-primitive ESLint rule (registry-driven:
shared-primitives.json) errors on any top-level re-declaration of a
registered primitive outside its canonical home, wired active in the
standing eslint . --max-warnings 0 pre-push leg, with 5 fixture-backed
pins. It earned its keep on first run: 7 additional stragglers the manual
scan missed, each explicitly dispositioned (the clean-process attestation
tree's independence is contract, not duplication). Part 5: the rule
header documents harvest inheritance — future provider shims must import the
shared guards, so the fan-out that created 17 duplicates cannot recur
silently.

All eight live catalogs probed: nested-shape reconciliation closes with a null result (FID-2026-0928-006) — closed 2026-09-28

The T96-followup per-vendor live probe ran against every live catalog endpoint
in the registry (all HTTP 200): openrouter 459, nous 418, cleanapis 33
(re-measured by FID-2026-0927-005), apinex 34, kiosapi 36, bai 57, orcarouter
204, nvidia 81, vercel 389 models.
The three parsers that read the nested
architecture.* fields are exactly the three vendors that measurably serve
populated nested fields; the other six serve no modality/tokenizer/
instruct_type data in either position — orcarouter is the instructive case: an
architecture object present on 182/204 models with every field null (an
empty shell that passes presence checks and maps nothing).

Verdict: no code change. Adding a nested read to the six would map
undefined for every model — the speculative-shape trap FID-2026-0927-006
ruled against, confirmed by measurement instead of argument. The null result
is mechanically enforced by negative contract pins (one leg per unmapped
parser, each citing the probe: apinex, bai, orcarouter, vercel, kiosapi,
nvidia), which fail the moment a vendor payload would populate a mapped field.

Also fixed: parseNvidiaModelsForTest — nvidia was the last live parser
without a test seam (Law 11). New openrouter-models-nvidia.test.ts (3 legs).

This closure unblocked FID-2026-0927-006 (the capabilities decision
record), which closed the same day with no code change, exactly as its ruling
stated it would.

The scope register now holds open items only (FID-2026-0928-005) — closed 2026-09-28

Operator ruling: "if the item is completed, it should not even be in scope, only
active/pending items should be in scope."
SCOPE.md could not comply, and the
measurement is the interesting part.

The gate was the obstacle, not the file. Stripping the completed index and
running the live collector produced 111 issues — every one from a historical
session summary, and none from either active FID.
Leg 2 of
scope-register-completeness.ts demanded a register line for every task cited
by "an active FID or a current session summary", and the code read "current"
as every summary written since 2026-09-19. A summary is written when a session
ends, so what it cites is finished by construction — the leg was requiring
completed work to stay in scope permanently, and each finished session added to
that set forever.

Change. Leg 2 now scans the active queue plus dev/agenda.md; session
summaries are no longer scanned. SCOPE.md drops all 34 completed task
sections. 582 → 246 lines, 16 open items, zero ## Task NN sections.

What this gives up, stated rather than absorbed. Work completed in a session
that filed neither a FID nor a CHANGELOG entry now leaves no gate-visible trace.
The compensating control is changelog-coverage, ratcheted against a baseline,
currently 0 uncovered of 423 archived FIDs. The gate also cannot tell a
finished task from an abandoned one — both look identical and both are now
accepted; an abandoned item is caught only if a live surface still names it.

Evidence. 8 new legs in scope-register-open-items-only.test.ts, 4 failing
before the change. Two of the eight pass before and after on purpose — the
retained active-FID controls, so the suite could not be satisfied by gutting the
check. A live adversarial probe confirmed an active FID citing Task 74 and
T83-A still yields both as issues. One pre-existing leg asserted the removed
behaviour and was inverted rather than deleted, so the narrowing stays
visible. The gate also caught this FID's own first draft for citing a task
number inside a fenced block — withoutInlineCode strips only single-line
spans, so a fence is a live claim, not a quotation.

The scope register was 74% historical prose and its pending work was in three places — closed 2026-09-28

An audit of SCOPE.md found the file breaking the rule its own header states
40 lines earlier ("a completed task is indexed here to one line"). Four defects,
all now fixed.

The open list was a third of the pending set. 15 items were open; the
Open items section held 8. Six more checkboxes — T95-B, T95-C, T97 and the
three Code Universe rulings R1–R3 — sat in per-task sections 120+ lines further
down, and Task 90's F3 residual was open in prose with no checkbox at all. A
reader asking "what is open?" got the wrong answer. All 16 now live in one
section, grouped by area, each keeping its full blocker text.

296 lines of completed-task prose sat under a heading that called itself an
index
, with per-section detail averaging 9–11 wrapped lines. Now one index
entry per task carrying the FID id, receipt, the substantive finding and a
CHANGELOG/summary pointer. The four largest sections collapsed hardest: Task 95
85 → 12 lines, Task 90 43 → 8, Task 92 39 → 16, Task 93 27 → 9.

The index boundary was not the open/complete boundary. Tasks 90, 95 and 97
were marked OPEN/PARTIAL/rulings-pending above ## Completed — index, while
Tasks 87, 84 and 62 sat inside it carrying open sub-items. Each index entry now
says so explicitly and points at Open items.

A 16th open item was hiding as prose. The cross-catalog nested-shape
reconciliation was carried as "deferred to FID-2026-0927-006 → Deferred" at
the foot of a COMPLETE task heading — wrong twice over, since that FID is
converged and still active and its subject is the capabilities field (Task
93, ruled NO). It is now a checkbox with a named blocker. It is also the clearest
live demonstration of a gap in scope.prohibited-disposition: the guard matches
the notation deferred:: / [DEFERRED], so the same disposition written as
prose passes.

Two factual corrections rode along: a severed sentence in the Task 84 entry
(T84-C remains orphaned mid-clause and restated two lines later), and a
register line asserting the 09-19 → 09-27 wave was uncommitted — it is in
history, verified against 462 commits.

No gate reads a section body. collectRegisteredTasks
(scope-register-completeness.ts:69-83) matches only the ## Task NN heading
and TNN-X item lines, so condensing bodies was provably safe. All 34 task
headings are retained and two were added (Task 68, Task 96), making register
coverage strictly larger. validate:repository, lint:md,
scope-register-check, quality:report and prettier --check all pass.

v0.0.34 — two built-in gateway providers (Vercel AI Gateway, Clean
APIs
), both live-verified end to end, and a critical soundness fix to the
Law 3 verification gate
that had been crediting a red test suite as a pass.
Also in this release: the @@ hunk-header diff fix, four mutation-proven
guards against controls that looked load-bearing and were not, a standing
type-check surface for the release engine, and the release-recovery path
(--resume plus an npm read-after-write retry) that was dead. Governance:
agents may stage, commit, and push granular commits to origin main; force-push,
history rewrite, and release mutations remain operator-only.

Code Universe: six colours below AA, an 8px type floor, and a bypassed palette (FID-2026-0928-004) — closed 2026-09-28

Severity medium. A design pass over the offline Code Universe export
(FID-2026-0928-003) found four defects in its stylesheet, all now fixed.
D1: six text pairs failed WCAG AA 1.4.3 at the 8–9px sizes they are used at
— the worst was .document-line-number #3f6681 at 3.24:1, the gutter on
every line of every document. Each colour was lifted per background to ≥4.62:1;
#607899 needed two different replacements because its three uses sit on two
differently-composited backgrounds. D2: the declared palette was bypassed —
--muted and --line were re-typed as literals in 14 places. Eight tokens
added, ten families routed: body hex 122 → 52 (distinct 57 → 40), var()
58 → 130. D3: 29 of 38 font-size declarations sat below 11px; the
floor is now 10px. D4: only three focus rules existed for ~15 control
groups; one shared cyan ring now covers ten. The regression suite reads the live
declaration out of the CSS, so reverting a value re-breaks it — 13 of 14 legs
failed before the change. No layout, markup or behaviour change, and the
deterministic-artifact gate stayed green. Open: R1 (the OS-window metaphor
and atmosphere are a Gate 7 product-identity call), R2 (short-viewport clipping
is NEEDS-REVIEW — only settleable by rendering), R3 (40 literals remain, needing
color-mix()).

The FID status parser could not read a bullet-form **Status:** line (FID-2026-0928-002) — closed 2026-09-28

Severity medium. Found by a dev/fids/ audit. STATUS_LINE
(packages/agent-runtime/src/echo/fid-verification-enforcement.ts:30) was
line-start-anchored, so the - **Status:** … metadata-bullet form written by
the older record template parsed as undefined — and the failure reason
misreported those records as having no status line at all. 12 of 423
archived FIDs were affected, one of them (FID-2026-0917-004) genuinely
declaring verified. Two further copies of the same anchor lived in
fid-validator.ts:67,249; all three now read through parseFidStatus, so the
status has one definition. The read was repaired rather than the 12 records
rewritten, which touches no history and covers future records. 11 unit legs,
5 red before the change.

The measured blast radius is zero today, and the FID says so. All 12 records
are in the archive, and activeFidFiles() (scripts/fid-check.ts:20-27) scans
only dev/fids/ top level, so the defect is latent rather than live: the first
bullet-form verified record left active would be silently exempt from the
verification contract. Two claims in the first draft of that FID were wrong —
it rated itself high, and set a verification criterion
(fid:verify --check must name the record) that was unsatisfiable because that
scan never reads the archive. Both were corrected in place rather than dropped.

skills:check read backticked prose as shell commands (FID-2026-0928-001) — closed 2026-09-28

Severity low. Project-scoped errors 17 → 3. One root cause: the
command-allowlist rule matched inline spans with /`([^`]+)`/g over the raw
skill body, without excluding fenced blocks. A ``` fence contributes
three backticks — an odd count the regex cannot pair — so it swallowed the
closing fence and mis-attributed every subsequent inline span. The false
words were the visible symptom (markdown is a fence info string, with and
and are second words of unrelated sentences); the real finding is that the same
defect also swallowed real commands after a fence, so the rule was
under-reporting as well as over-reporting. Fixed for all three forms — fenced
block, inline 3+ backtick run, and label: value record — and mutation-proven:
6 of 8 new legs fail with the extractor reverted. BLOCKLIST_PATTERNS runs
against the raw body and is not narrowed, so no dangerous pattern lost
coverage. Three quarantined SKILL.md files were also repaired: one had an
unquoted description whose colon-space made gray-matter throw, and the
early return on that finding was hiding four further defects in the same file;
one carried a duplicated frontmatter block. Open: tauri is a genuine CLI
absent from the command allowlist — a security-control change reserved to the
operator. Separately, discoverSkillDirs was reporting a legitimately
untrusted skill as SKILL.md not found: a skill's version ledger lives in the
trusted tier by design while its content sits in .quarantine/, so the checker
named the same skill twice — once missing, once as its own draft. Fixed by
skipping a ledger-only trusted dir that has a quarantined twin, while a ledger
with no draft still fails closed. skills:check project-scoped errors
17 → 3 → 1. The remaining finding is the tauri allowlist ruling (T95-B);
both remaining items are registered in SCOPE.md.

Law 3 credited a red test suite as a pass (FID-2026-0927-003) — fixed 2026-09-27, residual open

Severity critical. One representational root cause produced opposite
failures: Law 3 credit was shape-aware (hasToolResultError reads only
structured error fields) rather than outcome-aware.

  • F1 (critical, implemented). A failing bun test emits a non-zero
    exitCode plus plain text and no structured error, so the old predicate
    returned false and the gate credited a red suite as a pass — the gate was
    inert for the dominant failure mode and produced no evidence of its own
    failure. The exitCode was already in the payload and never read. Fix:
    hasCommandFailure in packages/agent-runtime/src/tools/tool-executor/tool-result-errors.ts,
    wired at result-lifecycle.ts:176 and custom-result.ts:134; 11 RED-first
    legs in command-outcome.test.ts including the end-to-end credit contract.
    writeSucceeded deliberately keeps the shape-aware predicate — a command
    that ran and exited non-zero is a successful write of a failing program.
  • F2 (liveness, implemented). verification-failure-paths.ts attributes a
    red run to the files it names, EnforcementState.failureAttributedFiles
    carries the set, and pre-write-gates-law3.ts permits the named file while
    still blocking every other dirty-unverified file. The anti-masking leg is
    pinned by a test asserting blocked === true. 13 legs in
    law3-failure-attribution.test.ts; five audit findings fixed, including an
    inert fsmPhase that no production caller supplied and a vacuous fail-closed
    test that would have passed with the exemption deleted.
  • F3 (partial — residual open, named not hidden). The repository gate now
    demotes a stale receipt to a non-fatal warning when every declared gate
    passes a live re-run, mutation-proven end to end on a real fixed FID
    (REAL_EXIT=0, byte-identical restore). Still open:
    fid-check.ts:40 and pre-write-gates-fid.ts:105 treat staleness as fatal,
    so the doc-edit loop survives at the write boundary — demoting there
    requires running the declared gates at write time, which the write path does
    not perform. That is a design tradeoff for the operator, not a patch.
  • F4/F5 are recorded rejected hypotheses, not defects. "Incomplete arguments
    for tool X" is a native LLM tool-call error (sdk/src/impl/llm/errors.ts:102)
    affecting every tool, not a str_replace serialization bug; and EHEL tracks
    dirtiness in in-memory Sets, so it never reads the git index — the observed
    git mv behaviour was plain git semantics.

Ruled: OpenRouterModel must not carry a capabilities field (FID-2026-0927-006) — converged 2026-09-27

Severity low. A decision record — zero code changed; the deliverable is
the decision and its evidence. Ruled during the Clean APIs work when that
vendor's catalog exposed the field and no live parser read it.

Cross-tabulated over all 33 live models: vision ⟺
architecture.input_modalities.includes('image') with 0 disagreements (the
28/5 split is identical in both fields); tools ⟺
supported_parameters.includes('tools') with 33 agree / 0 disagree;
reasoning is likewise derivable. streaming and json_mode are 33/33
constant
, so neither can discriminate between picker entries. Four of six
flags carry no information the payload does not already state
— a Law 13
violation (two truths for one fact) with no consumer. Only web_search
(11/33) is independent, and one boolean would hold it, not a six-element array.
model-info.ts:67-68 is the only genuine consumer of the capability-shaped
fields and it renders modality — which capabilities does not even contain;
openrouter.ts:55,60 merely writes it. supported_parameters has zero
in-repo presence, and capabilities is sent by one provider in twenty, so the
field would be undefined almost everywhere.

Clean APIs as a built-in provider (FID-2026-0927-005) — Closed + archived 2026-09-27

  • The design decision came from the probe, not the runbook.
    GET https://cleanapis.com/v1/models returns 401 keyless, which is the
    healthy boundary signal: the route exists and is auth-gated. That made this the
    authenticated live-catalog pattern (Nous / KiosAPI / B.AI) with a
    resolveKey wiring — a url-only registry entry would have looked correct
    and silently never listed a model. One entry in the second registry partition
    (spread at registry.ts:189), the catalog wrapper, gateway registration, and a
    live-catalog exception-manifest record with owner + evidence.
  • Live round-trip, both legs. Authenticated catalog 200 / 33 models, and
    POST /v1/chat/completions 200 OK returning content: "PONG" (500
    tokens) — not 401, 402, or 404. idTransform: 'strip' is proven rather than
    assumed
    : upstream ids are genuinely bare ("muse-spark-1.1", no
    openai/ segment), and the completion echoes the bare id back unchanged.
  • A defensive guard that was a routing bug. The first implementation guarded
    the prefix with startsWith('cleanapis/'). Because strip removes exactly
    one
    segment, that guard would have sent the wrong id on the wire for any
    upstream id sharing the prefix (cleanapis/router → router). Neither
    live-catalog precedent (vercel, orcarouter) has it — it belongs to
    custom providers, where the operator types their own prefixed ids. The
    eight-leg suite caught it; the code was changed, not the test, because the
    test's expectation was right.
  • The catalog served models the public site does not advertise
    (muse-spark-1.1, claude-opus-5.5, claude-sonnet-5) — a static pin would
    have been wrong on arrival, so the live catalog is load-bearing, not
    decorative.
  • Open remediation, named rather than folded in. This vendor serves the
    OpenRouter-shaped fields nested (top_provider, architecture,
    pricing), so OpenRouterModel.topProvider is never populated even though
    openrouter.ts and lookup.ts already populate it. Recorded as a
    structural mapping gap for its own record; the first draft's fix (read
    top_provider.max_completion_tokens into maxCompletionTokens) was wrong
    and is withdrawn. The per-model capabilities array is the only one of the
    vendor's 13 fields with no home on the type — and no other live catalog reads
    it either.
  • Doc surface. 13 hand-maintained surfaces synced (named by the drift
    checker) after generate:provider-docs; the drift gate fails closed when a
    provider entry lands and a hand-maintained surface misses it.

Unignored vendored tree + three silent-failure classes (FID-2026-0927-002) — Closed + archived 2026-09-27

  • Four guards, each mutation-proven before it was trusted — the method that
    found the counterpart in the first place. A release-readiness audit found the
    gate contract green 12/12 while four controls were unverified: an ignore rule
    scoped to a sibling directory, a test-timeout key Bun silently ignores, a
    coverage gate that tolerated the exact abbreviation class it was built to
    catch, and a 10 s cli flake with no surviving name. The unifying root cause is
    that a control which has never been broken is a belief, not a control.
  • F1a — the 77 MB / 4,150-file cline-main/ tree was unignored at the repo
    root
    , one level above the /resources/ rule that already covered it, and
    inside the release sweep's git add --all blast radius
    (git-publish.ts:78). Resolved by operator move under resources/ — the
    path was wrong, the rule was not, and no new ignore entry was needed.
  • F1b — scripts/audit-vendored-tree.ts: a top-level directory with no
    tracked files that git does not ignore is a foreign tree. Wired at
    validate-repository.ts:19,248, spread at :274, so it runs in the release
    contract (public-release/gates.ts:12,29) and not just locally. Proven by
    staging a synthetic foreign root (FAIL, exit 1) and removing it (PASS).
  • F2 — scripts/audit-bunfig-test-timeout.ts: fails when
    cli/bunfig.toml declares a [test] timeout, which Bun 1.3.14 does not read
    (timeout = 1 left a 36 ms test passing). Comment → mechanical gate.
  • F3 — abbreviation advisory + baseline staleness: range notation is
    legitimate house style, so collectAbbreviatedReferences advises rather than
    rejects, and collectStaleBaselineEntries fails a knownGaps entry the
    changelog already covers — the ratchet can tighten, never rot.
  • F4 — the 10 s flake is named, and it was not the test budget. Re-running
    under contention reproduced 5 failures, every one
    Expected to contain: "--agent" / Received: "" — the fingerprint of a spawn
    killed mid-boot. The cause was SPAWN_KILL_MS = 10000, the spawnSync kill
    budget in e2e-cli.test.ts: the per-test budget had 60 s of room and the child
    still died inside spawnSync, where it has no jurisdiction. Raised to
    45_000 and verified at the real pre-push load (typecheck + eslint + suite
    concurrently) — 7 pass / 0 fail, slowest test 2,181 ms, 20x under budget.
    Explicitly not claimed at 3x oversubscription, where 4 tests still meet the
    ceiling: that load is roughly 3x anything the pre-push gate produces, and no
    wall-clock budget survives it. The harness, not the constant, binds there.
  • Two measurement lessons, both paid for. A tail -8 chosen for readability
    destroyed the original diagnosis; 48 spinners chosen for severity would have
    destroyed the fix's validation. A bound is a decision, and both went wrong in
    opposite directions.
  • CHANGELOG coverage gate ended empty-baseline: 419 archived FIDs, 0
    uncovered, baseline 14 → 0 — after fixing two parser bugs its own tests caught
    and widening notation awareness to five forms.
  • CHANGELOG: release-readiness audit of the uncommitted Sept 19–27 wave
    (entry written 2026-09-27).

Diff panel: the @@ hunk row was rendered in the diff accent (FID-2026-0927-004) — Closed + archived 2026-09-27

  • Operator report: "on the EDIT block's, line 0 shows '@@', in teal.
    However, there is no reason for that first line to be a special color."

    Confirmed still present in this tree, so the report was not already-fixed.
  • The @@ row was the loudest element in the panel while carrying no change
    information.
    diff-viewer.tsx gave it two accent treatments no other
    metadata row received — foreground theme.diffHunkHeader (#18faf9, the
    design system's primary cyan) plus a cyan-tinted full-width bar (#15d5d5) —
    while the diff --git / --- / +++ rows got a single muted
    theme.diffMeta. It also contradicted the component's own docblock rule that
    "metadata rows render muted so the change content dominates". The token is
    deliberately retained: it is part of the declared design-system contract,
    and removing it is a theme-system refactor rather than a diff fix.
  • Colour fixed, then the row itself was removed (operator directive: "just
    trim/suppress the first row that contains @@"
    ). The leading @@ only restates
    the start numbers the gutter already prints beneath it. Later @@ rows are
    deliberately kept
    : each marks a real elision — rows jump from line 3 to line
    20 — that no other row communicates. Suppressing the first is more correct
    than suppressing all, because the first marks nothing.
  • The filter lives in the renderer, never the parser. parseDiffLines owns
    the counter reset (diff-stats.ts:120); dropping the row there would blank the
    gutter on every row — a cosmetic change silently destroying line numbers. A
    dedicated leg pins exactly that.
  • Both halves mutation-proven, not asserted. Restoring the teal yields
    Expected: "#8f8f99" / Received: "#18faf9"; suppressing all hunk rows
    instead of the first yields 7 pass / 3 fail. Both restores byte-identical.
  • Two vacuous-test traps, found and closed. In the dark palette muted and
    diffMeta are both #8f8f99, so a whole-document colour assertion was
    satisfied by the gutter spans and passed with the hunk row still teal. The
    same leg then went vacuous a second time once single-hunk diffs stopped
    rendering a row at all. Both rewritten to be structurally scoped to the hunk
    row rather than the document.
  • A patch that deleted what it meant to edit. One edit removed the whole
    lines.map block, leaving the panel rendering only its header strip — and
    tsc passed, because removing JSX is type-correct. Caught by re-reading the
    file after a subsequent patch failed to match.
  • CHANGELOG: the @@ hunk row no longer spends the panel's loudest colour on
    a line that carries no change information (entry written 2026-09-27).

Vercel AI Gateway as a built-in provider (FID-2026-0927-001) — Closed + archived 2026-09-27

  • Provider vercel shipped from one registry entry, measured against the live
    gateway first.
    Vercel AI Gateway (https://vercel.com/docs/ai-gateway) added as
    built-in provider vercel: kind: 'gateway', protocol: 'openai', base
    https://ai-gateway.vercel.sh/v1, AI_GATEWAY_API_KEY with the masked
    /provider vercel store as the second credential path (shell precedence per
    FID-2026-0804-001), keyless live catalog at /v1/models, and idTransform: 'strip' so upstream ids that are vendor-namespaced with slashes
    (openai/gpt-6-astra) keep exactly one segment stripped (vercel/) on the wire
    — the orcarouter precedent. The entry lives in a new second registry partition
    (registry-partitioned-2.ts, spread into PROVIDER_REGISTRY preserving
    Object.keys order) because registry-partitioned.ts sat at 298/300 lines and
    the quality ceiling forbids one more entry.
  • The gateway is OpenAI-shaped, not OpenRouter-typed — and the runbook was a
    claim, not the contract.
    The catalog publishes context_window/max_tokens
    where the shared types expect context_length/max_completion_tokens, and a
    numeric created against a string field; the new parser
    (cli/src/utils/openrouter-models/vercel.ts) maps the drift explicitly instead
    of trusting the archived walkthrough, which also under-documented the
    FID-2026-0809-008 audit contract: a live-catalog entry in
    PROVIDER_EXCEPTION_MANIFEST with owner + evidence paths is required or the
    registry audit fails closed. Both gaps grounded in the current contract modules
    before any code. In-pass repair: gateway.ts's doc header still named two
    removed providers and omitted Atria AI.
  • RED-first, then every gate green; credentials pinned both ways. Named
    failures before any source change (common 4 fail, cli 3 fail + module-missing
    import, sdk 2 fail) — the sdk routing leg drove the live chain and captured
    POST https://ai-gateway.vercel.sh/v1/chat/completions with Bearer auth and
    the stripped wire id alibaba/qwen-3-14b. Then 65/0 + 476 expects (common),
    27/0 (cli), 10/0 (sdk) on the touched suites; bun run test 12/12 workspaces 0
    fail; typecheck exit 0; eslint 0; prettier clean; lint:md clean;
    quality:report PASS (1498); validate:repository PASS;
    generate:provider-docs:check clean. Two new suites (parser contract;
    two-way credential legs split at the 300-line ceiling) plus widened pins in four
    existing suites. Keyed live inference deliberately not claimed — no
    AI_GATEWAY_API_KEY existed in this session. Receipt 13/13 live, re-stamped
    at the archived path (sha256:8e60648f…, 2026-09-27T16:07:49Z). Commit SHA
    pending operator git execution (G2 withheld).

Release stages that reported verdicts they had not established (FID-2026-0924-001) — Closed + archived 2026-09-25

  • Four asymmetries, one engine. An audit of the public-release pipeline for the
    class the entry below named — a single-shot read, or a mode/parameter only some
    branches honor — found four places where the pipeline said something it had not
    established, plus one declared constant no production path honored. DESKTOP_BUNDLES
    injected a sleepMs lambda that ignored its own ms argument, so the stage polled
    GitHub at 1 Hz for up to 45 minutes (≈2,700 requests where 90 were intended) against a
    client with no 429 branch and no retry; validateResumeReceipt reported git/bun/npm
    failing to run as a receipt-vs-HEAD mismatch; the gh precheck reasoned from the mode
    alone while the desktop stages are automation-only and shell out to gh; and
    pruneLocalOnlyFailedTag returned one false for "the tag is on origin" and for
    "origin was unreachable", so preflight asserted a tag state it had not read. The desktop
    poll window was also the only bounded wait an operator could not widen.
  • All five repaired with every decision preserved fail-closed. desktopRunPoller is
    the single production poller and honors the interval it is passed; the resume failure
    names the tool that did not run; requiresGhCli(options, env) derives the requirement
    from the mode and the desktop flag; FailedTagPruneOutcome
    (pruned | kept | kept-unverified) keeps "present" and "unknown" distinct while the
    guard still never deletes on an unverifiable claim; the desktop window reads
    SAVANT_RELEASE_DESKTOP_TIMEOUT_MS / SAVANT_RELEASE_DESKTOP_POLL_MS through the
    shared positiveEnvMs. Retry/429 was deliberately not added to
    githubApiRequest — it carries non-idempotent POSTs (/releases 201, /dispatches
    204), so the load was removed at its source instead of tolerated downstream.
  • Six pins, two proven load-bearing by mutation. Restoring the argument-ignoring
    lambda fails the cadence pin at 1002.88 ms; collapsing the verdict branch fails the
    tag-state pin. Release suites 143 pass / 0 fail across 22 files at the time; receipt
    5/5 live.
  • Closed 2026-09-25 on operator directive. At closure the record was found carrying
    verified while its receipt block still read pending — a status pointing at an
    artefact that was not in the file — so the receipt was stamped (5/5 gates) and then
    re-stamped LIVE at the archived path (sha256:0cbb8d44…, 2026-09-25T15:31:07Z) so the
    closed record stays byte-consistent with its final content. Commit SHA pending operator
    git execution (G2 withheld).

Release tooling gets a standing type-check surface (2026-09-25)

  • scripts/ had no tsconfig, so a wrong signature there was invisible to every
    standing gate.
    Discovered while implementing the --resume fix recorded below: the
    changed modules live in scripts/, which no tsconfig covers, and the root typecheck
    chains 12 workspaces only — the sync→async change to verifyPublishedPackage in that
    same session is the worked example. A purpose-built probe measured the real surface:
    117 errors across ≥20 files, concentrated in scripts/providers/** (which carry
    their own typeof fetch seams and double-assertion casts) and the scripts/__tests__
    test globals. A naive scripts/** config without the root extends is not a viable
    shape at all — it produced 126 module-resolution errors from the workspace imports
    alone, which is why the measurement was re-run correctly rather than quoted from the
    first attempt.
  • scripts/tsconfig.json added and wired as typecheck:scripts, and the surface was
    made green rather than scoped around.
    Root-inheriting, so strict and the repo
    paths aliases apply; it covers the release engine plus its suites, is appended to the
    typecheck chain in package.json, and is mirrored in protocol.config.yaml →
    commands.type_check. No manifest edit was needed, contrary to the item's recorded
    assumption:
    validateCommandParity runs over the workspace inventory (the
    package.json workspaces) and scripts/ is not one, so a VALIDATION_WORKSPACE_POLICY
    entry would itself have been flagged as missing from the inventory. The engine itself
    had no errors, but 7 surfaced in two suites — one class, fetch doubles rejected because
    typeof fetch is Bun's global (it carries preconnect) while the seam only ever calls
    (input, init?) => Promise<Response>. Instead of a double assertion per test the seam
    is declared once (public-release/fetch-seam.ts → ReleaseFetch) and five modules
    consume it (github-api.ts, assets.ts, desktop-manifest.ts, stages-verify.ts,
    desktop-workflow.ts).
  • Load-bearing, proven by mutation. Changing positiveEnvMs's return type to string
    fails the leg at 5 call sites — the exact class of signature change that previously
    required a hand-built probe. Evidence: bun run typecheck:scripts 0 errors exit 0; full
    bun run typecheck exit 0; release battery 145/0 across 24 files. The remaining untyped
    boundary (scripts/** outside the release engine) is carried as an open
    operator-scoped item in SCOPE.md, named rather than trimmed.

/init scaffold parity and release-sweep disclosure (2026-09-25)

  • Four "orphan" type files in .agents/types/ were the dependency closure of a stale
    tracked hub, not standalone cruft.
    .agents/types/agent-definition.ts imported
    ./agent-runtime, ./model-name and ./provider-options and re-exported
    ./available-tools, so deleting the four alone would have left four unresolvable
    imports in a tracked file. Measured before repair: agent-definition.ts (8654 B vs
    template 8990), tools.ts (4524 vs 3108) and tool-params-discovery.ts (2856 vs 5395)
    were stale, and the four generation-1 siblings were in neither COMMON_TYPE_FILES
    nor the template directory. The premise was presented back rather than executed, and
    the corrected plan approved: sync the three from the template, then remove the four.
    Evidence: cmp -s 8/8 OK; zero references to the four names remain under
    .agents/; the relative-import closure resolves (7 edges, 0 dangling).
  • /init is now pinned end-to-end through the real command, in a real temp project.
    cli/src/commands/__tests__/init-scaffold.test.ts drives the real command through the
    real project-root seam into a real temp dir: every sibling lands byte-identical to the
    template, the copy table equals the template dir exactly (a hub whose sibling never
    ships fails the pin), none skipped, none orphaned, and a second run never clobbers an
    operator edit. 4/4 pass, 65 expects. The first version of the closure leg failed and
    was right to: the scan matched from './types/agent-definition' inside the scaffold's
    own doc comment, so the parser now strips comments first and carries a non-vacuity
    guard.
  • A never-committed file can no longer enter a release commit silently.
    commitAllAutomationChanges runs git add --all over the whole worktree but warned
    only about dev/ + SCOPE.md, so any other untracked path was swept in unannounced.
    New exported untrackedPaths(porcelainZ) reads ?? records from the -z status
    form — measured: -z yields unquoted, NUL-separated records, while the non--z form
    quotes a path containing spaces, which is exactly how such a comparison goes silent.
    Pinned both ways (a plain path and a path with spaces; a tracked-only sweep stays
    silent) and proven load-bearing by mutation — collapsing the ?? filter to !! fails
    the positive leg (Received: ""). Call-graph: untrackedPaths →
    commitAllAutomationChanges → commitAutomationChangesOrTagHead → stages.ts:92.
  • Three further in-pass findings, all fixed in the same pass. The live format gate was
    already red on two files this session did not write (both uncommitted from 2026-09-24);
    two suites exceeded the 300-line quality ceiling and were split along their existing
    seam (the desktop workflow-contract block →
    public-release-desktop-release-contract.test.ts, the sweep-disclosure pins →
    public-release-sweep-disclosure.test.ts) with no test dropped — the battery reports
    the same 145 tests across 24 files; lint:md was red on two raw inline-HTML
    occurrences, fixed by wrapping the words in backticks; and FID-2026-0924-001 claimed
    verified over a receipt that still read pending, cleared by running the protocol's
    own bun run fid:verify <fid> --write (5/5 PASS).
  • Gates: bun run typecheck 12/12 exit 0; eslint . --max-warnings 0 exit 0;
    prettier --check . clean; lint:md clean; quality:report PASS (1498 baselined
    files)
    ; validate:repository PASS (was FAIL, 10 issues); release battery 145
    pass / 0 fail across 24 files
    ; cli init suites 14/0. Nothing committed (G2 withheld).

The /init scaffold is now import-free (2026-09-25)

  • A generated scaffold that imports a monorepo dependency compiles in the monorepo and
    fails in the user's project.
    util-types.ts:1 imported zod/v4/core, which resolved
    only because the root, cli, common and sdk manifests all declare zod; a fresh project
    with no node_modules reported util-types.ts(1,59): error TS2307. Operator ruling
    (verbatim): "Make the /init scaffold self-contained so it needs no external package,
    resolving the zod/v4/core dependency."
  • JsonSchema is now the accept-bound the runtime actually enforces, reached by
    measurement rather than preference.
    It is boolean | JsonSchemaObject, with
    JsonSchemaObject an object whose values are unknown — the runtime compiles schemas
    with convertJsonSchemaToZod at agent load, and schemas also arrive from dynamic
    sources checked at runtime rather than statically. A JSONValue bound was tried first
    and rejected on evidence: the dynamic-template layer produces
    {[key: string]: unknown}, so a JSON-value bound is narrower than what the pipeline
    accepts, and typecheck in common failed TS2322 on
    dynamic-agent-template.test.ts.
  • Evidence: a temp user project with no node_modules at all compiles under
    tsc --strict at exit 0; zod now appears 0 times in both
    .agents/types/util-types.ts and the shipped template; .agents/types/util-types.ts
    re-synced byte-exact to the template; the /init pin's reviewed-dependency ceiling is
    now empty with an anti-vacuity guard (4/4 green); full bun run typecheck 12/12
    exit 0.

The release recovery path was dead: --resume refused by its own guard, and an npm read-after-write with no retry (FID-2026-0922-001) — Closed + archived 2026-09-24

  • The --resume remedy could not run, and the block's own message recommended
    it.
    checkStaleTag returned severity: block whenever the release tag existed
    on origin — in every mode — while the resume argument it was handed was
    consulted only on the local-only branch. Every release failure after GIT_PUSH
    (npm publish, desktop stages, asset-retry exhaustion, post-release verification)
    was therefore unrecoverable: the command named by the failure itself exited 1 at
    its first step. Fixed by scoping the tolerance to the mode it belongs to —
    mutation mode still blocks a genuine re-cut (the shipped-version guard, retained
    and still pinned), while resume warns and keeps the tag, because the tag on
    origin is the release being finished and its HEAD binding is verified
    downstream (preflight.ts dereferences the existing tag and fails unless it
    points at release HEAD; the transaction re-checks the receipt's HEAD binding).
  • The npm read-after-write had no retry while its sibling did. The v0.0.33 cut
    exited 1 on a registry-propagation race the registry itself had just warned
    about ("being processed and may take a few minutes to become available"):
    packageIsPublished read the registry once, while verifyReleaseAssets
    immediately before it retried for a bounded window. The presence check now polls
    a bounded, env-overridable window (SAVANT_RELEASE_NPM_TIMEOUT_MS /
    SAVANT_RELEASE_NPM_POLL_MS) and still fails closed on expiry with the
    --resume remediation; only the not-found class is retried, so a broken
    environment is never papered over by waiting. The shared retry-window parse moved
    to one module (public-release/env-ms.ts) instead of a second private copy.
  • Pinned RED-first, and proven load-bearing. Both new pins failed against the
    unmodified tree — the guard pin with Expected: "warn" / Received: "block", the
    npm leg with a missing export (no bounded window existed at all) — and collapsing
    the window to single-shot afterwards made the absorb leg fail in 0.73 ms. 4 new
    pins; the release suites report 138 pass / 0 fail across 22 files; typecheck
    12/12, eslint --max-warnings 0, prettier, lint:md, quality and
    validate:repository all green.
  • The v0.0.33 receipt itself remains unreachable by design — its HEAD binding
    means any fix commit changes HEAD and the transaction refuses — so it stays
    recorded as the hand-verified erratum rather than a rewritten receipt.
  • Receipt re-stamped LIVE at the archived path (3/3 gates, sha256:a3fe41e4…).
    Commit SHA pending operator git execution (G2 withheld).