Skip to content

Releases: PerryLink/jevcore

v0.4.1

Choose a tag to compare

@PerryLink PerryLink released this 09 Oct 12:42

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_ask returned 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. JevService stamps
    egress onto every result, renderResult forwards it, and ask.ts returns
    renderResult(...) verbatim — while ask.ts's output schema declared neither
    truncated nor egress. rank.ts and check.ts declared both. So every call
    came back as tool "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 at fc8f016; only the tool that returns the core's
    rendered payload directly was affected, which is why jev_rank and jev_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.ts now asserts each tool's payload
    against its own declared schema.

  • The question map's redaction leaked secrets nested inside a question. The
    fix that stopped credential_exposure being 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 and redactions was 0. 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, because Object.entries(7) is
    []; null produced a TypeError rather than EgressShapeError. 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().lastCall reported: 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 incrementing failures for a call whose
    provider answered correctly. A cache hit also dropped warning, 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
    JevResult field by field, and the rebuilt value lost truncated and egress.

  • The published CLI answered --version with the wrong version.
    jevcore-cli@0.4.0 reported 0.3.1, because the constant was kept in step with
    package.json by 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
    reading JEV_RESULT_EOF closed the heredoc early and every line after it was
    parsed by the runner as a further $GITHUB_OUTPUT command — an output whose
    name came from the document. The delimiters are generated per run. Separately, a
    newline in the command input forged a second ::error:: annotation line;
    newlines are stripped from any value before it reaches a workflow command.

  • scripts/check-workflow-shell.mjs both missed violations and invented them.
    It did not see run: |2-, a plain scalar whose value continues on the next
    line, or run written 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.0 is not a version to install.
    jevcore-cli@0.4.0 is live and cannot be installed: its name was new, a first
    publish of a package name cannot be staged, and it pinned jevcore@0.4.0
    exactly — a version that never left staging. latest moves to 0.4.1, which
    resolves.

v0.3.0

Choose a tag to compare

@PerryLink PerryLink released this 09 Oct 12:42

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_rank did not work at all in jevcore-dsh 0.2.2. Its tool card was
    built by calling summarize(asRendered(value), …), and summarize reads
    value.answers — a ranking carries ranking. asRendered is 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.ts now calls
    output.presentationMeta for all three tools, and reverting the fix turns four
    of those tests red.

  • npx -y jevcore-mcp did not start. lib/bin.js had no shebang, and npm's
    cmd-shim reads the first line to decide how to launch a bin: with no #! it
    calls the file directly, so the generated Windows shim had no node prefix and
    the .js went 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
    tsc inherits, 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.

  • resolveCheck failed open, and could contradict itself. When the
    sufficiency question had no answer it returned supported on the strength of
    supports alone; and a payload whose sufficient was 0.9895 could still come
    back insufficient. 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
    final status !== undefined subsumes the two tests before it — and the
    401 || 403 branch 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's timeout is 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.
    isConcurrencySafe was never declared, and the host reads undeclared as
    exclusive, so N independent judgments ran strictly in series against a pool
    that allows ten.

  • noul boundaries could not be reached from any tool. The question builder
    called noul(instructions) and dropped the documented criteria: {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.0 from 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 /api the 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 a typesafe/ prefix, and the id
    that rule produces — typesafe/jev-latest — is the one the live route
    rejects, while bare jev-latest works), the engines.node difference 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 confidence as "Jev's own calibration". It is a
    concentration statistic derived from the probabilities — and a noul answer
    has none at all.

  • The declared state cap was not a cap. For payloads dense in characters that
    need escaping, capJsonText measured 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 + 200 slack now asserts the limit itself.

  • Redaction erased ordinary field names. Sixteen of sixteen non-secret names
    were removed because the key rules matched substrings (author for auth), and
    a label like api_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: totalTokens is still
    removed, because it cannot be told apart from userToken by 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 own env because the harness scrubs KEY/PASSWORD/SECRET/TOKEN from
    the child environment. Without it a configured key silently yields synthetic
    answers.

  • mcpName and server.json, so jevcore-mcp can be listed in the official
    MCP Registry, where the sibling dsh-cert-mcp already 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 in lib/ reaches a host at runtime.

Changed

  • The default probability floor now agrees with the self-consistency band.
    minProbability was 0.6 while the band's upper bound is 0.7, so a noul at 0.65
    was simultaneously decided and uncertain; 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 resolve undecided, none resolve allow that did not
    before.

  • jev_check gains undecided for the case where the evidence is sufficient but
    neither side reaches its threshold — previously reported as insufficient,
    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

Choose a tag to compare

@PerryLink PerryLink released this 09 Oct 12:42

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. LICENSE is now the standard Apache
    License 2.0 text, carried without a copyright line because the licence puts the
    copyright statement in NOTICE, 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 report MIT on 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-dsh follows the plugin conventions of the harness it plugs into.
    dsh.manifestVersion: 1; engines.dsh and the @deepseek-ai/dsh-tools peer
    range gained the >=0.1.6-0 <0.2.0 segment, so one pin covers the current alpha
    line as well as the two earlier ones; engines.node is the harness's
    ^22.19.0 || >=24.0.0 rather than >=20.

  • files[] ships src and CHANGELOG.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.yml becomes release.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 ✅ Published and exits 0 while
    only staging the version for approval, and the staged version then occupies
    that version number while being invisible to npm stage list — which is how
    0.2.2 was consumed without ever reaching the registry.

Added

  • scripts/check-readme-sync.mjs, run by check and by prepublishOnly. 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.mjs also ships CHANGELOG.md to each package, and
    .gitattributes pins 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

  • NOTICE named a dependency that no longer exists, a script that was deleted,
    and the wrong licence.
    It listed @openrouter/sdk as an optional dependency of
    two packages and offered packages/core/scripts/check-openrouter-schemas.mjs as
    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.