Repository navigation
Releases: PerryLink/jevcore
Release list
v0.4.1
0.4.0 was staged and never released, except for the CLI. This is what an
adversarial pass over it found, and the first item is why the release was pulled.
Fixed
-
jev_askreturned invalid output on every call, in every real host. The
host validates a tool's return value against the schema that tool declares, on
the ordinary dispatch path, and rejects an undeclared key.JevServicestamps
egressonto every result,renderResultforwards it, andask.tsreturns
renderResult(...)verbatim — whileask.ts's output schema declared neither
truncatednoregress.rank.tsandcheck.tsdeclared both. So every call
came back astool "jev_ask" returned invalid output: "value.egress" is not a declared property (additionalProperties: false).This was already broken in the published 0.3.1. The schema and the core's
stamping are byte-identical atfc8f016; only the tool that returns the core's
rendered payload directly was affected, which is whyjev_rankandjev_check
kept working. A thousand lines of tests never saw it because they drive
definition.execute(...)— one level below the validator the registry runs on
every top-level call.test/tools.test.tsnow asserts each tool's payload
against its own declared schema. -
The question map's redaction leaked secrets nested inside a question. The
fix that stoppedcredential_exposurebeing replaced by"[redacted]"disabled
the key rules for the entire question subtree, so a secret named by its key
inside a question stopped being redacted at all:noul({ password: '…' })
reached the provider verbatim andredactionswas0. Now only the map's
top-level ids are exempt — they key the answers and are protocol
identifiers — while each question's value is redacted with the full rules. -
The shape guard did not fire for every reshape. A redactor returning a
number or a boolean produced no error at all, becauseObject.entries(7)is
[];nullproduced aTypeErrorrather thanEgressShapeError. The guard
now requires a non-null, non-array object whose id set is exactly the declared
one. -
recent()'s freeze was shallow, so the record could be rewritten.
recent()[0].redactionRules.push('FORGED')succeeded and changed what
stats().lastCallreported: the record object was frozen, its array was not,
and it was the array the service kept. This package describes the call history
as the honest record of what happened, and a record a caller can edit is not
that. -
An open breaker still charged the call budget.
budget.reserve()ran before
breaker.assert(), so a provider that had been failing burned the budget on
calls that were never made — measured at three of three consumed with zero
transmissions. The breaker is consulted first. -
A cache failure could be recorded as a provider failure, turning a storage
bug into an unhealthy endpoint and incrementingfailuresfor a call whose
provider answered correctly. A cache hit also droppedwarning, so a
synthetic answer came back from the cache looking like a real one. -
The safety and context gates dropped the egress facts, the same defect class
fixed in the MCP and DSH adapters earlier in this release: they rebuild a
JevResultfield by field, and the rebuilt value losttruncatedandegress. -
The published CLI answered
--versionwith the wrong version.
jevcore-cli@0.4.0reported0.3.1, because the constant was kept in step with
package.jsonby hand and the hand slipped during the bump. It is read from the
manifest at run time now, so there is no second copy to forget. -
The GitHub Action's output heredocs were not delimiter-safe. A stdout line
readingJEV_RESULT_EOFclosed the heredoc early and every line after it was
parsed by the runner as a further$GITHUB_OUTPUTcommand — an output whose
name came from the document. The delimiters are generated per run. Separately, a
newline in thecommandinput forged a second::error::annotation line;
newlines are stripped from any value before it reaches a workflow command. -
scripts/check-workflow-shell.mjsboth missed violations and invented them.
It did not seerun: |2-, a plain scalar whose value continues on the next
line, orrunwritten inside a flow mapping — and it reported backticks inside
quoted-heredoc bodies and single-quoted strings, where bash expands nothing at
all. A guard that is wrong in both directions teaches people to ignore it.
Changed
- The release path records why
0.4.0is not a version to install.
jevcore-cli@0.4.0is live and cannot be installed: its name was new, a first
publish of a package name cannot be staged, and it pinnedjevcore@0.4.0
exactly — a version that never left staging.latestmoves to 0.4.1, which
resolves.
v0.3.0
A seven-angle audit of the three packages, with the findings fixed rather than
recorded. Two of them were already published, and they come first because they
are the reason this release exists. The minor-version bump is for the changed
timeout semantics and the widened error vocabulary, not for the fixes.
Fixed
-
jev_rankdid not work at all injevcore-dsh0.2.2. Its tool card was
built by callingsummarize(asRendered(value), …), andsummarizereads
value.answers— a ranking carriesranking.asRenderedis an assertion,
not a conversion, so every call threw, the registry turned the throw into
returned invalid output, and the model received zero candidates. The
expression is replaced by a summary written for a ranking, and the old one is
kept as a test that asserts it throws, so it cannot be "simplified" back.All 410 tests passed while this shipped, because every one of them called
execute, and the projection the registry runs on every top-level call had
no coverage at all.test/tools.test.tsnow calls
output.presentationMetafor all three tools, and reverting the fix turns four
of those tests red. -
npx -y jevcore-mcpdid not start.lib/bin.jshad no shebang, and npm's
cmd-shimreads the first line to decide how to launch a bin: with no#!it
calls the file directly, so the generated Windows shim had nonodeprefix and
the.jswent through its file association — a host that hangs with no output
rather than a process that fails. Both READMEs advertise that command. The
source now carries the shebang, the file carries its executable bit (which
tscinherits, so the artifact is executable too), and the smoke test asserts
the shebang by byte and launches through a real shim instead of
node <entry>— the substitution that let this ship. -
resolveCheckfailed open, and could contradict itself. When the
sufficiency question had no answer it returnedsupportedon the strength of
supportsalone; and a payload whosesufficientwas 0.9895 could still come
backinsufficient. Both were reproduced against the built library before the
fix. -
Every HTTP status collapsed into one error code, so nothing could tell
"retry" from "give up".openrouter.ts's condition was a tautology — the
finalstatus !== undefinedsubsumes the two tests before it — and the
401 || 403branch in the TypeSafe provider was dead code. Rate limiting, a
rejected key, exhausted quota, a malformed request and a transport failure were
indistinguishable. The original status now travels on the error. -
The timeout bounded one attempt, not the call, and the default did not do
what its own comment said it did. The SDK'stimeoutis documented as
per-attempt "without a total retry budget", so 30s across 3 attempts is ~90s of
blocking — the number the comment existed to prevent. A total budget now bounds
the call.requestTimeoutMs: 0, documented as "disables the timeout" and
impossible under the SDK's own validation, is either honoured or refused
explicitly instead of failing every call behind a network-shaped error. -
The three tools silently opted out of the harness's parallel pool.
isConcurrencySafewas never declared, and the host reads undeclared as
exclusive, so N independent judgments ran strictly in series against a pool
that allows ten. -
noulboundaries could not be reached from any tool. The question builder
callednoul(instructions)and dropped the documentedcriteria: {true, false}boundary; the field's declared type was the choice/score map, so it
could not have carried one. Both the plugin and the MCP server now accept
boundary?: NoulCriteria, with the same field name on both surfaces. -
The MCP server reported
version: 0.1.0from a hardcoded string while the
package was at 0.2.2, so every host displayed a version that had never existed. -
The MCP server's default OpenRouter baseURL was missing the
/apithe SDK's
path needs — it fed the egress report rather than the request, so the report
named an endpoint the call never used. -
Three false statements in the READMEs, corrected in all five languages: the
OpenRouter model-id rule (the READMEs demanded atypesafe/prefix, and the id
that rule produces —typesafe/jev-latest— is the one the live route
rejects, while barejev-latestworks), theengines.nodedifference between
the packages, and that default baseURL. One table cell had also been truncated
mid-sentence in the English source for three releases. -
The bundled skill described
confidenceas "Jev's own calibration". It is a
concentration statistic derived from the probabilities — and anoulanswer
has none at all. -
The declared state cap was not a cap. For payloads dense in characters that
need escaping,capJsonTextmeasured the JSON before escaping and sent the
result afterwards: a 500-character cap emitted 692, and a 16,000-character
cap emitted 23,344. The head length is now found by bisecting the serialized
envelope, so the declared limit holds for every input, and the test that allowed
maxChars + 200slack now asserts the limit itself. -
Redaction erased ordinary field names. Sixteen of sixteen non-secret names
were removed because the key rules matched substrings (authorforauth), and
a label likeapi_key=was consumed along with its value, so a log could not
even be read to ask whether it held a credential assignment. Rules are anchored
to whole names now, and a label survives with its value replaced — the JSON
stays parseable. One over-redaction is kept deliberately:totalTokensis still
removed, because it cannot be told apart fromuserTokenby shape.
Added
-
Results account for themselves.
truncated, the size actually sent, and
the redaction counts travel on the result. A state over the cap used to be cut
silently, and the answer came back as though Jev had seen all of it. -
docs/quickstart.md— the core had 69 exports and no runnable example.
Every snippet in it was run against both the published package and a fresh
build, and the outputs matched. -
docs/hosts.md— seven MCP hosts, four mutually incompatible configuration
shapes, the Windows variants, and the failure modes worth knowing: some hosts
filter the environment a server is spawned with, and the DSH plugin row needs
its ownenvbecause the harness scrubsKEY/PASSWORD/SECRET/TOKENfrom
the child environment. Without it a configured key silently yields synthetic
answers. -
mcpNameandserver.json, sojevcore-mcpcan be listed in the official
MCP Registry, where the siblingdsh-cert-mcpalready is. -
Error codes that name what happened, the original HTTP status, and the symbols
earlier release notes promised but never exported (MAX_SCORE_LEVELS,
MAX_CHOICE_OPTIONS,isEmptyEntry,NoulCriteria,EntryType, and the two
request defaults). -
scripts/check-mojibake.mjs— the corrupted-dash class that shipped once
cannot be found by scanning for replacement characters, because the artifact is
a legal ASCII?. This gate also compares source against build output, which
is how a description that only exists inlib/reaches a host at runtime.
Changed
-
The default probability floor now agrees with the self-consistency band.
minProbabilitywas 0.6 while the band's upper bound is 0.7, so a noul at 0.65
was simultaneouslydecidedanduncertain; the comparison was also inclusive,
which left the same disagreement exactly at 0.7. Both floors are now 0.7 and the
noul threshold is strict, so the two readings agree everywhere. This is the
one deliberate change to default behaviour, and it moves the wrong way for
nobody: more answers resolveundecided, none resolveallowthat did not
before. -
jev_checkgainsundecidedfor the case where the evidence is sufficient but
neither side reaches its threshold — previously reported asinsufficient,
which contradicted the same payload's own probability. A missing sufficiency
answer now fails closed instead of reading as support. -
The mojibake gate runs after the build rather than before it, so it inspects the
bytes that will ship instead of reporting the same staleness on every run. -
Tests: 410 → 551.
Known limitation
Releases cannot use npm's trusted publishing from this repository yet. GitHub
signs OIDC tokens for repositories created after 2026-07-15 with an immutable
subject claim, and npm's registry cannot match that form; the exchange is
rejected and the setting cannot be turned off. Reported upstream as
npm/cli#9969 with this repository's
reproduction. Releases go out through the staged path described in
PUBLISHING.md until it is fixed.
v0.2.2
Repository and packaging changes. The decision core and the three entry points
behave exactly as 0.2.1 left them; what changes is the licence, what each package
ships, and how much of it a gate checks.
Changed
-
The licence is Apache-2.0, not MIT.
LICENSEis now the standard Apache
License 2.0 text, carried without a copyright line because the licence puts the
copyright statement inNOTICE, where it now is; all four manifests say
Apache-2.0, as do the five READMEs. A release is the only thing that can make
this visible — published metadata is immutable, so 0.2.1 and every version before
it still reportMITon npm. -
Every package ships five READMEs. The four translations were listed in
files[]from the start while the package directories held none, so npm dropped
them without a word and every published tarball carried the English page alone.
Each package now carries its own set: a package's npm page is its own document,
so the repository's translations could not stand in for it. -
jevcore-dshfollows the plugin conventions of the harness it plugs into.
dsh.manifestVersion: 1;engines.dshand the@deepseek-ai/dsh-toolspeer
range gained the>=0.1.6-0 <0.2.0segment, so one pin covers the current alpha
line as well as the two earlier ones;engines.nodeis the harness's
^22.19.0 || >=24.0.0rather than>=20. -
files[]shipssrcandCHANGELOG.md, matching the sibling plugins, so a
stack trace from a published package points at readable source. -
Releases authenticate with OIDC trusted publishing instead of a stored
token.publish.ymlbecomesrelease.yml— the filename npm's trusted
publisher matches exactly, extension included, and the name 38 of the sibling
plugin repositories use. The workflow holds no credential, takes
id-token: write, and publishes with provenance. Hand-publishing is no longer
a fallback: with 2FA on the account it prints✅ Publishedand exits 0 while
only staging the version for approval, and the staged version then occupies
that version number while being invisible tonpm stage list— which is how
0.2.2 was consumed without ever reaching the registry.
Added
-
scripts/check-readme-sync.mjs, run bycheckand byprepublishOnly. It
treats each five-language README set as a unit: same##sections, same fenced
code blocks compared byte for byte, same links, same configuration keys, same
licence, and the same ordered structural shape — headings, table rows, code
blocks, rules and list items — with prose excluded, because prose is what a
translation is for. The shape is what catches a section dropped and another
duplicated in its place, which a section count alone cannot see. -
scripts/sync-legal-files.mjsalso shipsCHANGELOG.mdto each package, and
.gitattributespins LF for every text file, so the byte comparison above cannot
fail for a line-ending reason. -
Three CI workflows, taken from the sibling plugin repositories:
compat.yml
(pack, install and import all three entry points, then mount the plugin row in a
scratch profile),plugin-doctor.yml(the static plugin gate) and
scorecard.yml(OpenSSF Scorecard).
Fixed
NOTICEnamed a dependency that no longer exists, a script that was deleted,
and the wrong licence. It listed@openrouter/sdkas an optional dependency of
two packages and offeredpackages/core/scripts/check-openrouter-schemas.mjsas
a verification script; both were gone by 0.2.0, and the file still said MIT.
Rewritten against what the manifests and the tree actually contain.