Skip to content

Releases: blamejs/stash

v2.0.1

Choose a tag to compare

@github-actions github-actions released this 11 Aug 04:20
Immutable release. Only release title and notes can be modified.
v2.0.1
6f8e87a

A documentation-correctness release. No API, behavior, error, or on-disk
format changes: a 2.0.0 store opens under 2.0.1 unchanged and every verb does
exactly what it did. What changes is the standard the documented examples are
held to. The @example blocks in the source comments generate the API
reference and ship inside the tarball, so they are code a reader copies --
but nothing had ever run them. They were compiled and the result discarded,
which catches a syntax error and nothing else: an example calling a method
that has been renamed, or passing an option the code stopped accepting,
parses perfectly and is still wrong. All of them now execute on every build,
each in its own process, and the build fails if one does not. Turning that on
found a broken example on the first run, fixed here. Examples are also now
resolved through the package's published exports map, so a snippet
importing a subpath the package does not publish fails the build rather than
the reader's terminal.

Added

  • The build executes every @example in the published documentation and
    fails if one of them does not run. Each example runs as a real module in
    its own process and counts as verified only when that process exits cleanly
    -- an example that hands its work to a test runner, as the
    backend-conformance snippet does, is judged on the assertions rather than
    on the import returning. Import specifiers resolve through this package's
    own exports map, so an example naming @blamejs/stash/backends/disk is
    checked against what the package actually publishes, and an example whose
    body is only prose or only imports is rejected for showing no call. There
    is no way to opt an example out. CONTRIBUTING.md describes what an example
    may assume.

Fixed

  • The example for close() constructed a Stash without importing it, so
    copying it produced a ReferenceError. It now carries the import the other
    constructor examples do. This was the only documented example that did not
    run; the rest were confirmed correct by executing them.

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 08 Aug 04:44
Immutable release. Only release title and notes can be modified.
v2.0.0
e2621b4

A small, sharp-edged major, and one memory-disclosure fix. This release ships
WITHOUT a preceding deprecation minor -- an exception to the post-1.0
commitment, recorded with its reason in MIGRATING.md, so read the recipe
rather than relying on having been warned at runtime. Everything the store
does is otherwise unchanged: the verb set, the on-disk format, the ref
format, expiry and read budgets, replication, digest agility, and the CLI all
behave exactly as they did in 1.x. What changes is how a few failures report
themselves. Rejecting an entry whose metadata is too large for a sidecar is a
judgement about stored content, not about a caller's argument -- so it now
throws IntegrityError with code EINTEGRITY, the same verdict the read
side of that identical bound has always used, and the same class every other
content rejection uses. Previously it threw a bare TypeError with no code
at all, labelled push: even when store() was the caller, which left a
replication path reporting a verb the caller never invoked and an error the
documented err.code contract could not describe. Separately,
runBackendConformance now requires the name its published { name, create() } contract has always specified and labels every registered case
with it; certifying two backends in one run previously produced two identical
sets of test titles with nothing to distinguish a failure. Upgrading is two
mechanical edits at most, both listed in MIGRATING.md, and neither touches
stored data: no migration, no re-write, no format change. A 1.x store opens
under 2.0 unchanged.

Changed

  • The Node floor moves from 24.18.0 to 24.19.0. It remains a floor and
    not a ceiling -- any newer 24.x, and later majors per the LTS calendar, are
    supported -- and nothing the store does depends on a capability that first
    shipped in 24.19; the patch level is a security-currency floor, kept
    current at the major boundary where raising it is not a surprise. Consumers
    pinned below 24.19.0 upgrade Node; nothing else changes.
  • Fuzzing runs the same targets under jazzer.js directly instead of through
    ClusterFuzzLite, whose action cannot build a JavaScript project at any
    sanitizer setting. Pull requests touching src/ get a burst against every
    target and a scheduled run fuzzes longer each night, with a crash's
    reproducer kept as a run artifact. The seed-corpus verdict check still runs
    with plain Node and no engine. The engine is installed only inside the
    fuzzing job and outside the checkout, so the zero-dependency posture --
    runtime and dev -- is unchanged.
  • An entry whose meta is too large for a sidecar is now rejected with
    IntegrityError (code EINTEGRITY) instead of TypeError. The bound is
    enforced by the backend write that serves both push() and store(), and
    a replicated entry is untrusted stored input, so the rejection is a content
    verdict carrying a code you can branch on -- matching how the read side of
    that same bound has always reported an over-large sidecar. If you catch
    this case, catch IntegrityError (or branch on err.code === 'EINTEGRITY') rather than TypeError. The message no longer names a verb,
    because the line cannot tell which one called it.
  • runBackendConformance(factory, ...) now requires factory.name to be a
    non-empty string, and prefixes every case it registers with it. The { name, create() } contract already specified name, and the type check
    already named it, but nothing enforced or used it -- so a third-party
    author certifying two backends in one run got two byte-identical sets of
    test titles and a failure that named no backend. If you were passing only
    { create }, add a name.
  • v1.x is superseded and will NOT receive security patches, withdrawing the
    24-month window v1.0 announced would open when its successor shipped.
    v1.x had no known consumers and v2.0 is a two-edit upgrade with no
    on-disk format change, so the line is retired rather than serviced. The
    memory-disclosure fix below therefore ships only in v2.x; LTS-CALENDAR.md
    and SECURITY.md record both facts. If you are on v1.x and cannot upgrade,
    open a security advisory and the position will be revisited.
  • A Uint8Array created in another realm -- a vm context, a second module
    loader -- is now accepted as a push() or store() source. It was already
    accepted when yielded as a chunk, so the two paths disagreed about the same
    value, and the documented source set names Uint8Array with no realm
    qualifier. An 8-bit unsigned view remains the only typed array that may
    pass: a Uint16Array is still refused, because copying one would silently
    store a single byte per element.

Fixed

  • A source that misreported its own length could make the store record bytes
    the caller never supplied. length on a typed array is an ordinary
    property, so a Uint8Array subclass holding two bytes but reporting 512
    caused the copy to allocate 512 bytes from Node's shared pool and write
    only the two real ones -- padding the entry with whatever the pool last
    held, and certifying a size and digest over content that was never handed
    over. Sources are now copied by their real byte length, which no property
    can forge. A store embedded alongside other tenants was the exposure; the
    affected copy also charged maxSize and maxTotal for the phantom bytes.
  • The shared source check no longer names a verb. push() and store() both
    reach it, so its message said push: even when store() was the caller --
    the same defect the sidecar-size verdict fixes, one function above it.
  • The temp-file grace period was documented as making verify report an
    in-flight .tmp without repairing it. It reports neither, and it does
    discard one aged past the grace under { repair: true }.
  • consumeRead's sidecar rewrite was described as atomic. It is deliberately
    an in-place rewrite through a single descriptor -- which is what prevents a
    concurrent drop() from being undone -- and a crash mid-write is caught by
    the next read, which rejects the short sidecar.
  • clear() resolves to the number of live entries destroyed. Entries that
    had already expired are reaped as well but are not counted, and each live
    destruction leaves a 'clear' grave visible through tombstones(). None
    of this was stated.
  • The memory backend's tombstone map stores id alongside destroyedAt and
    cause, and consumers read that id; the field comment omitted it,
    inviting an edit that would have broken replication.
  • Constructor option errors now all carry the new Stash: prefix. Seven of
    nine were missing it, so the same option could report two
    differently-shaped messages depending on which check rejected it.

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 14:30
Immutable release. Only release title and notes can be modified.
v1.0.0
f8316d3

The store is feature-complete and the surface it presents is now a contract.
Bytes in, a random-capability ref out, bytes out once and they're gone: the
git-stash verb set, per-entry expiry and read budgets, streaming size and
store-wide limits, integrity verification and audit, tombstone-based
replication, digest agility, and the operational CLI are all shipped and
frozen against breaking change. From 1.0, a breaking change ships a new major
preceded by a deprecation warning at least one minor ahead, and each major
carries a 24-month security-patch window after its successor ships. Nothing
about the code, the API, the on-disk format, or any documented guarantee
changed in this release -- 1.0 is the final 0.1.x store with a commitment
attached, so consumers upgrade with npm install @blamejs/stash@1 and no
code changes. The one load-bearing property is unchanged and now permanent:
there is nowhere in the store to put a key, so it can never decrypt, and no
operator can be compelled to.

Changed

  • The public API surface is declared stable under semantic versioning. Every
    verb (push, apply, pop, show, has, list, reconcilable,
    stats, tombstones, store, drop, clear, verify, prune,
    close), every constructor option, every typed error code, the ref format,
    and the on-disk sidecar format are covered: a breaking change ships a new
    major, preceded by a deprecation warning at least one minor ahead and a
    migration recipe. See MIGRATING.md and LTS-CALENDAR.md.
  • The support policy takes effect: each major receives 24 months of
    security-only patches beginning when its successor ships, on the
    zero-dependency posture (no third-party CVE lane -- a vulnerability is in
    this library's code or in Node itself). The Node floor is 24.18.0.
  • The published package now includes the support-window calendar
    (LTS-CALENDAR.md), the vulnerability-disclosure policy (SECURITY.md), and
    the roadmap (ROADMAP.md) alongside the changelog, migration guide, and
    spec, so every cross-reference in the packaged README resolves for a
    consumer reading the extracted tarball rather than pointing at a file the
    package omits.

v0.1.18

Choose a tag to compare

@github-actions github-actions released this 23 Jul 14:01
Immutable release. Only release title and notes can be modified.
v0.1.18
53678be

A sweep of the tarball's prose -- the source comments, the threat model, the
architecture and spec docs -- to remove development-process voice in favor of
plain descriptions of the store's behavior. Comments that named internal
build artifacts a reader cannot resolve (numbered internal-rule and
fragile-area references), narrated when a feature was added rather than what
it does (milestone and version tags like (M4) or implemented in M2), or
described planned-but-not-yet work now state the current, shipped behavior
directly. The code, the API surface, the on-disk format, and every documented
guarantee are unchanged -- this release only makes the shipped explanations
read for someone encountering the store for the first time.

Changed

  • Source comments and the operator-facing docs (SPEC.md, THREAT-MODEL.md,
    ARCHITECTURE.md, README.md, CONTRIBUTING.md) now describe the store's
    behavior and how to use it rather than how it was built: internal
    build-order and milestone tags, references to internal development
    conventions a reader cannot resolve, and phrasing that narrated
    version-to-version change were replaced with plain statements of the
    current shipped behavior. The threat model states each defense as
    implemented fact without per-defense build-provenance tags; the
    architecture doc presents the shipped surface as a capability list. No API,
    behavior, format, or guarantee changed -- prose only.

v0.1.17

Choose a tag to compare

@github-actions github-actions released this 23 Jul 13:27
Immutable release. Only release title and notes can be modified.
v0.1.17
e138733

The default 'restore' policy has always kept a failed read's entry for
retry. 'burn' is the opt-in that destroys a read entry on the assumption
that a read attempt may have observed its bytes -- but crash recovery applied
it even to a claim whose process died before any byte was read, turning a
crash into silent data destruction that burn's own rationale does not
justify. onPopFailure now governs only the LIVE read path -- a read that
fails mid-drain, resolved in-process by its own handler. Crash recovery of a
stale orphan always RESTORES it and never burns, since a crashed process
observed nothing recovery can confirm and handing a once-only read back for a
retry is safer than destroying it. The store's wall-clock posture -- and its
behavior under a clock step in either direction -- is now written down.

Changed

  • The store's clock posture is now documented (SPEC.md 7.2): every time-based
    decision -- expiry, claim-lease freshness, tombstone pruning -- reads the
    wall clock, because each must survive a restart and, for expiry and
    pruning, agree across replicas. Expiry and pruning are deterministic (an
    absolute expiresAt / destroyedAt, not skew-sensitive); a live reader's
    claim is immune to a clock step; and a backward step only defers a
    destruction the push-time terms already permit -- it never destroys an
    entry early nor lets one outlive its terms.
  • The README documents materializing an entry to a file in a single,
    digest-verified copy (pipeline(await stash.apply(ref), createWriteStream(dest))). The store deliberately offers no
    materializeTo method: a store-side copy would give up the digest-verified
    read, cross the store's write-scope boundary, and work only on a filesystem
    backend, where piping the verified stream is already single-copy and
    backend-agnostic.

Fixed

  • Under onPopFailure: 'burn', crash recovery destroyed a stale claim left
    by a crashed process even when no byte had been read, turning a crash
    before any read into silent data destruction. onPopFailure now applies
    only to a LIVE read that fails mid-drain (resolved in-process); crash
    recovery of a stale orphan always RESTORES it and never burns, even under
    'burn'. The entry comes back for a retry and no tombstone is written. The
    default 'restore' policy is unchanged.

v0.1.16

Choose a tag to compare

@github-actions github-actions released this 23 Jul 03:47
Immutable release. Only release title and notes can be modified.
v0.1.16
e599c27

The SPEC.md 9 backend interface is now a public extension point with a stated
stability discipline, not an undocumented internal seam: any object
implementing its method set is a first-class backend for new Stash({ backend }), and the contract is executable. The new @blamejs/stash/conformance
subpath exports runBackendConformance(factory, { test }) -- the behavioral
suite the in-tree memory and disk backends pass -- so a store on a filesystem
this library does not ship certifies interchangeability against the same
cases without cloning the repository. It imports no test runner, so a backend
author wires their own. Alongside it, a prototype-key-confusion hole
(CWE-1321) is closed in two lookups keyed by untrusted strings: a stored
digest's algorithm prefix and a CLI subcommand token could name an inherited
Object.prototype member and resolve to a phantom the registry never defined
-- both now gate membership on Object.hasOwn, and a source-wide detector
keeps the class from reappearing. Documentation gains a CommonJS consumption
guarantee (pinned by a fixture), a soundness criterion for the onPopFailure: 'burn' trade, the rationale behind the Node floor, and the memory backend's
claim semantics without a filesystem.

Added

  • @blamejs/stash/conformance -- a shippable, zero-dependency,
    test-runner-agnostic backend conformance harness.
    runBackendConformance(factory, { test, assert? }) registers the SPEC.md 9
    behavioral suite against your backend factory, driving the shipped Stash
    consumer path (push / apply / pop / store) and asserting the frozen error
    verdicts (ENOREF, ECLAIMED, E2BIG, EFULL), so a third-party backend
    certifies itself against the same cases the in-tree backends pass without
    cloning this repository. It imports no test runner -- you wire your own
    (node:test or otherwise) -- and the in-tree conformance run composes the
    identical harness against both backends, so there is one definition of the
    shared cases.
  • SPEC.md section 9 now declares the backend interface a stable, spec-first
    contract: its method surface, its semantics (claim atomicity, consumeRead
    atomicity, tombstone first-write-wins, digest verification on read), and
    its error expectations change only with a spec change. Pre-1.0 that
    contract is stable within a version line.

Changed

  • CommonJS consumers can require() the main entry and both backend subpaths
    on the Node floor -- the module graph has no top-level await and imports
    only a synchronous JSON module -- now documented in the README and pinned
    by a spawned .cjs fixture against a future regression.
  • The onPopFailure: 'burn' trade has a stated soundness criterion (SPEC.md
    6, SECURITY.md): 'restore' (the default) keeps a failed pop's entry
    intact for retry and is safe for irreplaceable bytes, while 'burn'
    reinstates partial-delivery data loss and is sound only when a partial read
    must never be retried and the loss is acceptable.
  • The >=24.18.0 Node floor's rationale is recorded (SPEC.md 2,
    LTS-CALENDAR.md): the capabilities the store relies on -- the stable
    permission model, explicit resource management, require(esm) -- arrived at
    the Node 24 major; the patch level is a security-currency floor, not a
    feature dependency.
  • The MemoryBackend @module block documents its claim semantics without a
    filesystem -- the synchronous Map move as the atomicity two concurrent pops
    race on, a claimedAt stamp as the disk mtime analogue, and
    process-lifetime claims with no cross-process crash recovery -- and the
    bounded-insert capacity gate records its two-pass (prune then stats) design
    as an irreducible invariant.

Security

  • CWE-1321 (prototype-key confusion): a stored digest whose algorithm prefix,
    or a CLI subcommand token, named an inherited Object.prototype member
    (constructor, __proto__, toString, valueOf) resolved to that member
    instead of a registry miss. The phantom defeated the digest read's
    algoOf(digest) ?? default fallback -- turning a self-describing read into
    a crash on an algorithm the store cannot construct -- and slipped past the
    CLI's unknown-command guard so an inherited name was dispatched as a known
    command. Both lookups now gate membership on Object.hasOwn, so only a
    defined algorithm or command ever resolves and a non-registry key is always
    a clean miss; a source-wide detector fails the build on any reintroduction
    of the inline membership shape.

v0.1.15

Choose a tag to compare

@github-actions github-actions released this 18 Jul 09:59
Immutable release. Only release title and notes can be modified.
v0.1.15
30ce324

A batch of replication- and recovery-correctness fixes. reconcilable() is a
new resilient query for the anti-entropy loop: list() stays loud on a
corrupt sidecar (right for an audit), but one damaged entry no longer halts
the replication of every healthy one -- reconcilable() returns the healthy
entries and reports the corrupt ids separately, so a sync keeps flowing and
surfaces the damage instead of stopping. store() now reconciles on byte
identity rather than the algorithm-tagged digest string, so two stores
holding the same bytes under different digest algorithms (a mixed-algorithm
topology, first-class since digest agility) reconcile idempotently instead of
throwing a spurious integrity conflict -- verified byte-for-byte, so
genuinely different bytes and a lying replica still fail closed, and the
reconcile no longer rejects an already-present entry just because the
receiving store's limit is now smaller. The disk backend's blob rename -- the
one mutation rename that was not retried -- now rides out a transient
filesystem fault (a briefly-held handle on Windows) exactly as the sidecar
and claim renames already do, so a legitimate push is not lost. And crash
recovery and the claimed-read path are hardened throughout: a live reader's
claim is guarded from the moment acquisition begins (so a forward wall-clock
step can never hand a once-only entry to a second reader, nor destroy a read
mid-drain), a claim orphaned by a faulted resolution is always reclaimed
rather than stranded, a crash-corrupted sidecar on a claimed entry no longer
wedges every operation, and a replicated entry with an exhausted read budget
is rejected. Documentation was also swept for stale single-algorithm digest
references left over from digest agility.

Added

  • reconcilable() -- a resilient reconciliation query returning { entries, corrupt }: the healthy entries to replicate plus the ids of entries whose
    sidecars are too damaged to read. It is the source read for the documented
    anti-entropy loop, so one corrupt sidecar can no longer halt replication of
    the healthy entries; list() remains loud over corruption (an audit must
    be), and the corrupt ids are surfaced for verify({ repair: true }), never
    silently skipped.

Fixed

  • store() reconciles a replicated entry on BYTE IDENTITY, not the
    algorithm-tagged digest string. Two stores holding the same bytes under
    different digest algorithms carried different "<algo>:<hex>" strings, so
    an identical-bytes replica was rejected as a digest conflict and could
    never reconcile. It now settles the idempotent/conflict verdict on the
    bytes -- identical bytes reconcile as a no-op, genuinely different bytes
    still fail closed with IntegrityError, and a lying replica is caught --
    nothing is written and no id is resurrected.
  • A same-bytes duplicate under a different digest algorithm now reconciles
    idempotently even when the receiving store's maxSize is smaller than the
    entry -- a lowered limit, or a replica with a tighter limit, no longer
    wedges mixed-algorithm anti-entropy on a spurious SizeExceeded. A
    reconcile writes nothing, so it bounds its byte-identity re-hash by the
    already-stored entry's own size; an oversized replica under an existing id
    is reported as an IntegrityError byte conflict, never a limit error.
  • The claimed-read path (pop and budgeted apply) is hardened around crash
    recovery, which never age-reclaims a claim a live reader holds -- only
    orphan claims from a crashed prior run. The live-holder guard now protects
    a claim across its whole life: from the moment acquisition begins, through
    the drain, until it resolves. So a forward wall-clock step past
    claimTimeout can neither resurrect a once-only entry for a second reader
    during acquisition nor reclaim/burn the read a consumer is still mid-drain
    on; and a claim orphaned by a faulted resolution (a restore, commit, or
    burn that throws) is always reclaimed by recovery on the next operation,
    rather than stranded ECLAIMED until the process restarts. The guard is
    shared per store (keyed by backend identity), so even two Stash instances
    constructed over one store -- single-writer-per-root is the contract, but
    the guard holds regardless -- never age-reclaim each other's live reads.
  • A crash during a budgeted read that leaves a claimed entry with a corrupt
    sidecar no longer wedges the entire store. Because crash recovery runs
    before every operation, one such entry previously made every call --
    push, apply, pop, drop, list, stats -- fail with an integrity
    error, with no way back. Recovery now reaps the unreadable entry; a genuine
    layout fault still fails loudly.
  • store() rejects a replicated budgeted entry whose read budget is already
    exhausted (readsLeft 0) as malformed, instead of accepting it and then
    throwing on the first read: a live budgeted entry always has at least one
    read remaining, since the read that spends the last credit destroys the
    entry.
  • Disk backend: the blob .tmp->final rename now retries a transient
    filesystem fault (a briefly-lingering handle, e.g. on Windows under
    contention) instead of failing an otherwise-valid push and destroying the
    already-streamed bytes -- the one mutation rename that was not yet wrapped
    in the transient-fault retry every sibling rename uses.
  • Documentation and code comments no longer describe the integrity digest as
    fixed to sha256: prose and @spec references now reflect the
    construct-time algorithm choice (sha256 by default) and the full FIPS
    coverage (180-4 for sha2, 202 for sha3/shake).

v0.1.14

Choose a tag to compare

@github-actions github-actions released this 17 Jul 23:44
Immutable release. Only release title and notes can be modified.
v0.1.14
46d7cd4

The store gains its first executable entry point. npx @blamejs/stash <command> (or the stashjs command from a global install) exposes the query
and maintenance verbs: verify (audit for damage, --repair to remove
condemned entries, orphans, and corrupt graves), stats, prune, list
(--include-expired to reveal lapsed entries), tombstones, and has <ref>. Add --json to any command for a machine-readable document, for cron
and scripting. The root comes from --root <dir>, else $STASH_ROOT, else
./.stash; a missing root is refused rather than conjured into an empty
store from a typo. The CLI composes only already-shipped verbs -- it never
moves bytes (no push/apply/pop/store) and never destroys by ref (no
drop/clear), so it hands out no capability and streams no blob. It runs under
node --permission with read/write scoped to the root exactly as the library
does, fails closed with stable exit codes, and never echoes a ref, a metadata
value, or a filesystem path in an error. Point it at a stash whose owning
process is stopped, or a cold-standby replica: like every access to a disk
root, it is single-writer, and every command except verify runs the
crash-recovery scan first.

Added

  • The stashjs CLI (npx @blamejs/stash <command>): verify [--repair],
    stats, prune, list [--include-expired], tombstones, and has <ref>, each a thin adapter over the matching Stash verb. --json on any
    command emits the raw object for scripting; the default is a human table
    (which omits opaque per-entry metadata). The root is taken from --root,
    $STASH_ROOT, or ./.stash, and must already exist. Exit codes are stable
    and distinct: clean, damage-found, usage error, malformed ref, integrity
    fault, or I/O fault. It exposes only the query and maintenance verbs --
    never the bytes-moving or destroy-by-ref verbs -- and holds the library's
    permission-model and capability-free-error posture.

v0.1.13

Choose a tag to compare

@github-actions github-actions released this 17 Jul 19:47
Immutable release. Only release title and notes can be modified.
v0.1.13
a46e783

On a platform without O_NOFOLLOW (Windows), the disk backend cannot refuse a
symlink at the open itself, so after opening a blob or sidecar it
cross-checks the descriptor's identity against a no-follow lookup of the name
-- catching a symlink traversed at open or a name swapped for a different
object afterward. That check keyed on device + inode alone. Windows
synthesizes the inode from the NTFS file index and can transiently report 0
for a file under heavy parallel I/O, so two distinct in-root files could both
read as inode 0 and be mistaken for the same object -- a fail-open in the
swap guard. The identity comparison now also requires size and creation time
to match. Because the new terms are ANDed on, the check only ever becomes
more selective: it never accepts a swap it rejected before, and an untampered
read (whose open descriptor and name resolve to the same inode) still
matches, since size and creation time agree for one object. This is
defense-in-depth -- planting a symlink in the store root already requires a
privilege the recommended permission sandbox denies -- but a security guard
that silently passes on an identity collision is a real gap, now closed. No
API change.

Fixed

  • Disk backend, platforms without O_NOFOLLOW (Windows): the post-open
    symlink-swap guard compared only device + inode, so two distinct files that
    transiently reported inode 0 (an NTFS-file-index quirk under heavy parallel
    I/O) could be mistaken for one object -- a fail-open. The file-identity
    comparison now also requires size and creation time to agree. The terms are
    ANDed, so the check is strictly more selective and cannot vouch for a
    swapped object it previously rejected; an untampered read still matches.
    The same identity check is now the single choke point behind both the
    read-path swap guard and the crash-recovery interrupted-claim check.

v0.1.12

Choose a tag to compare

@github-actions github-actions released this 17 Jul 19:27
Immutable release. Only release title and notes can be modified.
v0.1.12
36e6015

The integrity hash is now a construct-time choice: new Stash({ backend,
digest }) selects sha256 (the default, unchanged), sha512, sha3-256,
sha3-512, or shake256 -- all node:crypto builtins, so the zero-dependency
rule holds. This is crypto-agnosticism for INTEGRITY, not confidentiality:
there is still no key and no cipher anywhere in the tree. The stored digest
is self-describing ("algo:hex"), so a read -- and verify() -- hashes with the
algorithm the entry was WRITTEN with, never a global assumption. A store may
therefore hold entries under different algorithms (the option changed over
the store's life, or store() replicated an entry that carried its own) and
each still verifies. The construct-time option sets the algorithm for new
pushes only; omitting it keeps sha256, so every existing store is
byte-identical. An unknown algorithm is a config-time TypeError; a stored
digest with an unknown algorithm or the wrong hex length is an
IntegrityError.

Added

  • digest constructor option -- the integrity-hash algorithm for new writes:
    sha256 (default), sha512, sha3-256, sha3-512, or shake256. sha2
    is FIPS 180-4; sha3 and shake are FIPS 202; shake256's output is pinned
    to 64 bytes. The stored digest is self-describing ("<algo>:<hex>"), so
    apply, pop, and verify() hash each entry with its own algorithm -- a
    store may mix algorithms and every entry still verifies, and store()
    replicates an entry with its algorithm intact. The default is unchanged, so
    an omitted option leaves every existing store byte-identical. This stays
    inside SPEC.md section 1: still no key, no cipher -- the digest verifies
    integrity, never confidentiality.