Repository navigation
Releases: Vernum-Projecten/Veredictum
Release list
0.1.6
Changed
- The repository moved to the
Vernum-Projectenorganisation, and the
container image moves with it (#567). The console image publishes as
ghcr.io/vernum-projecten/veredictum; the old
ghcr.io/rubentalstra/veredictumkeeps the tags it already carries and
gains no new ones, so a deployment pinned to it stays on 0.1.5 until it
moves.docker/docker-compose.yml, the hosted overlay and its
env.examplename the new reference, and the image's
org.opencontainers.image.sourcelabel points at the new repository, which
is what links the package to it. The registry's own trust anchor moves too:
a reproduced or console-tier record is issued by a workflow under
Vernum-Projecten/Veredictum, and a submission naming the old repository is
refused. - The copyright holder of Veredictum's own work is Vernum Projecten B.V.
(#556).LICENSEandNOTICEname the company beside the Veredictum
contributors; the Apache-2.0 terms are unchanged, and maintainer credit
stays a person. The FerroEHR party statement the test fixtures carry
names the company as vendor, as FerroEHR's own statement now does. - The hosted instrument's box grew from a Hetzner CPX12 (1 vCPU, 2 GB) to a
CX33 (4 vCPU, 8 GB), and its concurrency cap moved with it: the official
instrument at console.veredictum.eu now drives two runs at once, with the
queue covering everything past the cap as before (#523). The README's
stated conditions and the committed deploy posture describe the new
machine.
Fixed
- The console's body limit reached only the five server-owned routes, so every
#[server]endpoint kept axum's 2 MiB default and a paste past it was cut
off by the transport before the page that judges it ever saw it. Each
endpoint now carries a cap derived from the number its own code enforces, so
every payload an endpoint accepts reaches the code that judges it, and a size
refusal within the transport cap is that endpoint's own sentence (#496). - A finished run's elapsed time on the console's live screen kept growing on
every read, because the snapshot derived it from the wall clock instead of
the run's own completion instant. A terminal run (finished, cancelled,
expired, or failed) now reports the fixed duration it actually took (#464).
Security
rustlsmoves to 0.23.45 for RUSTSEC-2026-0285, TLS 1.3 handshake
messages incorrectly accepted across encryption level boundaries (#562).
The lockfile moves; no source changes.
0.1.5
Added
-
Every declared fact in a results document says where it came from (#461).
Two members inresults.json, both published in
schemas/results.schema.json.tech_profile.sourcereadsdeclaredwhen
the party statement's its-rest entry named the recorded format list, and
defaultedwhen no declaration named this ITS and every format the
instrument speaks was recorded instead.ambiguity_dispositionsnow carries
what the campaign applied: one record peroption_selectregister arm the
ICS declared, in the register's authored order.What it closes: a run that declared no technology profile published five
formats,canonical-xmlamong them, and nothing in the document said whether
that was a claim or a fallback. The recorded list is what the verdict
pipeline selects gating records with, so the two carry different claims. The
dispositions were a hardcoded empty list on a run that drove both arms of
fifteen register-tracked option families, and the empty list is now the
honest record of a campaign that answered none.One constructor assembles the document for both campaign seams, the live run
and the re-judgement of a recording, so a member added to one is a compile
error at the other rather than a silent difference between two records.Absence is unknown, never a value. A document written before a member existed
carries none, andscripts/checks/registry-rederive.shreports the absence
and recomputes anyway. Members that contradict each other are refused:
replay --againstrefuses a record readingtech_profile.source: declared
besideselection_basis: statement_blind, because no re-judgement
reconstructs a campaign that had no declaration to read a profile from. -
A results document names the statement that selected it (#490). New
statement_digestmember inresults.json: the leading 8 bytes of the
SHA-256 over the party statement's bytes as they sit on disk, 16 lowercase
hex characters, the same shapeixit_digestalready published. A reader
holding the declaration a record was driven under recomputes it with
sha256sum statement.json | cut -c1-16.runandreplayboth write it,
andschemas/results.schema.jsonpublishes it.What it closes: a record identified the claim it was selected under by
selection_basisand the declared its-rest format list, so two different
statements declaring the same formats were one value.replay --againstnow
refuses a re-judgement handed a statement the record does not name, and the
refusal prints both digests. The console tier's whole claim is that CI
re-derives the verdicts from the recorded exchanges, and a re-derivation
under somebody else's claim is not a re-derivation of that record.Absence is unknown, never a match. A campaign no statement selected writes no
digest, whichselection_basis: statement_blindalready says out loud, and a
document written before the member existed carries none either. A
re-derivation of such a record is reported and recomputed rather than
refused, soscripts/checks/registry-rederive.shkeeps re-deriving every
record published before this release. -
A red run exports the evidence behind its own rows (#463). New subcommand
veredictum evidence, which carves a named set of a finished run's recorded
exchanges out of itstranscript.jsonand writes them as one bundle. It reads
no statement: sealing a record needs a claim, and reading the exchanges a run
recorded does not, which is exactly the case when a run has gone red. One
command turns the red rows into a triage input:veredictum evidence --transcript run/transcript.json \ --results run/results.json --failing --out run/evidence.json--failingselects everyfailedanderroredcase the results record
names;--only <CASE>(repeatable) and--filter <SUBSTRING>name a set
directly, and the three union. Each exported case carries the outcome row the
run recorded beside its requests and responses, so a reader holds one
document rather than two.An export that would carry nothing is refused, exit
2, with no file
written. A selection matching no recorded case names what was asked for and
what the transcript actually carries; a selection whose every case recorded
nothing names those cases. A selection that half-matched still exports, and
the bundle'swithout_exchangesnames every case it could not carry, so a
partial answer never reads as a complete one. This is what the first live
triage's hand-written extraction got wrong: it produced 130,761 empty objects
and zero scalar values, valid JSON of the right shape and size with nothing
in it, and nothing said so.The
authorizationrequest header's value is withheld by the export itself,
whatever the transcript held, pinned by a test over a run driven with a
credential. Response bodies are the wire's own bytes and can carry real
patient data, so the bundle is operator-controlled output like the transcript
it comes from.The console offers the same document on a red run: the results screen's
Evidence for a triage section downloads it from/export/evidence.json,
and says which of the two reasons applies when it cannot — no row went red, or
the run was driven without recording its wire.schemas/evidence-bundle.schema.jsonis the published shape, drift-guarded
like every other emitted schema.
Changed
-
The command reference and the engine pin are generated (#466). The CLI
surface is what clap declares, and three documents used to restate it by hand
under a guard that compared subcommand names. Now
the command reference is rendered
fromveredictum <command> --helpbyscripts/render/commands-md.sh, the
hand-written notes per command sit beside it inwebsite/book/commands/, and
scripts/checks/cli-surface.shrefuses a committed page the generator does
not reproduce. So a flag's help text reaches the published page in the change
that edits it. The binary's own header listing and the rootCLAUDE.mdtable
are gone; both point at--helpand at that page.The console's
ENGINE_PINis emitted by a new
app/veredictum-console/build.rsout of the manifest's exact crates.io pin,
so the version the chrome displays cannot disagree with the version the
console links, and a release cut moves one line instead of two.
results.example.jsongains the drift gate it never had: it carried a
version two releases old, andscripts/checks/example-results-drift.shnow
holds the committed copy to its generator's output. -
The proforma stays, the answers leave (#465). ISO/IEC 9646-7 splits an ICS
in one place: every cell of the proforma belongs to its specifier except the
support and supported-values columns, which belong to the supplier of the
implementation, and ISO/IEC 17050-1 is titled a supplier's declaration of
conformity. This repository was committing two filled-in declarations, one of
them about EHRbase, a product nobody here speaks for. Both are gone. The
proforma stays exactly where it was:artifacts/vocab/capability_matrix.yaml,
43 rows with a specification citation each.What this changes for anyone driving the instrument.
validatesweeps no
party/directory any more and gains--statement <FILE>, which runs the
static conformance review of ISO/IEC 9646-1 and -7 over one submitted
declaration: a claimed capability with no verdict-bearing case, aSigning
claim whose ixit declares no posture, a served-extension family the wire
surface does not carry. Every one of those checks survives unchanged; only its
input moved from a committed fixture to a supplied document. The summary line
now counts capability rows where it counted party statements, and the
console's instrument page does the same.workload-coverageand the
hollow-battery half ofclaim-completenessare re-grounded on the matrix, so
the catalogue side of both is enforced over all 43 rows whatever anybody
claims — measured by experiment before anything was removed: emptying one
capability's battery is still caught, bycapability-depth, naming the row
and the shortfall.The console stops offering a list of committed declarations to attach. The
claim is the submitter's, so the paste box and the compose-from-tiers button
are the only ways one enters, and the endpoint that read a statement from a
client-supplied path is gone with the tree it read from. The published image
no longer carries/work/partyandVEREDICTUM_PARTYis retired.The
reproducedtier's question is answered inregistry/RULES.mdrather
than left open: without a supplier declaration there is no claim and so no
conformance verdict, so a reproduction either cites a declaration the supplier
published or publishes a survey labelled as an observation. Neither committed
topology cites one, so both dropped theirstatement, and the lane refuses to
bundle a reproduction rather than inventing a claim. The submission criteria
an entry is scored against are unchanged, so the rules version is unchanged.One filled-in declaration survives as a named test fixture,
fixtures/declaration/, for a product that does not exist. The SMART lane's
committed test issuer moved beside it asfixtures/smart-test-issuer/,
contents and warnings untouched.
Fixed
- A case declaring no capability is refused at authoring time (#500). The
two readers of a case'scapabilitieslist disagreed about the empty one:
the runner drove such a case against a server, and the verdict pipeline
deselected it because no claim intersects an empty list. The row burned a
server's time and bore no verdict. An empty list is now illegal: the
publishedcase-coreschema carriesminItems: 1oncapabilities, so
validatereport...
0.1.4
This release builds the hosted instrument and everything that judges what it
produces: console.veredictum.eu on a box this repository provisions, several
people driving the catalogue at once, a submission opened by the instrument's own
App identity, CI that re-derives a submitted record's verdicts before anything is
signed, and a signing key that exists only in a protected environment.
No record has travelled that path yet. Nothing has been driven at
console.veredictum.eu, submitted, re-derived, signed and verified offline as one
sequence. Every part of it is tested on its own and the parts have not been run
in series, so read this as the pipeline arriving rather than as a result. The
first record is what v0.1.5 exists to produce.
Added
- One image, carrying the data it judges against (#420). The published
image now holds the catalogue, the vendored specification oracle and the party
declarations at/work, sodocker compose upgrades a server in an empty
directory and the hosted instrument mounts nothing. It replaces a second image
whose only content was those three directories copied over the release, and
whose data came from whichever branch last built it — so the official
instrument was pairing a released engine with an unreleased catalogue. An
operator who wants their own trees bind-mounts over the baked paths, which
still takes precedence. The image declares
eu.veredictum.image.carries-catalogue, and the hosted deploy refuses an
image that does not, so an older release cannot reach an instance that would
find/workempty. - The registry signing key exists, and its public half is committed
(#403).registry/keys/registry-signing.pub.ascis what a reader checks a
published console record against, with the instrument through
veredictum verify-recordor without it throughgpg --verify. The primary
key certifies and an ed25519 subkey signs, so a verified record names the
subkey fingerprint. The secret half is in theregistry-signingenvironment
and nowhere else: it was generated in a throwaway keyring, never entered a
personal one, and that environment carries required reviewers plus a branch
policy namingmainalone. Before it was used, the engine's own signing and
verification code was run over this exact key material, because the committed
test keypair is RSA and an EdDSA key would otherwise have reached production
on a path nothing here had exercised. - A finished run submits itself to the registry (#391). The console's
/run/submitscreen states what the run knows — the endpoint it drove, when
it started, the catalogue revision, the engine version — and collects the
disclosure the submission rules make mandatory, the conflict-of-interest
sentence included. An empty mandatory value is refused by name before
anything is opened. The instrument then writes the entry and the five record
files through its own GitHub App identity: blob, then tree, then commit, then
ref, so the commit GitHub signs is the one that lands, and a commit reported
unverified pushes no branch at all. The submission arrives on
console-run/<run-id>and carries no provenance block, because the
re-derivation lane writes that after it has recomputed the judgement. The App
identity isVEREDICTUM_GITHUB_APP_ID,VEREDICTUM_GITHUB_APP_KEY,
VEREDICTUM_GITHUB_INSTALLATION_IDandVEREDICTUM_REGISTRY_REPO; any of
them unset is a first-class state that explains what to configure and offers
no button. No credential the run was driven under reaches the branch, pinned
by a gate over a run driven with one. - A recorded run can be re-judged from its own transcript:
veredictum replay(#392). The transport is now the only seam between the driver and
the wire, so a replay answers every composed request out of the recording and
reaches its outcomes through the same request composition, response
classification and assertion evaluators the live run used. With--against
it holds a submittedresults.jsonto what the recorded exchanges support and
names every row that differs. A case whose recording runs out, or whose replay
composes a request the recording does not carry, records a transport failure:
a verdict is never reproduced over evidence nobody has. - The console tier's lane (#392).
scripts/checks/registry-rederive.sh
re-derives aconsolesubmission's outcomes from its transcript and its
verdicts from its outcomes;.github/workflows/registry-console.ymlruns
that gate, then seals the record from a protected environment and writes the
provenance block the instrument is not allowed to write for itself. A record
altered after the run fails the gate, which is pinned by a test. - A conformance entry may carry the
statementit was judged against, so a
claim that lives nowhere else is still one anybody can recompute against. A
consoleentry must carry it, with the transcript and the ixit. - Two people can drive the hosted console at once (#389). The one job slot
is a map keyed by the run id, so several runs execute side by side and every
seam addresses a named run instead of asking whether one exists. Two drive at
once, a start past that ceiling is accepted and QUEUED with its place and an
estimated wait, one address gets one run in flight and a second start is
answered with the run it already has, a run past thirty minutes of wall clock
is ended by the console with its partial record discarded, and finished runs
are evicted from memory oldest-first while their artifacts stay where the run
wrote them. The connection draft is per submitter too, so two visitors
composing a connection no longer overwrite each other. The caps are named in
one place and are starting values to re-measure on the chosen host. VEREDICTUM_CLIENT_IP_HEADER(#389). Behind a proxy the peer address is
the proxy, so the console reads a forwarded client address only from the
header the operator names; unset, it uses the socket peer and reads no
forwarded header at all.VEREDICTUM_POSTURE, and the targets a public instance refuses (#390).
A hosted console drives whatever endpoint a visitor names, so it can be
pointed at addresses only it can reach. Set the variable tohostedand the
console refuses loopback, RFC 1918 private, link-local, unique-local,
unspecified, multicast, RFC 6598 shared and broadcast targets in both address
families, the IPv4-mapped IPv6 forms included, before any socket opens; the
name is resolved first and every address it answers with is checked, because
a hostname under the visitor's control resolving to a private address is the
whole attack. The
refusal names the address, the family and the RFC that defines it, and it
reaches the visitor as a notification. Both seams that reach a
visitor-named endpoint are covered: the reachability probe, and the run
start before the engine is spawned. Unset orlocalrefuses nothing, so an
operator keeps driving a CDR atlocalhost; any other value refuses to
start, because a public instance that read a typo aslocalwould drive
whatever a visitor named.- Per-address rate limits on the probe and the run start (#390). They read
the same submitter identity the concurrency caps use, and a refusal states
when the visitor may try again. - The registry publishes a third kind of entry:
console(#393). A run
performed at console.veredictum.eu, the official hosted instrument, against
an endpoint the submitter named. Its verdicts are re-derived here from the
transcript the submission carries, and the record is signed only after they
match, with a key held in a protected CI environment that the instrument
cannot reach. Every field of aconsoleprovenance block is written by that
lane rather than by the instrument, so a performer cannot state its own
provenance.registry/RULES.mdstates what the kind attests and what it
cannot — it cannot attest the environment, because the submitter chose the
endpoint — and the conformance board labels and orders the new rows. - The hosted posture travels inside the image (#423). The published image
now carriesdeploy/hosted/docker-compose.ymlanddeploy/hosted/Caddyfile
at/app/posture/, and the one command the hosted box's deploy key may run
extracts them from the image it just pulled and installs them. Before this,
a committed change to either file reached the box only when somebody copied it
there by hand. A posture change now arrives the way a catalogue change does,
at a release, with the provenance of the artifact it travelled in, and the
deploy key gains nothing: it still runs one script and writes no arbitrary
file. An extraction that comes out empty stops the deploy, a candidate compose
filedocker compose configrefuses to parse is never installed, each
replaced file is kept as.prev, and a changed Caddyfile restarts the caddy
service — a bind-mounted file's contents changing does not recreate the
container that mounts it, so without that restart a proxy change served
nothing.
Changed
- Every reader-facing page describes the hosted instrument as what it is
(#395). console.veredictum.eu is the official conformance instrument: a run
performed there is an official run, and the record it produces is re-derived
and signed here. The README, the landing page and the book say that, along
with the one thing such a record cannot attest — the submitter chose the
endpoint. Running the instrument yourself publishes your own claim, which the
same pages now describe as a different question rather than a lesser one.
scripts/checks/hosted-instrument-language.shrefuses the words "demo" and
"sandbox" on every surface a reader meets, as whole words, sodemonstration
and the CSP directive name are untouched. - **The hosted instrument runs on a box this re...
0.1.3
Changed
- The console speaks about the console (#382). Every screen that printed a
command line or pointed at a terminal now states what it did itself: the
verify page's "check it without this page" box, the benchmark pages' three
command boxes, the empty-state hint that told a reader to runbench, and
the verdicts line about "the bodies the command line writes". Documents that
leave the console keep their verification instructions, because their reader
is holding a record rather than looking at a screen.
Fixed
- Every server function answered 404 in the published image (#381). A
server-function URL isxxh64over the crate's absolute manifest directory
unlessSERVER_FN_OVERRIDE_KEYsays otherwise, and the release pipeline
builds the two halves in different places: the server binary inside a job
container, the WASM bundle on the host runner. The 0.1.2 image therefore
registered one set of paths and requested another, so every call the
hydrated console made fell through to the not-found page. A workspace
.cargo/config.tomlfixes the key for every build, and the image smoke now
diffs the shipped binary's path set against the shipped bundle's and
refuses any difference.
0.1.2
Added
- The hosted console is documented where readers look (#374): the README,
the landing page and the book's entry pages name
https://console.veredictum.eu as the no-install path, with its ephemeral
lifecycle stated plainly. - The two 409 delete-conflict
ETagoutcomes assert theW/weakness
indicator behindapplies: { its_rest: ">=1.1.0" }, the same sentence and
gate as the thirteen 412 outcomes #360 ratcheted (#361). - The error-response surface the released ITS-REST actually pins gains its
coverage (#340). A newpresent-with-bodyheader matcher carries the
Resources.md§JSON Format MUST in its own shape — the header is asserted
when the response carries a content body and carries no criterion when it
does not — and the demographic create binding's 400 outcome now declares it
forContent-Type, with the media type itself grounded on the OAS
(specifications/responses/400.yaml,content: application/json) because
the docs text names no media type for an error body. Register AMB-217 is
untouched: the error body's members stay unpinned. The new
create_party-error_body_content_typecase and the
error-body-content-typewire-surface element name the behaviour, so a red
row localizes to it. - One header may declare several expectations. The
outcomes.*.headers
value now accepts a sequence beside the single form, so two rules of
different strength or different dating on the same header each keep their own
ground. Every412outcome that expects the latestversion_uidinETag
now also asserts theW/weakness indicator behind
applies: { its_rest: ">=1.1.0" }, which ratchets that MUST from one
binding to thirteen. - The archetype-root invariants reach every top-level class the release
makes a root (#339). Two refusal cases with their invalid corpus twins:
an EHR_STATUS whose rootarchetype_node_idcontradicts its
archetype_details.archetype_id(RMEHR_STATUSis unconditionally an
archetype root), and a directory FOLDER whosearchetype_node_idis empty
(Archetype_node_id_validbinds every LOCATABLE). Template-level
validation of EHR_STATUS and FOLDER commits has no released wire binding —
the only released 422/template sentence reaches COMPOSITION and the
DEVELOPMENT demographic surface — so that boundary is register entry
AMB-230, reported upstream (#355), never an invented expectation. - The hosted console (#348). The released container image serves the
public reading surface atconsole.veredictum.euon Vercel: the catalogue,
the party statements and the specification oracle, baked into the image
from the checkout. The posture is view-only by construction, because the
image ships no engine binary. Deploys ride the project's Deploy Hook only —
a real release pings it after the image tags apply, a posture push and a
manual dispatch redeploy the same image — and the verification polls the
servedengine X.Y.Zfooter, never a bare 200. - The dump/load authorization refusals drive without a declared
dump_location: the refusal is a role or authentication decision taken
before any path is consulted, so a literal placeholder location suffices
and the boundary is testable on every party that declares the admin split
(#286). - Signing-on posture profiles (#335). Every bench pack defines
minimal-signed-digestandminimal-signed-pgpbesideminimal, so a CDR
that signs versions out of the box benches without switching a shipped
feature off; the canaries keep refusing any declaration the deployment
contradicts. Pack versions move with the definition (community-vitals
andaql-mixto 1.1.0,smoketo 1.2.0) and the generated pack manifest
and methodology page follow. benchwarns once when a credential rides a plain-httpbase URL that is
not loopback; the run proceeds, because the operator names the transport
and a local quickstart is legitimatelyhttp://localhost(#296).
Fixed
- The image carries the engine (#375). Every published image so far held
only/usr/local/bin/veredictum-console, and the console spawns the pinned
veredictumbinary to drive a run — so the compose quickstart's console
served its screens and failed every run it started. Both build paths now
ship the engine beside the console at/usr/local/bin/veredictum, the
runtime names it inVEREDICTUM_ENGINE, and the release smoke asks the
pulled image forveredictum --versionand refuses anything but the
released version. Proving that on the from-source path exposed a second
break in the same build:.dockerignoreexcludedassets/, which the
console compiles against twice over — itspublic/entries are symlinks
intoassets/brand/, and its export module reads the seal-card master
throughinclude_str!— so--target runtime-from-sourcehad been failing
to compile at all.assets/brand/is re-included. The hosted console at
console.veredictum.eugains the engine with it (owner ruling): a visitor
can drive a real run against a CDR they control, writing into a/work/out
the overlay creates for the console's own uid, and that output is gone at
the next deploy. - The published image hydrates (#369). The per-architecture ssr binary
was compiled withoutLEPTOS_OUTPUT_NAMEin the compiler's environment, so
leptos'soption_env!probe baked a_bgwasm URL into it while the site
bundle ships the cargo-leptos-namedveredictum-console.wasm— every
published image to date served its pages SSR-only and 404'd its own client
bundle on each load. The compile step now carries the name, and the image
smoke fetches every/pkg/asset the served page references, so the class
cannot ship green again. - One judgement per sealed record (#243). Preparing the console's export
ran the full judgement five times over the same run — once per fact the
summary, the seal card, the badge and the HTML report each asked for — and
reading the export section ran it twice more. One judgement of a
1,147-outcome campaign costs about 2.7 seconds on an unloaded developer
machine and far more on a busy one, so the repeats were most of the wait
between the engine's manifest and the rendered files. The judgement now runs
once and feeds all of them. What the bundle contains is unchanged: the same
manifest, the same signature, the same three presentation files. - The book's console screenshots stop changing on every capture pass
(#243). A capture pass now serves the console in capture mode, where the
run clock, the record digest and the signing time render as fixed stand-ins,
so six images no longer churn with no interface change behind them. The mode
changes what the screen displays and nothing that is written, sealed or
signed, and it is off in every ordinary run. - An XML response body is refused as unjudgeable instead of read as an empty
document (#285). The driver collapsed every body it could not parse as JSON
into a plain string, so an XML-negotiated read reached thefield,
equivalent,instance_of,result_setandsignaturefamilies as a value
with no members and each one reported the asserted fact absent — a failed row
charged to a server that answered exactly as it was asked to. Canonical XML
and canonical JSON are separate bound document forms (ITS-REST
specifications/docs/overview/Resources.md§Data representation) and this
runner parses the JSON binding only, so those families now take the
inconclusive channel with the served media type named, and theversion
family refuses the same way when itsORIGINAL_VERSIONenvelope read comes
back unparsed. Nothing narrows on the JSON path,xml_rootandreturns
keep grading the served text, and auid_patternjudged off the resolved
identity still gates. - A
lifecycle_stateassert reads anIMPORTED_VERSIONthrough the version
it wraps (#322). The judge read the property off the top level of the
served envelope, which an imported version cannot carry: released ITS-JSON
components/RM/Release-1.1.0/Common/IMPORTED_VERSION.jsonrequires
contribution,commit_auditanditemunder
additionalProperties: false, and RM common
UML/classes/imported_version.adoc§Functions effectslifecycle_state ()
from_item.lifecycle_state_. A conformant server was charged a failed row
for answering exactly as the release binds it. The row now resolves the fact
throughitem, a wrong term still fails, andchange_typekeeps reading the
wrapper's owncommit_audit, which is a real property of the class. - The measured path stops substituting silently for a defect (#293). A
workload stage naming an operation the vocabulary does not carry is now a
named finding at the schedulable filter, so a typo'dopfails loud
instead of dropping its journey from the mix and quietly shrinking the
offered load. The corpus stride forms its product inu128, where the
widest operand pair needs 96 bits, so the overflow fallback that stopped
striding past ~6.9e9 arrivals is gone rather than proven unreachable. The
capture store's cleanup path recovers a poisoned shard the way its read
path already did, so a worker that panics mid-window cannot leak every
instance hashing to that shard. - The
latest-version-uidETag comparison names the object it is about
(#235). The driver kept onelast_version_uidslot for the whole row, so
a row that wrote object A and then provoked an error on object B graded B's
entity tag against A's uid. The slots are now per versioned object, keyed by
theobject_idof the committedOBJECT_VERSION_ID(BASEbase_types
master05 §Syntaxes), and the object a step addresses is read from the path
parameters the request was built from, falling back to theIf-Matchthe
request sent for thedirectoryandehr_statusroutes that carry no uid...
0.1.1
Added
- A public results registry with signed submissions and two labelled tiers
(#158). Published results now live in one append-only tree, conformance and
benchmark alike, and every entry carries who submitted it, what they
disclosed, the artifacts it stands on by digest, and how far anyone here
verified it.schemas/registry-entry.schema.jsonis the versioned entry format, emitted
from the code like every other published schema. The disclosure is
mandatory: the submitter and their relationship to the system, the
deployment with its image digests, the instrument version, the machine, what
was switched on behind the result, and the interest the submitter holds in
the outcome.schemas/registry-topology.schema.jsonis the second new
schema, for the deployments the reproduction lane may compose.- The tier is the discriminant of the entry's provenance block, so it cannot
be claimed without the evidence its variant requires.reproducednames a
workflow of this repository, its run, and the attestation predicate;
self-reportednames the scheme, the signature, the artifact it covers and
the command that checks it. No signing key exists in this repository or in
its Actions, and the gate refuses one committed under the registry. .github/workflows/registry-reproduce.ymlis the tier-1 lane: it composes a
topology declared underregistry/topologies/, drives the catalogue, and
attests the run, the judgement and the images that actually answered from
the workflow's own OIDC identity through Sigstore. It composes nothing a
submitter wrote, and it does not gate a merge.scripts/checks/registry-submission.shis the submission gate, wired into
CI as its own tier: append-only over the entries and their evidence, the
schema and the rules, id uniqueness, every artifact digest recomputed, the
supersede edges resolved, the pairing with the benchmark board's records,
and both boards held to what is committed.- Two rendered boards, kept separate on purpose.
conformance-board.htmlis
new and reads its numbers out of each entry's ownverdicts.json;
benchmarks.htmlnow takes its tier badge from the registry instead of
printing one constant. Both carry the standing boundary: an entry is a
report, never a certificate. registry/RULES.mdis the published submission contract, covering the two
tiers, the mandatory disclosure, append-only with supersede-by-reference,
the dispute path, and the standing authorization a hosted-endpoint
reproduction needs.
- The Robot-battery coverage gaps close on released ground (#220). Eleven
cases and one binding variant, each derived from the released text rather
than from the foreign suite's expectation.- AQL
LIKEgains its escape row: master03 §LIKE states "To match a literal
?or*, the respective character in a pattern must be escaped by using
the backslash\character", so an escaped wildcard selects only the
literal character while the unescaped twin still matches the whole set. ORDER BYand the aggregates gain their non-numeric rows: a DV_DATE_TIME
and a DV_TEXT path sort ascending by default, descending onDESC, and
resolve a tie on the leftmost expression with the next one;MIN/MAX
return the edges of a non-numeric ordered set in the input's own type and
COUNTreturns an Integer over the same argument.- A
result_setassertion may declarecells: instant, and the two
non-numeric query cases above declare it on their date/time rows. ITS-REST
docs/overview/Resources.md§Datetime format assigns the query path only
a SHOULD ("Retrieval or querying those resources SHOULD return date,
datetime, or time values in the (original) format provided by underlying
backend engine"), and BASEiso8601_timezone.adoc§Description makesZ
"a literal meaning UTC …, i.e. timezone+0000", so a served+00:00is
the same fact as a committedZ. Under the mode the row gates on the
instant and the run prints the tolerated respelling as anobserved:line
beside the tally. The default is unchanged exact-lexeme comparison, which
is what tests the unconditional write-path sentence one line above the
SHOULD;match: countrefuses the modifier, because it compares no cell. SUMandAVGdeclare Integer or Real input and assign nothing outside
it, so register entry AMB-227 (option_select, upstream #229) records the
silence and twin cases pin the two branches a party may declare,
aql-nonnumeric-aggregate-refusedandaql-nonnumeric-aggregate-executed.
No case asserts a value the released text leaves unassigned.- The
is_modifiablerefusal reaches the update and delete routes. The
composition and directory PUT and DELETE are refused on a deactivated EHR
under register AMB-82, each row verifying through a read that the refused
write committed nothing, and the positive twin proves the RM carve-out:
the EHR_STATUS of a deactivated EHR is still writable, which is the only
route by which an EHR is reactivated. - RESULT_SET metadata is judged exactly as far as the released ground
reaches. The docs text calls every member "optional (implementation
dependent)" and the released schema permits additional members, so nothing
is required; what a service does serve is held to its declared shape by a
newabsent_or_matchesfield predicate, which passes on an absent member
and judges a present one. - The catalogue carries 1130 cases and 249 bindings; the
AqlBasic,
CompositionOps,DirectoryOpsandEhrStatusdepth floors move to 52,
63, 95 and 52.
- AQL
- The content-validation chapter reaches inside the data structure (#220).
Nine cases close the constraint axes the chapter never carried. A dedicated
CONT-DV_TEXT-validate_patternruns a real regular expression through
C_STRING.patternon a DV_TEXT, the sibling the DV_URI and DV_EHR_URI cases
already had. Four cardinality cases constrain the member container of each RM
container class —ITEM_TREE.items,ITEM_LIST.items,ITEM_TABLE.rowsand
CLUSTER.items— over all six intervals and member counts 0, 1, 3 and 6;
CLUSTER.itemsis 1..1 in the RM, so a zero-member CLUSTER is refused even
under an unbounded cardinality, which is the one place the four classes
differ.CONT-ELEMENT-value_null_flavour_existenceputs
C_ATTRIBUTE.existenceon both ELEMENT attributes and pins the invariant
that ties them: exactly one ofvalueandnull_flavouris present, so
committing both or neither is refused. ThreeCONT-ITEM-type_*cases narrow
an ITEM_TREE member slot throughC_OBJECT.rm_type_nameacross the abstract
ITEM class and its CLUSTER and ELEMENT subtypes. The catalogue carries 1128
cases, and theArchetypeValidationdepth floor moves to 134. - A client body
uidnever survives a content-object commit (#202).
Register entry AMB-226 (fixed_handling, upstream #221) records the silence
the released text leaves oncomposition_create,directory_createand
ehr_status_update: none of the three says what a service does with a
client-supplied bodyuid, and none declares a conflict response to refuse
with, where the CONTRIBUTION create states the rule and the COMPOSITION
update states a match rule against the URL identifier. The commit succeeds
and the served identity is the server-mintedOBJECT_VERSION_ID. Three
cases pin it —I_EHR_COMPOSITION.create_composition-client_supplied_uid,
I_EHR_STATUS.clear_ehr_queryable-client_supplied_uidand
I_EHR_DIRECTORY.create_directory-client_supplied_uid— each asserting the
commit is accepted, that the version identity the response names is not the
client's value, and that the client's own identifier addresses nothing
afterwards. - The web console reads bench records (#166). A
/benchmarkssurface
lists everybench-result*.jsonunder the mounted output directory, and
takes uploaded ones through a plain HTML form that needs no JavaScript. An
uploaded batch is transient and swept on a timer, because the console stores
nothing of its own. A record opens in full: the pack and the machine that
offered the load, the posture block with every item labelledverifiedor
declared-only, whether the record is submittable and which requirement it
misses, the cross-repetition percentiles in microseconds with the millisecond
reading beside them, the failed-arrival reading of every repetition and phase
on the target and on each baseline, the same-machine baselines with their
pinned images and recipe, and the relative index the record derived. Two or
more records align side by side, and the pack, host, posture, scale and
submittability mismatches are stated above the numbers rather than left for a
reader to notice. Every figure carries the discipline that produced it, and
the benchmark-versus-conformance boundary statement renders verbatim from the
record on every one of those views. The per-operationHdrHistogramV2
encodings are tabulated rather than drawn: decoding one is the engine's own
histogram reader, which the console reaches once its engine pin carries the
bench module (#179). is_modifiablejudges the whole CONTRIBUTION, in any member order
(#201). No released text says whenEHR_STATUS.is_modifiableis evaluated
relative to a commit, so register entry AMB-225 records the atomic-set
reading and the schedule gates on it: a content member is refused exactly
when the EHR is deactivated and the change set carries noEHR_STATUS
member settingis_modifiable = true, whatever position the members hold.
Two new cases close the open corners — a mixed set whose status member keeps
is_modifiable = falseis refused whole, and anEHR_STATUS-only set is
...
0.1.0
The first stable release: the milestone closed at zero open issues, and
the pre-release alpha line ends here.
Added
- Scope builds the claim from CNF tiers (#100). The run wizard's Scope
screen gains a row of tier checkboxes — CORE, STANDARD, OPTIONS, SEC-BASIC —
each carrying the capabilities the capability matrix puts in that tier and
the number of catalogue cases those capabilities gate. Both counts come from
the published library's owntier_memberswalk, the one every profile
verdict is computed from, so the row cannot drift from the answer the
judgement gives. Composing writes an ad-hoc statement into the same paste box
the vendor path uses: the product identity from the Connect step, the checked
tiers as claimed profiles, their required capabilities as the claimed
capabilities, the schedule release read from the committed statements under
the mounted party tree, and the spec-component versions derived from the
catalogue's ownappliesfloors. The operator reads the document before
saving it, and it is then validated, stored and written into the run's output
directory exactly like a pasted one, so a verdict stays a pure function of a
statement. Option branches stay undeclared, because only the party running
the server knows which branch it realizes. - The console exports a signed record, and anyone can check one (#68). The
verdicts screen gains one step that hands the finished run to the pinned
instrument's ownverdicts --sign-key: the rendered documents, a digest
manifest over them, and a detached OpenPGP signature over that manifest. The
console seals nothing itself. Beside the sealed set it renders three files a
party publishes, each carrying the record digest prefix that ties it to the
signed bytes — the brand's seal card with its three slots filled from the
record, a compact badge SVG with copy-paste markdown and HTML snippets, and
a self-contained HTML report of the results and verdicts surfaces whose
footer carries the full digest, the signer fingerprint and the signing time.
All three are pure functions of the record, so the same bundle reproduces
the same bytes. The whole bundle downloads as one archive. /verifyis a public record check (#68). No run, no server and no
account: upload a bundle and the published library recomputes every digest
its manifest names and checks the detached signature. A tampered file names
itself. The upload is a plain HTML form posting to a server route, so it
works with no JavaScript at all; uploaded bundles are transient and swept on
a short timer. The honesty box renders on every outcome — a valid signature
proves integrity and origin since signing, not the run's conditions, not the
system under test's identity claims, not the catalogue's coverage — and the
page prints theveredictum verify-recordequivalent beside it, so nobody
has to trust the console to check the console.- The export surfaces carry the openEHR trademark acknowledgment and the
independence disclaimer visibly (#94). The seal card renders it in its
caption area and the report in its footer, because those are what a party
publishes. run --record-exchangespersists the wire (#96). The flag writes
transcript.jsonbesideresults.json: per case, in send order, the
request line, request headers and request body, and the response status,
headers and body. It is off by default, and persistence is a serialization
of exchanges the driver already holds, so a recorded run sends nothing extra
and reaches the same verdicts as an unrecorded one. The artifact records a
SUT's response bodies verbatim, so it can carry real patient data: it is
operator-controlled output, never a log, and theauthorizationrequest
header's value is withheld. With--sign-keythe sealed record manifest
covers the transcript.run-transcript.schema.jsonjoins the published schema set. The run
transcript is its own artifact family, separate from the verification pack's
replaytranscript.schema.json, which requires an adjudicated verdict per
entry and carries no request side at all.- The console records and reads the wire. Scope gains a "Record the wire
exchanges" checkbox, off by default with the clinical-data caution beside
it, and the results drawer renders each recorded exchange as request and
response panes. A run driven without the flag says so where the wire would
be.
Changed
- A measured population now varies leaf by leaf, so earlier measured records
are not comparable with later ones (#137). The performance pack used to
stamp each composition with only the event-context times and the composer
name, so every composition of a population carried identical clinical values.
A server storing that population can share structure, index entries and cache
pages that a real population would never let it share, which flattered every
number measured over it — records produced before this change are flattered
in exactly that way, and a number from one of them must not be compared with
a number produced after it. The pack now reads the leaf constraints the
operational template itself declares and redraws every numeric leaf inside
its own permitted range:DV_QUANTITYmagnitudes against the
C_DV_QUANTITYinterval declared for the leaf's units, andDV_COUNT
magnitudes against theC_INTEGERrange declared for aDV_COUNT
magnitude. A leaf whose permitted range the template does not declare, and
every coded, textual and date-time leaf, keeps its committed value, so no
arrival can send an instance the template refuses. The draw is seeded from
the template key and the arrival index, so the same run reproduces the same
population byte for byte. perf_run::pack::PackTemplatecarries aconstraintsfield. The library
type gains the leaf ranges read out of its operational template, which is a
breaking change for anything constructing the struct literally.- The console answers an unknown address with a real page (#84). A path
outside the route tree used to render the bare string "Page not found."
with no chrome, no title and no way back. It now renders inside the
console's own sidebar and toast chrome, sets its own title, names the path
that missed, and offers the instrument and the catalogue as routes out. The
HTTP status is unchanged at 404. - The console ships a full icon set (#84).
favicon.ico,
apple-touch-icon.png,icon-192.png,icon-512.pngand a web manifest,
every one of them rendered from the two brand SVG masters by
scripts/render/brand-icons.shso the mark cannot fork. The image serves
them from the samepublic/mount as the seal. - Every run-wizard mutation reports both outcomes as a notification.
Saving the scope, previewing the selection, starting a run and cancelling
one each raise a toast on success and on failure, with the failure copy
naming the object, the instrument's own diagnostic verbatim, and the next
action. The detailed inline panes stay beside them, which is where a
schema finding or a per-chapter breakdown is read line by line. - The console reads two new environment variables.
VEREDICTUM_SIGN_KEY
names the armored OpenPGP secret key the export seals with, and
VEREDICTUM_VERIFY_KEYnames the public half. Both are optional and both
unset is a first-class state the surfaces explain rather than an error. The
export asks for the public key too because it verifies its own bundle before
stating who signed it and when: it never prints a signing time it has not
checked. A passphrase reaches the spawned instrument through
VEREDICTUM_SIGN_PASSPHRASEand its child environment only — never a
signal, a file, a command line, or a log line.
Fixed
- Issue numbers inherited from FerroEHR now say so (#123). Three
ambiguity-register.schema.jsonandwire-surface.schema.jsondescriptions
carried bare#2545,#2546and#271, which resolve to unrelated issues
on this repository's tracker; they readFerroEHR#NNNNnow, as do the
runner comments and the generated wire-surface coverage report that carried
the same ids. Description text only: no schema keyword, type or constraint
moved. validate --write-reportwrites somewhere that exists (#91). The
coverage report's path was climbed out of the spec tree
(<specs>/../../conformance/coverage-report.md), which matched the old
mono-repo layout and, at this one, resolved to a repository-root
conformance/directory that has never existed. The report is now derived
from the artifact root it describes and lands at
<ROOT>/coverage-report.md, so it follows the catalogue rather than
wherever the specs happen to be mounted.coverage_report_pathtakes the
artifact root and returns a plainPathBuf, since the derivation can no
longer fail.
0.1.0-alpha.6
Fixed
- The release pipeline locates the dependency SBOM instead of assuming its
depth (#109 aftermath). cargo-cyclonedx writes the instrument's SBOM
beside its crate manifest, except when the workspace member's version
matches a published crates.io version, when it writes to the workspace
root — both observed first-hand. The binaries lane searched a fixed depth,
found nothing at the alpha.5 cut, and both binary legs failed; it now
searches both locations and excludes the console crate's SBOMs by path.
0.1.0-alpha.4
Added
- A committed example results document,
examples/results.example.json,
generated by the crate's own machinery (cargo run --example make_example_results, deterministic): schema-valid, invariant-checked,
with a real embedded HDR V2 histogram and a verdict computed against the
catalogue's POC case. It doubles as reader documentation for the results
schema and as a real seed for theparty_documentandhdr_v2fuzz
targets, which previously started from mutations. - A deliberate library API,
veredictum::pipeline, so the engine is
consumable by something other than the command line. It carries one seam per
whole operation —cataloguevalidates an artifact tree,conformance
drives it against a running system under test,judgementcomputes the
verdicts and renders the submission set,assetsrenders the published
visuals and the schema set, andmeasuredruns the class window, the stress
ladder and the AQL probe. Every seam returns typed values: a validation
carries its findings and the tree it loaded, a run carries the results
record and its outcome tally, a judgement carries the verdict report and its
documents as named bodies, and the measured window reports its progress as
typed events. Nothing returns console text, so a consumer renders its own
views over the same facts the command line prints. Theveredictumbinary
is now a clap front end over exactly those seams; its behaviour, its output
and its exit codes are unchanged. - A documentation website at https://veredictum.eu, built from
website/and
deployed to GitHub Pages by a newDocsworkflow. The root serves a
hand-written landing page in the project's own brand palette, and/docs/
serves an mdBook with five chapters: an introduction, installation, running
the instrument, a command reference covering every subcommand with its real
flags, the conformance method (the attribution law, positive and negative
testing, the ambiguity-register lifecycle), and catalogue authoring. The site
loads nothing from an external host, renders in both light and dark, and takes
its palette from the brand tokens.scripts/site/build.shassembles the same
tree locally that the workflow deploys, theCNAMEfor the custom domain
included. A pull request touching the site builds, lints and link-checks it
without deploying. - The vendored CKM ADL 1.4 archetype pack is exercised in this repository, by
tests/it/corpus_packs.rson everycargo nextest run. All 944 ADL 1.4
exports are decoded as UTF-8 and required to open with anarchetype (…)
header declaringadl_version=1.4and to declare the archetype id their file
name carries; all 944 AM 1.4 XML twins are read to end of input and required
to root atarchetypeinhttp://schemas.openehr.org/v1with that same
identity; both counts are pinned against the pack's own inventory record. The
pack had no exerciser here — its only one was an ADL-engine parse gate in the
repository this instrument was split out of, and this repository ships no ADL
parser. The pack stays as reserve material for wire batteries the catalogue
has not authored yet, and the exercise is at the byte level, which is what the
instrument can perform first-hand. - The ADL 2 pair pack and the CKM Operational Template breadth pack gain the
same byte-level exercisers, so every vendored corpus tree in this repository
now has one. The pair pack's 654 files are all read and refused when empty,
its 322 ADL 2 sources are checked foradl_version=2.0.6and its 330 ADL 1.4
twins foradl_version=1.4, each against the archetype id written inside it,
and the 321 archetypes upstream published in both dialects are proven to pair
with a twin in the same directory. The files that do not pair are pinned as
what they are: one ADL 2 template, which the archetypes-only 1.4 half has
nothing to hold, and nine ADL 1.4 archetypes this snapshot never converted.
The template pack's 305 exports are each parsed to end of input and checked
to root attemplateinhttp://schemas.openehr.org/v1carrying a template
id, and its file list is compared against the record's own vendored table
rather than against its count alone. - A fuzzing lane over the readers that parse text or bytes the instrument did
not write, in its own nightlyfuzz/workspace: six libFuzzer targets
covering the${…}reference and identifier grammars, the decision-table
literal grammar, the citation reader, a case core end to end through YAML and
the published schema into the typed model, the IXIT, statement and results
documents a party publishes, and the HDR histogram V2 decode path a measured
verdict is re-derived from. Seeds come from the catalogue and the party
declarations already committed here; recorded findings live in
fuzz/regressions/and are re-checked by every run. The harnesses compile on
the pull-request path as a gating CI job, and a weekly campaign fuzzes each
target with its corpus kept between runs.fuzz/README.mdcarries the threat
model and the commands,.claude/rules/fuzzing.mdthe discipline and the
crash-to-regression-test procedure. veredictum::load::yaml_str_to_valueparses artifact YAML from a string under
the same budget and duplicate-key refusal the file reader uses, and
veredictum::validateexposescitation_clauses,expand_bracesand
section_candidates, so a consumer can read a citation the way the validator
does.- A published VEX record under
security/vex/, in OpenVEX format: the
distroless base's adjudicated OpenSSL finding as a hand-authored statement
beside its.trivyignore.yamltwin, and the Rust advisoriesdeny.toml
accepts as a GENERATED document whose id set cannot drift from the gate —
scripts/security/vex-generate.shrefuses on any disagreement and the CI
guard tier regenerates and diffs on every pull request. The scheduled
published-image scan applies the documents, and
scripts/security/scan-images.shreruns that exact scan locally.
Changed
- The container image is the web console now.
ghcr.io/rubentalstra/veredictum
ships the newveredictum-consoleLeptos server (app/veredictum-console,
a second workspace package that never publishes to crates.io) instead of the
CLI, per the ruling recorded indocker/Dockerfilewhen the image first
shipped: the CLI payload was a placeholder, and its no-toolchain paths are
cargo install veredictumand the attested release binaries. Start the
console withdocker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/work" ghcr.io/rubentalstra/veredictum:<tag>; it binds loopback through the
publish flag because the console has no login. The server answers
/healthz, the image bakes aHEALTHCHECKthat probes it (the binary is
its own probe, because distroless carries no curl), and the binary drains
in-flight requests on SIGTERM, sodocker stopends it gracefully. The
image build properties are unchanged: pushed by digest, smoke-driven and
scanned before any tag applies, SLSA provenance and an SBOM attested on
the digest,:latestmoving only on a release tag. - The CKM template breadth pack is re-vendored. CKM published new asset
versions ofips-problem-listandips-allergies-and-intoleranceson
2026-08-19, so those two exports carry different bytes. The library is still
305 vendored templates beside the one private-incubator template that answers
404 without an account.
Fixed
- Three ways a document the instrument was JUDGING could stop the instrument,
all found by the new fuzzing lane on its first local campaign. A
decision-table cell nesting 4000 lists deep, or chaining 4000 ordinal tuples,
ranLiteral::from_textoff the stack; a Rust stack overflow aborts rather
than unwinding, so a validator run died instead of reporting a finding. And a
113-byte citation carrying 22{a,b}groups in one path hint asked citation
resolution for four million strings, hanging the run: the 32-variant ceiling
was applied across a clause's tokens but not within one. Literal nesting is
now bounded atliteral::MAX_NESTINGand brace expansion at
validate::MAX_CITATION_VARIANTS, both refusing with a typed finding. The
grammars' own forms are unaffected — a literal reaches three levels and an
authored shorthand names two or three sibling documents. - The README quoted 1107 spec-cited cases, which was the file count under
artifacts/schedule/. The instrument reports 1103, because the four
schedule/performance/journey definitions load as measured-workload
definitions and are not case cores. The page now carries the number
validateprints and says where that number comes from. - Re-running
scripts/vendor/ckm-archetypes.shwould have regressed two facts
in the pack'sPROVENANCE.md: the corrected mixed-licence count, and now the
exerciser. The script emits both, so the record survives a refresh. - The SonarQube lane no longer runs on a Dependabot pull request.
SONAR_TOKEN
is an Actions secret and a Dependabot run reads a separate store, so every
such run failed on the missing secret. The lane is advisory and gates no
merge, so skipping it costs nothing.
0.1.0-alpha.3
Fixed
-
The image vulnerability gate refused to tag the
0.1.0-alpha.2image, so that
release published its binaries and its crate but no pullable image tag. The
finding was real:libssl3t64in the distroless base, CVE-2026-14456, HIGH,
with a Debian fix the base image has not been rebuilt against — the current
:nonrootdigest still carries the vulnerable version, so a base bump does not
resolve it and a distroless image has no package manager to upgrade it in a
layer of our own.It is adjudicated as unreachable rather than suppressed, on the shipped
binary's own ELF header: its dynamic dependencies arelibgcc_s,libmand
libconly. TLS is rustls and the JOSE signing is aws-lc-rs, so nothing this
project builds links OpenSSL, and the image is distroless — no shell, no
package manager, no second executable that could load the library. The entry
lives in a new.trivyignore.yaml, scoped to that one package by PURL, with
the evidence and a three-month expiry, so it has to be re-argued rather than
quietly becoming permanent.
Added
scripts/checks/image-labels.sh, in the ungated guard tier. The base image
digest is declared in three places — the runtimeFROM, the Dockerfile's
base.digestlabel, and the release pipeline'slabels:input, which is the
copy the published image actually carries because it overrides the Dockerfile's
— and an automated base bump edits only the first. Without the guard, merging
one publishes an image whosebase.digestnames a parent it was not built on.
It also checks that every shared OCI key agrees between the two declaration
sites, and refuses to pass vacuously if the publishing lane it expects is
absent.ARCHITECTURE.mdat the repository root: the instrument's design record,
moved here from the FerroEHR mono-repo where it was written. It is the design
authority for the machinery — the artifact set and the case-core field
definitions, the operation bindings, the outcome taxonomy and the ambiguity
register, the assertion vocabulary, verdict computation — and it carries the
population-anchored performance-class model with its journey decomposition,
plus the evidence base and the ISO/IEC 9646 and CASCO grounding the scheme is
built in. Names and paths were adapted to this tree; the substance is
unchanged.- The GHCR image-pulls badge in the README, now that the package exists.
- A Dependabot
ignoreforrandmajor bumps. The dev-dependency exists to hand
pgp's signing call an RNG, andpgp 0.20is onrand_core 0.6, so a major
bump does not compile. Patch and minor bumps within the pin are still proposed,
and advisory-driven updates are unaffected.
Changed
- The container image states in its own header that its current payload is a
placeholder: it ships the CLI today and becomes the web UI's image when that
lands (#6). The CLI's own distribution channels arecargo install veredictum
and the prebuilt binaries on each release. - The distroless base moves to the current
:nonrootdigest
(sha256:a77defd6…). This is not a security fix — the new digest carries
the samelibssl3t64version, verified by scanning it — it is base currency,
so the image is not built on a two-month-old parent.