Compare commits

..

1 commit

Author SHA1 Message Date
a128f0eda5 fix(index): a field holds every value kind it was written with, not the first one
Some checks failed
CI / Node 22 (push) Successful in 12m28s
CI / Node 24 (push) Successful in 12m27s
CI / Integration + conformance (Node 22) (push) Failing after 16m13s
CI / Bun (latest) (push) Successful in 12m27s
The metadata index fixed a field's value type from the first value it saw.
Every later value of another kind was coerced to that type, and when coercion
failed — `Number('electronics')` is NaN — the value was dropped from the index
with no error at all. The row stayed readable by id and by vector search and
vanished only from equality filters on that one field, which is what made it so
quiet: writing `category: 'electronics'` rows and then `category: 5` rows left
`where { category: 5 }` returning nothing, while the same rows in a
numbers-only corpus answered correctly.

The column store now keeps one posting column per (field, kind), where a kind
is a JavaScript typeof class. The first kind a field sees keeps the historical
`_column_index/<field>/` layout, so a single-kind field is byte-identical to
what earlier versions wrote and an index written before this opens unchanged;
each later kind takes its own column at `_column_index/<field>/k/<kind>/`.

Equality reads the column matching the query value's own kind, so `{c: 5}` and
`{c: '5'}` match different rows and neither is coerced into the other. Ranges
route by the kind of their bounds, and an unbounded range — the "has any value"
probe behind `exists` — reads every kind. A mixed field orders by kind first,
then by value, because a number and a string have no order between them. A
value that cannot be encoded for the column its own kind selected now raises
instead of being skipped: that path is unreachable by construction, and if it
is ever reached it is the silent drop this change exists to end.

Two neighbours fell out of the same routing. A boolean query value is now
encoded to the 1/0 the column stores, so boolean equality matches at all. And
an integer column widens to f64 the first time a non-integer arrives, so 4.5 is
stored as itself rather than rounded to 5 and answering the wrong query.

Field type inference reports every kind a field holds beside its dominant
reading, rather than leaving callers to treat one type as the whole answer.

Pins: mixed-kind equality in both write orders, `5` vs `'5'`, booleans mixed in,
a numeric range over a mixed field's numbers, close/reopen keeping every typed
posting, and an index in the pre-existing on-disk shape still reading.
`tests/critical-neural-validation.test.ts` — which writes `category` as strings
in one test and as numbers in another against one shared brain — passes whole
for the first time.
2026-09-03 08:52:58 -07:00
54 changed files with 424 additions and 855 deletions

View file

@ -2,28 +2,6 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [10.4.13](https://source.soulcraft.com/soulcraftlabs/open-brainy/compare/v10.4.12...v10.4.13) (2026-09-03)
- A shutdown that holds its listener until the exit decision, and a test suite that closes every brain it opens
- fix(shutdown): the engine's signal handler keeps its listener registered until the exit decision is made — closing the last live instance no longer deregisters the handler mid-run, so a second signal delivery during a clean shutdown can never kill the process after the work is done (a2ea21b3)
- fix(release): the release wall entry commits under an explicit git identity read from the developer's checkout; a host with no identity refuses by name instead of failing inside git (aac853d3)
- test(hygiene): every brain a test file creates is closed by that file — 40 files fixed, the leaks that let a stray cadence narrate into later files are gone; brains whose init() was expected to fail are closed too (6eb5e448)
### [10.4.12](https://source.soulcraft.com/soulcraftlabs/open-brainy/compare/v10.4.11...v10.4.12) (2026-09-03)
- Mixed-kind fields index exactly, arrays to 256, a drained loop is not a shutdown, and finds project from the column store
- fix(index): a metadata field holds every value kind it was written with — one posting column per (field, kind); an equality filter reads the query value's own kind, a range routes by its bounds; nothing is refused and nothing is silently dropped; an index written by the old shape opens unchanged (a128f0ed)
- fix(metadata): metadata arrays index up to 256 elements; a longer array refuses at write time by name (MetadataArrayTooLargeError) — a vector parked in metadata now throws; move it to `vector` (e435da78)
- fix(shutdown): beforeExit runs a non-closing flush only — a script that never calls close() exits with the writer lock on disk and no clean-shutdown marker, and the next open evicts the stale lock and folds the log, bounded; SIGTERM and SIGINT are unchanged (6baa4d7f)
- feat(find): field projection — find({fields}) and get({fields}) resolve scalars from the column store on every leg, including vector-leg finds; absent fields stay absent (ad0f493f)
- fix(find): orderBy is the order on every find path, not only the metadata-only one (5e720d17)
- fix(metadata): the legacy sparse range path orders values, or refuses by name — never ranks by hash (a7eb7f52)
- fix(close): a read-only brain writes nothing under `_system/` (f27a7776)
- fix(contract): the flush gate's internals are private, not doors (72c8ee6a)
- test(hygiene): the triple-intelligence correctness cases sit in the gate; the idle and connected-find pins name the brain they measure (28083981)
- ci(release): the rail writes its own wall entry into the shared releases repo — never hand-written again (adcb883e)
### [10.4.11](https://source.soulcraft.com/soulcraftlabs/open-brainy/compare/v10.4.9...v10.4.11) (2026-09-02)
- ci: superseded pushes cancel their own runs (concurrency per ref) (6053f6d4)

View file

@ -4,11 +4,6 @@
<h1 align="center">Brainy</h1>
> **Frozen at 10.4.13 (2026-09-03).** This repository is the reference implementation of the Brainy store format and API,
> published under the MIT license. Version 10.4.13 is its last release; the repository is read-only from here. The engine
> continues as `@soulcraft/brainy`, which bundles this layer as owned code; every published version of this package stays
> available on The Source. Use this repository to read a Brainy store independently or to verify the conformance contract.
<p align="center">
<b>Three database paradigms. One API. Zero configuration.</b><br>
The in-process knowledge database for TypeScript — vector search, graph traversal,<br>

View file

@ -1,15 +1,5 @@
# @soulcraft/brainy — Release Notes for Consumers
> **Frozen at 10.4.13 (2026-09-03).** 10.4.13 is the last release of `@soulcraftlabs/brainy`; this repository is read-only from here.
> Release notes for the product engine continue on its own wall.
Machine-readable release notes are published at
https://source.soulcraft.com/soulcraftlabs/releases/raw/branch/main/open-brainy.json
(this engine) and
https://source.soulcraft.com/soulcraftlabs/releases/raw/branch/main/brainy.json
(the product engine) — read by HQ's `/hq/releases` door, and the source of
truth ahead of this file.
This file is the **quick reference for downstream sessions** tracking Brainy changes.
Full auto-generated changelog: `CHANGELOG.md` · Releases: https://source.soulcraft.com/soulcraftlabs/open-brainy/releases

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "@soulcraftlabs/brainy",
"version": "10.4.13",
"version": "10.4.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@soulcraftlabs/brainy",
"version": "10.4.13",
"version": "10.4.11",
"license": "MIT",
"dependencies": {
"@msgpack/msgpack": "^3.1.2",

View file

@ -1,6 +1,6 @@
{
"name": "@soulcraftlabs/brainy",
"version": "10.4.13",
"version": "10.4.11",
"brainyContract": 1,
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
"main": "dist/index.js",

76
releases/brainy.json Normal file
View file

@ -0,0 +1,76 @@
{
"product": "brainy",
"entries": [
{
"version": "11.0.5",
"date": "2026-09-02",
"headline": "Graph-first finds in production, and opens that stop rescanning history",
"items": [
"find({ connected, where }) now walks the neighbours first and filters only those rows through a native door — correct at every page and O(neighbours), never the whole store.",
"related() with a list of verb types returns every requested kind (a fast path had silently kept only the first).",
"Deferred-embedding recovery resumes from a low-water mark instead of rescanning the whole generation log at every open — measured at two minutes on a large brain, now milliseconds."
],
"url": null,
"thumb": null
},
{
"version": "11.0.4",
"date": "2026-09-01",
"headline": "Closes in milliseconds, index rebuilds without the disk-sync storm",
"items": [
"close() no longer pays deferred compaction or waits out an in-flight rebuild — measured 8 ms against the 4-minute closes it replaces; deferred work resumes at the next open, in the background.",
"The metadata index's rebuild syncs to disk per shard instead of per row, and the durability point moved to the publish step — the same guarantee, a fraction of the disk traffic.",
"A new native filter door evaluates queries over exactly the candidate rows a graph walk found, never the whole store."
],
"url": null,
"thumb": null
},
{
"version": "11.0.3",
"date": "2026-09-01",
"headline": "The embedding upgrade ceremony runs on every brain",
"items": [
"A brain opened through the standard plugin now carries its embedding-model identity, so the full-precision upgrade ceremony can run on it.",
"A one-fix release; nothing else changed."
],
"url": null,
"thumb": null
},
{
"version": "11.0.2",
"date": "2026-08-31",
"headline": "One embedding quality everywhere, 34× faster imports",
"items": [
"Every runtime embeds with the same full-precision model — search quality no longer depends on where you run.",
"Bulk embedding measured 3.14.2× faster, and an online re-embed ceremony upgrades existing stores without downtime.",
"The engine's change feed is documented, with the SSE/WebSocket fan-out pattern for realtime surfaces."
],
"url": null,
"thumb": null
},
{
"version": "11.0.1",
"date": "2026-08-31",
"headline": "Deletes inside transactions are safe",
"items": [
"Deleting relations inside a transact() no longer corrupts index bookkeeping.",
"A store that deletes its last relation keeps serving instead of refusing."
],
"url": null,
"thumb": null
},
{
"version": "11.0.0",
"date": "2026-08-28",
"headline": "One install, one engine — Brainy",
"items": [
"The former two-package pair is one package: the native engine under the familiar API. One import is the whole install.",
"A missing native build refuses loudly with its cures named; nothing falls back silently.",
"Stores open in place — no migration."
],
"url": null,
"thumb": null
}
],
"history": "The version line continues from the 4.3.x native-engine releases; their record lives in the product repository's CHANGELOG.md."
}

136
releases/open-brainy.json Normal file
View file

@ -0,0 +1,136 @@
{
"product": "open-brainy",
"entries": [
{
"version": "10.4.11",
"date": "2026-09-02",
"headline": "Hybrid finds filter before they hydrate, one owner per shutdown, and a faster open",
"items": [
"Hybrid finds (query/vector combined with a filter, including connected and fusion finds) now filter first and hydrate only the page — one batchGet of exactly the requested rows, instead of hydrating everything the search side found. Fixes a bug where any page after the first came back empty.",
"A brain now has exactly one shutdown owner — a host and its engine no longer race to close the same store, and a follow-up flush requested during a running flush is handed off cleanly instead of ever risking a stall.",
"find({ path }) and other path-scoped VFS searches now serve a real range over the indexed path (O(log n)) instead of refusing the query outright — both scoped and recursive:false searches were silently broken before this.",
"Open no longer rescans a brain's whole fact log on every open — sealed segments the manifest already accounts for are skipped, collapsing a multi-second open term to near-zero on large brains.",
"commitTransaction() now refuses by name if single-ops are still pending, and a read-only open no longer writes clean-shutdown evidence it didn't earn — two correctness invariants that were previously assumed, not enforced."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.11",
"thumb": null
},
{
"version": "10.4.10",
"date": "2026-09-02",
"headline": "A planner door for indexes, batched containment repair, and a fixed near()",
"items": [
"An optional planFindPage door lets an index plan a find() and answer it in one call, instead of the engine assembling the plan itself.",
"repairContainment's reconcile pass now walks paged edges once instead of issuing one graph call per file.",
"find({ near }) now searches around the anchor's own vector and refuses by name when none is available, instead of silently querying with no vector at all."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.10",
"thumb": null
},
{
"version": "10.4.9",
"date": "2026-09-02",
"headline": "Graph-first finds, honest verb arrays, and opens that stop rescanning history",
"items": [
"find({ connected, where }) now walks the neighbours first and filters only those rows — correct at every page, and O(neighbours) instead of O(store).",
"related() with a list of verb types (or sources, or targets) returns every requested kind — four fast paths silently kept only the first.",
"Deferred-embedding recovery resumes from a low-water mark instead of rescanning the whole generation log at every open — measured at two minutes on a large brain, now milliseconds."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.9",
"thumb": null
},
{
"version": "10.4.7",
"date": "2026-09-01",
"headline": "Count ledgers can no longer race themselves",
"items": [
"Concurrent count flushes coalesce into one writer with a trailing pass — parallel flushes can no longer corrupt a store's count ledger.",
"Atomic writes carry a per-process sequence, so two processes' temp files can never collide."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.7",
"thumb": null
},
{
"version": "10.4.6",
"date": "2026-08-31",
"headline": "Transactions cross the index seam safely",
"items": [
"Deleting relations inside a transact() no longer fails against the metadata index — operations take a JSON-safe view at the moment they execute.",
"Fixes a class of transaction failures on stores with integer-mapped relation endpoints."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.6",
"thumb": null
},
{
"version": "10.4.5",
"date": "2026-08-31",
"headline": "Recovery tells the truth, docs live at home",
"items": [
"A torn generation-log tail is a terminal verdict with a named cure — never an endless wait at open.",
"A sealed segment declares only the generations it actually holds.",
"The engine's documentation now publishes from its own repository."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.5",
"thumb": null
},
{
"version": "10.4.4",
"date": "2026-08-28",
"headline": "Faster opens, quieter idle",
"items": [
"Opening a store discovers generations from directory names instead of walking the log, and answers \"any entities?\" with one directory read.",
"The flush-request watch is event-driven; idle stores stop paying a polling heartbeat.",
"A slow open now names the exact step it is in, so operators see what is being paid and why."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.4",
"thumb": null
},
{
"version": "10.4.3",
"date": "2026-08-27",
"headline": "Open Brainy, under its own name",
"items": [
"The same engine as 10.4.2, now published as @soulcraftlabs/brainy — the MIT reference engine, on The Source.",
"No code changes; your imports change once and everything else stays put."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.3",
"thumb": null
},
{
"version": "10.4.2",
"date": "2026-08-27",
"headline": "Vectors that lie are refused, counts that drift are caught",
"items": [
"A zero-norm vector is not a vector: the index refuses them, rebuilds skip them, and a sanctioned unvector door removes them cleanly.",
"The canonical count ledger derives from identity records and marks legacy-derived ledgers suspect at load.",
"Plugin activation failures keep their original error as cause, so the real frame reaches your logs."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.2",
"thumb": null
},
{
"version": "10.4.1",
"date": "2026-08-26",
"headline": "Writes that change nothing cost nothing",
"items": [
"The read gate is per index family, and a write carrying unchanged data never re-embeds.",
"The vectored-row count joins the ledger, so vector coverage is a number you can read, not a guess."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.1",
"thumb": null
},
{
"version": "10.4.0",
"date": "2026-08-26",
"headline": "Repair routing, the vector ledger, and honest empties",
"items": [
"Repairs route to the index that owns the damage, and the open gate closes the vector leg until coverage is proven.",
"An empty string is real data, not a missing field.",
"The metadata crossing never carries raw integer relation endpoints — a whole class of serialization faults closed."
],
"url": "https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v10.4.0",
"thumb": null
}
],
"history": "Earlier releases are recorded in CHANGELOG.md in this repository."
}

View file

@ -159,21 +159,9 @@ CHANGELOG_ENTRY="### [${NEW_VERSION}](https://source.soulcraft.com/soulcraftlabs
${COMMITS}
"
# A CURATED entry wins over the generated one. When a release is cut from a
# lineage that diverged from the previous tag (a candidate branch carrying
# main's history), `git log <last-tag>..HEAD` lists every commit the tag never
# saw — old notes, already-shipped fixes under new hashes, merge commits — and a
# wall entry derived from it would misreport the release. If CHANGELOG.md
# already carries a `### [NEW_VERSION]` heading, it was written on purpose:
# keep it, and skip the generated prepend entirely.
CURATED_ENTRY=false
if grep -qE "^### \[${NEW_VERSION}\]" CHANGELOG.md 2>/dev/null; then
CURATED_ENTRY=true
echo -e "${YELLOW}CHANGELOG already carries a curated ### [${NEW_VERSION}] entry — keeping it, not generating one from commits${NC}"
fi
# Prepend to CHANGELOG.md after header
if [ "$CURATED_ENTRY" = false ] && [ -f "CHANGELOG.md" ]; then
if [ -f "CHANGELOG.md" ]; then
# Read header (first 4 lines)
HEADER=$(head -n 4 CHANGELOG.md)
# Read rest of file
@ -188,21 +176,16 @@ fi
echo -e "${GREEN}✅ CHANGELOG updated${NC}\n"
# Step 6b: Update the releases wall entry — mechanical, derived from the
# CHANGELOG entry just composed. The fleet's HQ page reads open-brainy.json
# from the one shared releases repo, soulcraftlabs/releases on The Source —
# this used to be hand-written after every release (David: never again —
# make it a step of the rail, landed in the one shared home; this repo no
# longer hosts its own copy). This step clones/fetches that repo into a
# local cache, prepends the entry, and pushes it directly — a real
# cross-repo push, refusing loudly (never skipping) on any
# clone/validation/commit/push failure.
# CHANGELOG entry just composed. The fleet's HQ page reads releases/open-brainy.json
# directly; this used to be hand-written after every release (David: never
# again — make it a step of the rail).
echo -e "${BLUE}5⃣▸ Updating the releases wall...${NC}"
node scripts/wall-entry.mjs --product open-brainy --version "${NEW_VERSION}" --date "${RELEASE_DATE}" --from-changelog CHANGELOG.md
echo -e "${GREEN}✅ Releases wall updated${NC}\n"
# Step 7: Create release commit
echo -e "${BLUE}6⃣ Creating release commit...${NC}"
git add package.json package-lock.json CHANGELOG.md
git add package.json package-lock.json CHANGELOG.md releases/open-brainy.json
git commit -m "chore(release): ${NEW_VERSION}"
echo -e "${GREEN}✅ Release commit created${NC}\n"

View file

@ -2,85 +2,48 @@
/**
* @module scripts/wall-entry
* @description The releases-wall entry, made mechanical. The fleet's HQ page
* reads one public JSON per product from the ONE releases repo on The Source
* (soulcraftlabs/releases, files <product>.json at its root shape
* {product, entries:[{version, date, headline, items, url, thumb?}]}), at
* https://source.soulcraft.com/soulcraftlabs/releases/raw/branch/main/<product>.json.
* Those entries were hand-written after every release, then briefly written
* into this repo's own releases/<product>.json; this script is the one door
* that composes an entry and lands it in the shared repo, so it is never
* hand-written and never forked across repos again.
* reads one public JSON per product (releases/<product>.json shape
* {product, entries:[{version, date, headline, items, url, thumb}], history}).
* Those entries were hand-written after every release; this script is the
* one door that composes one, so it never has to be typed by hand again.
*
* Two modes:
*
* 1. Generate + publish (default):
* 1. Generate + write in place (default):
* node wall-entry.mjs --product <p> --version <v> --date <YYYY-MM-DD> \
* --from-changelog <CHANGELOG.md>
* --from-changelog <CHANGELOG.md> [--file releases/<p>.json]
* Derives an entry from the CHANGELOG.md entry for <v> (headline = the
* entry's first bullet, items = every bullet, trimmed of its trailing
* commit hash), then:
* - clones (or, if a cached clone already exists, fetches and resets)
* the releases repo into a local cache directory,
* - prepends the entry to <cache>/<p>.json, newest first replacing
* any existing entry for the same version so a re-run is idempotent,
* - validates the file's shape before and after,
* - commits the change as "chore(wall): <p> <v>" and pushes main.
* A failure at any step (clone, validation, commit, push, a
* non-fast-forward remote) exits non-zero naming the cure. Nothing is
* ever skipped the wall either lands correctly or the release fails.
* commit hash), prepends it to --file (default releases/<product>.json,
* newest first), refusing by name if <v> is already present, and
* validates the whole file's shape + ordering before and after writing.
* Both engines run this identically, each against its own repo's
* releases/<product>.json the wall file always lives beside the
* CHANGELOG it is derived from, never in another repo.
*
* 2. Dry run:
* node wall-entry.mjs --dry-run --product <p> --version <v> \
* --date <YYYY-MM-DD> --from-changelog <CHANGELOG.md>
* Derives the entry exactly as above and prints it, along with the file
* it would be written to, but touches no clone and no remote usable
* from a fresh checkout with no cache and no network.
* 2. Validate only (--check):
* node wall-entry.mjs --check --file <releases/p.json>
* Validates the file's exact key set (top-level and per-entry), field
* types, and strict-descending semver ordering with no duplicates.
* Read-only; never writes. Exit 0 = clean, exit 1 = named violations
* printed to stderr.
*
* 3. Validate only (--check):
* node wall-entry.mjs --check --file <path/to/product.json>
* Validates an arbitrary wall file's exact key set (top-level and
* per-entry), field types, and strict-descending semver ordering with
* no duplicates. Read-only; never writes. Exit 0 = clean, exit 1 =
* named violations printed to stderr.
*
* The remote and the local cache directory are each overridable
* (--remote / --cache-dir, or WALL_ENTRY_RELEASES_REMOTE /
* WALL_ENTRY_RELEASES_CACHE_DIR) so tests can point at a throwaway local
* bare repo and a throwaway cache directory never the real remote or the
* real developer cache.
*
* No dependencies beyond the system `git` binary CHANGELOG parsing,
* semver comparison, and JSON shape checking are all hand-rolled below.
* No dependencies CHANGELOG parsing, semver comparison, and JSON shape
* checking are all hand-rolled below.
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { homedir } from 'node:os'
import { dirname, join } from 'node:path'
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
const DEFAULT_REMOTE = 'git@source.soulcraft.com:soulcraftlabs/releases.git'
const ENTRY_KEYS = ['version', 'date', 'headline', 'items', 'url', 'thumb']
const FILE_KEYS = ['product', 'entries', 'history']
/** @returns {string} */
function defaultCacheDir() {
const base = process.env.XDG_CACHE_HOME || join(homedir(), '.cache')
return join(base, 'soulcraft-releases')
}
// Required on every entry; "thumb" is optional (may be absent, or present as
// string | null) — matching the HQ contract's {..., thumb?}.
const ENTRY_REQUIRED_KEYS = ['version', 'date', 'headline', 'items', 'url']
const ENTRY_OPTIONAL_KEYS = ['thumb']
const ENTRY_ALLOWED_KEYS = [...ENTRY_REQUIRED_KEYS, ...ENTRY_OPTIONAL_KEYS]
const FILE_KEYS = ['product', 'entries']
// The public permalink pattern, by product. Every entry MUST carry an https
// permalink: HQ's parser rejects a wall whose entries carry url: null (the
// whole feed became unreadable on 2026-09-02). A product whose forge repo is
// private links its PUBLIC package page on The Source instead of a release
// page that would 404 for HQ's readers.
// The public release-page URL pattern, by product — only products with a
// PUBLIC forge repo get a derived link. A product without an entry here
// (e.g. "brainy", whose repo is private) gets url: null, matching every
// entry the fleet has shipped for it so far — a private link would 404 for
// anyone reading the public HQ page.
const RELEASE_URL_PATTERNS = {
'open-brainy': (version) => `https://source.soulcraft.com/soulcraftlabs/open-brainy/releases/tag/v${version}`,
'brainy': (version) => `https://source.soulcraft.com/soulcraft/-/packages/npm/@soulcraft%2Fbrainy/${version}`,
}
/**
@ -147,11 +110,10 @@ function compareSemver(a, b) {
}
/**
* Validate a wall file's full shape: top-level keys ("product", "entries"
* no more, no less), per-entry keys and field types ("thumb" optional), and
* strict-descending semver ordering with no duplicates. Collects every
* violation instead of failing on the first, so a caller reports the whole
* picture in one pass.
* Validate a wall file's full shape: top-level keys, per-entry keys and
* field types, and strict-descending semver ordering with no duplicates.
* Collects every violation instead of failing on the first, so --check
* reports the whole picture in one pass.
* @param {unknown} data
* @returns {string[]} Violation messages; empty means the file is clean.
*/
@ -173,6 +135,9 @@ function validateShape(data) {
if (typeof obj.product !== 'string' || obj.product.trim() === '') {
errors.push('top level: "product" must be a non-empty string')
}
if (typeof obj.history !== 'string' || obj.history.trim() === '') {
errors.push('top level: "history" must be a non-empty string')
}
if (!Array.isArray(obj.entries)) {
errors.push('top level: "entries" must be an array')
return errors // nothing further to check without an array
@ -187,8 +152,8 @@ function validateShape(data) {
}
const entry = /** @type {Record<string, unknown>} */ (rawEntry)
const keys = Object.keys(entry)
const missing = ENTRY_REQUIRED_KEYS.filter((k) => !(k in entry))
const extra = keys.filter((k) => !ENTRY_ALLOWED_KEYS.includes(k))
const missing = ENTRY_KEYS.filter((k) => !(k in entry))
const extra = keys.filter((k) => !ENTRY_KEYS.includes(k))
if (missing.length) errors.push(`${label}: missing key(s) ${missing.join(', ')}`)
if (extra.length) errors.push(`${label}: unexpected key(s) ${extra.join(', ')}`)
@ -204,11 +169,11 @@ function validateShape(data) {
if (!Array.isArray(entry.items) || entry.items.length === 0 || entry.items.some((it) => typeof it !== 'string' || it.trim() === '')) {
errors.push(`${label}: "items" must be a non-empty array of non-empty strings`)
}
if (typeof entry.url !== 'string' || !/^https:\/\/\S+$/.test(entry.url)) {
errors.push(`${label}: "url" must be an https permalink — never null; HQ's parser rejects the whole feed`)
if (!(entry.url === null || typeof entry.url === 'string')) {
errors.push(`${label}: "url" must be a string or null`)
}
if ('thumb' in entry && !(entry.thumb === null || typeof entry.thumb === 'string')) {
errors.push(`${label}: "thumb" must be a string or null when present`)
if (!(entry.thumb === null || typeof entry.thumb === 'string')) {
errors.push(`${label}: "thumb" must be a string or null`)
}
})
@ -275,8 +240,8 @@ function extractChangelogBullets(changelog, version) {
/**
* Derive a wall entry from a CHANGELOG.md.
* @param {{product: string, version: string, date: string, changelogPath: string, url?: string, thumb?: string | null}} opts
* @returns {{version: string, date: string, headline: string, items: string[], url: string, thumb: string | null}}
* @param {{product: string, version: string, date: string, changelogPath: string, url?: string | null, thumb?: string | null}} opts
* @returns {{version: string, date: string, headline: string, items: string[], url: string | null, thumb: string | null}}
*/
function deriveEntry({ product, version, date, changelogPath, url, thumb }) {
if (!parseSemver(version)) fail(`--version "${version}" is not a semver string`)
@ -289,11 +254,7 @@ function deriveEntry({ product, version, date, changelogPath, url, thumb }) {
const items = extractChangelogBullets(changelog, version)
const headline = items[0]
const pattern = RELEASE_URL_PATTERNS[product]
if (url === undefined && pattern === undefined) {
throw new Error(`wall-entry: no permalink pattern for product "${product}" — add one to RELEASE_URL_PATTERNS or pass --url; entries never carry url: null`)
}
const resolvedUrl = url !== undefined ? url : pattern(version)
const resolvedUrl = url !== undefined ? url : (RELEASE_URL_PATTERNS[product]?.(version) ?? null)
const resolvedThumb = thumb !== undefined ? thumb : null
return { version, date, headline, items, url: resolvedUrl, thumb: resolvedThumb }
@ -305,140 +266,43 @@ function deriveEntry({ product, version, date, changelogPath, url, thumb }) {
* @returns {Record<string, any>}
*/
function loadWallFile(filePath) {
if (!existsSync(filePath)) fail(`"${filePath}" does not exist`)
if (!existsSync(filePath)) fail(`--file "${filePath}" does not exist`)
/** @type {unknown} */
let data
try {
data = JSON.parse(readFileSync(filePath, 'utf8'))
} catch (err) {
fail(`"${filePath}" is not valid JSON: ${/** @type {Error} */ (err).message}`)
fail(`--file "${filePath}" is not valid JSON: ${/** @type {Error} */ (err).message}`)
}
const errors = validateShape(data)
if (errors.length) {
fail(`"${filePath}" fails shape validation \n ${errors.join('\n ')}`)
fail(`--file "${filePath}" fails shape validation before any write \n ${errors.join('\n ')}`)
}
return /** @type {Record<string, any>} */ (data)
}
/**
* Run a git command, throwing an Error whose message is git's own stderr
* (trimmed) on failure every caller wraps this to name the cure.
* @param {string[]} args
* @param {string} cwd
* @returns {string} stdout, trimmed.
* Prepend `entry` to the wall file at `filePath`, refusing by name if the
* version is already present, validating before and after, and writing the
* file back with the repo's exact formatting (2-space JSON, trailing newline).
* @param {{version: string, date: string, headline: string, items: string[], url: string | null, thumb: string | null}} entry
* @param {string} filePath
* @param {string | undefined} expectedProduct
*/
function git(args, cwd) {
try {
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
} catch (err) {
const stderr = /** @type {any} */ (err).stderr
const message = (typeof stderr === 'string' && stderr.trim()) || /** @type {Error} */ (err).message
throw new Error(message)
}
}
/**
* Resolve the git identity for the wall commit from the repository the rail
* is actually running in the developer's own checkout (`process.cwd()`;
* `release.sh` invokes this script from the repo root with no `cd`), via
* git's normal config precedence (repo-local, then global, then system).
* Never guessed and never left to git's own "who are you?" prompt: a host
* with no configured identity anywhere (a bare CI box, say) must refuse
* loudly rather than have git manufacture a placeholder identity or hang.
* @returns {{name: string, email: string}}
*/
function resolveWallCommitIdentity() {
const repo = process.cwd()
let name = ''
let email = ''
try {
name = git(['config', 'user.name'], repo)
} catch {
name = ''
}
try {
email = git(['config', 'user.email'], repo)
} catch {
email = ''
}
if (!name || !email) {
fail('no git identity for the wall commit — set user.name/user.email')
}
return { name, email }
}
/**
* Ensure a clean, up-to-date local clone of the releases repo at
* `cacheDir`, checked out on `main` cloning fresh if `cacheDir` has no
* `.git`, otherwise fetching and hard-resetting onto `origin/main` (so a
* stray local commit or edit left by a previous failed run can never leak
* into the next one).
* @param {string} remote
* @param {string} cacheDir
*/
function ensureReleasesClone(remote, cacheDir) {
if (existsSync(join(cacheDir, '.git'))) {
try {
git(['remote', 'set-url', 'origin', remote], cacheDir)
git(['fetch', '--prune', 'origin'], cacheDir)
git(['checkout', 'main'], cacheDir)
git(['reset', '--hard', 'origin/main'], cacheDir)
git(['clean', '-fd'], cacheDir)
} catch (err) {
fail(
`cannot refresh the cached releases checkout at "${cacheDir}" from "${remote}" — ${/** @type {Error} */ (err).message}\n` +
` cure: delete "${cacheDir}" and re-run so it re-clones from scratch, or confirm SSH access with "ssh -T git@source.soulcraft.com"`,
)
}
return
}
mkdirSync(dirname(cacheDir), { recursive: true })
try {
git(['clone', remote, cacheDir], dirname(cacheDir))
} catch (err) {
fail(
`cannot clone "${remote}" — ${/** @type {Error} */ (err).message}\n` +
` cure: confirm SSH access with "ssh -T git@source.soulcraft.com" and that the soulcraftlabs/releases repo exists yet`,
)
}
try {
git(['checkout', 'main'], cacheDir)
} catch (err) {
fail(
`cloned "${remote}" into "${cacheDir}" but could not check out "main" — ${/** @type {Error} */ (err).message}\n` +
` cure: confirm the releases repo's default branch is named "main"`,
)
}
}
/**
* Prepend `entry` to the wall at `<cacheDir>/<product>.json`, replacing any
* existing entry for the same version (idempotent re-runs), validating
* before and after, committing, and pushing or refusing loudly, naming
* the cure, at whichever step fails.
* @param {{version: string, date: string, headline: string, items: string[], url: string, thumb: string | null}} entry
* @param {string} product
* @param {string} remote
* @param {string} cacheDir
*/
function publishEntry(entry, product, remote, cacheDir) {
ensureReleasesClone(remote, cacheDir)
const filePath = join(cacheDir, `${product}.json`)
if (!existsSync(filePath)) {
fail(
`"${filePath}" does not exist in the releases repo — cure: seed "${product}.json" at the repo root first (it must exist before any release rail can prepend to it)`,
)
}
function applyEntry(entry, filePath, expectedProduct) {
const wall = loadWallFile(filePath)
if (wall.product !== product) {
fail(`"${filePath}" has product "${wall.product}", but --product "${product}" was given — refusing a cross-product write`)
if (expectedProduct && wall.product !== expectedProduct) {
fail(
`--file "${filePath}" has product "${wall.product}", but --product "${expectedProduct}" was given — refusing a cross-product write`,
)
}
const replacing = wall.entries.some((e) => e.version === entry.version)
wall.entries = [entry, ...wall.entries.filter((e) => e.version !== entry.version)]
if (wall.entries.some((e) => e.version === entry.version)) {
fail(`refusing — version ${entry.version} is already present in "${filePath}"`)
}
wall.entries = [entry, ...wall.entries]
const postErrors = validateShape(wall)
if (postErrors.length) {
@ -446,53 +310,22 @@ function publishEntry(entry, product, remote, cacheDir) {
}
writeFileSync(filePath, JSON.stringify(wall, null, 2) + '\n', 'utf8')
const status = git(['status', '--porcelain', '--', `${product}.json`], cacheDir)
if (status === '') {
console.log(`wall-entry: "${product}.json" already carries an identical entry for ${entry.version} — nothing to commit or push`)
return
}
const identity = resolveWallCommitIdentity()
try {
git(['add', `${product}.json`], cacheDir)
git(
['-c', `user.name=${identity.name}`, '-c', `user.email=${identity.email}`, 'commit', '-m', `chore(wall): ${product} ${entry.version}`],
cacheDir,
)
} catch (err) {
fail(`cannot commit the wall entry in "${cacheDir}" — ${/** @type {Error} */ (err).message}\n cure: inspect "${cacheDir}" by hand and re-run once its git state is clean`)
}
try {
git(['push', 'origin', 'main'], cacheDir)
} catch (err) {
fail(
`push to "${remote}" failed (likely a non-fast-forward — another release landed on main first) — ${/** @type {Error} */ (err).message}\n` +
` cure: re-run this release step; it re-fetches and resets onto the latest origin/main before retrying`,
)
}
const sha = git(['rev-parse', 'HEAD'], cacheDir)
console.log(
`wall-entry: ${replacing ? 'replaced' : 'wrote'} v${entry.version} in "${product}.json" (${wall.entries.length} entries, newest first) — pushed ${sha} to ${remote} main`,
)
console.log(`wall-entry: wrote v${entry.version} to "${filePath}" (${wall.entries.length} entries, newest first)`)
}
function main() {
const args = parseArgs(process.argv.slice(2))
if (args.check) {
const filePath = /** @type {string | undefined} */ (args.file)
if (!filePath) fail('--check needs --file <path>')
const filePath = /** @type {string | undefined} */ (args.file) ??
(typeof args.product === 'string' ? `releases/${args.product}.json` : undefined)
if (!filePath) fail('--check needs --file <path> (or --product <name> to default to releases/<name>.json)')
const wall = loadWallFile(/** @type {string} */ (filePath))
console.log(`wall-entry --check: "${filePath}" OK — product "${wall.product}", ${wall.entries.length} entries, newest-first, no duplicates`)
process.exit(0)
}
// Generate mode (default, also covers --dry-run): --product, --version,
// --date, --from-changelog required.
// Generate mode (default): --product, --version, --date, --from-changelog required.
const product = /** @type {string | undefined} */ (args.product)
const version = /** @type {string | undefined} */ (args.version)
const date = /** @type {string | undefined} */ (args.date)
@ -507,8 +340,8 @@ function main() {
fail(
`missing required flag(s): ${missing.join(', ')}\n` +
'Usage:\n' +
' wall-entry.mjs --product <p> --version <v> --date <YYYY-MM-DD> --from-changelog <CHANGELOG.md> [--dry-run]\n' +
' wall-entry.mjs --check --file <path/to/product.json>',
' wall-entry.mjs --product <p> --version <v> --date <YYYY-MM-DD> --from-changelog <CHANGELOG.md> [--file releases/<p>.json]\n' +
' wall-entry.mjs --check --file <releases/p.json>',
)
}
@ -524,16 +357,8 @@ function main() {
thumb: thumbArg,
})
const remote = /** @type {string} */ (args.remote ?? process.env.WALL_ENTRY_RELEASES_REMOTE ?? DEFAULT_REMOTE)
const cacheDir = /** @type {string} */ (args['cache-dir'] ?? process.env.WALL_ENTRY_RELEASES_CACHE_DIR ?? defaultCacheDir())
if (args['dry-run']) {
console.log(`wall-entry --dry-run: would write to "${join(cacheDir, `${product}.json`)}" in ${remote} (main), pushed as "chore(wall): ${product} ${version}"`)
console.log(JSON.stringify(entry, null, 2))
process.exit(0)
}
publishEntry(entry, /** @type {string} */ (product), remote, cacheDir)
const filePath = /** @type {string} */ (args.file ?? `releases/${product}.json`)
applyEntry(entry, filePath, /** @type {string} */ (product))
}
main()

View file

@ -544,23 +544,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* and narrate forever. Reset by {@link deregisterShutdownHooksIfIdle}. */
private static beforeExitNarrated = false
/** True for the entire duration of ONE `closeOnShutdown()` run (the
* signal-path handler in {@link registerShutdownHooks}) from before it
* starts closing instances until after it has decided whether to exit.
* THE RACE THIS CLOSES: closing the LAST live instance calls
* `close()` `deregisterShutdownHooksIfIdle()` synchronously, which
* removes `Brainy.sigtermListener` from `process` while `closeOnShutdown`
* (that very listener's OWN still-running invocation) hasn't yet reached
* `exitIfSoleShutdownOwner()`'s `process.exit(0)`. In that window Node has
* NO registered SIGTERM listener, so a second/concurrent delivery of the
* same signal (a raced re-send, common on a loaded host) falls through to
* Node's default disposition and kills the process outright the
* clean-shutdown work already finished, but the process never reports the
* 0 it earned. `deregisterShutdownHooksIfIdle()` checks this flag and
* defers; `closeOnShutdown()`'s `finally` re-runs the deregistration check
* once it is done, so the listener never actually leaks past its use. */
private static shutdownSignalHandlerActive = false
/** Poll cadence (ms) for the migration LOCK when a provider exposes no
* event-driven `whenMigrationComplete()` signal. See {@link awaitMigrationLock}. */
private static readonly MIGRATION_POLL_INTERVAL_MS = 250
@ -2213,29 +2196,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
*/
const closeOnShutdown = async () => {
console.log('Shutdown signal received - flushing pending data...')
// HOLD THE LISTENER FOR THE WHOLE RUN. Closing the LAST live instance
// below calls close() → deregisterShutdownHooksIfIdle(), which removes
// Brainy's own SIGTERM/SIGINT listeners from `process` — synchronously,
// before THIS invocation has reached exitIfSoleShutdownOwner()'s
// process.exit(0). Left alone, that opens a window with no registered
// listener for the signal at all, so a second/concurrent delivery of
// the same signal (a raced re-send — not rare on a loaded host) falls
// through to Node's default disposition and kills the process outright
// AFTER the clean-shutdown work already finished, reporting a signal
// kill instead of the 0 the shutdown earned. Setting this flag makes
// deregisterShutdownHooksIfIdle() defer; the `finally` below re-checks
// it once this run is fully done — closeOnShutdown, not a nested
// close(), owns exactly when the listener actually comes off.
Brainy.shutdownSignalHandlerActive = true
try {
// DEFER ONE MACROTASK. A host application registers its own listener
// on the same signal, and Node runs listeners in registration order —
// ours is usually first, because the brain was opened before the
// host wired its shutdown. Yielding once lets every other listener
// for this signal run its synchronous prologue, so a host that calls
// close() gets to be the owner. It is only a courtesy, never the
// safety: close()'s own single-flight gate is what makes a lost race
// harmless.
// DEFER ONE MACROTASK. A host application registers its own listener on
// the same signal, and Node runs listeners in registration order — ours
// is usually first, because the brain was opened before the host wired
// its shutdown. Yielding once lets every other listener for this signal
// run its synchronous prologue, so a host that calls close() gets to be
// the owner. It is only a courtesy, never the safety: close()'s own
// single-flight gate is what makes a lost race harmless.
await new Promise<void>((resolve) => setImmediate(resolve))
let closedCount = 0
@ -2275,13 +2242,6 @@ export class Brainy<T = any> implements BrainyInterface<T> {
`their writer locks were released, but their next open will run crash recovery.`
)
}
} finally {
// Release the hold and run the deferred check ourselves — the last
// close() above may have found the flag set and skipped its own
// deregistration, so nobody else will do this if we don't.
Brainy.shutdownSignalHandlerActive = false
Brainy.deregisterShutdownHooksIfIdle()
}
}
/**
@ -2444,17 +2404,9 @@ export class Brainy<T = any> implements BrainyInterface<T> {
* script that closed every brain exits on its own a library must never
* keep its host process alive. Re-initializing later re-registers them
* (the `shutdownHooksRegisteredGlobally` flag resets here).
*
* Deferred (not skipped {@link closeOnShutdown}'s `finally` always
* re-checks) while a signal-path shutdown is actively running: that
* handler's OWN still-in-flight invocation is `Brainy.sigtermListener`, and
* removing it out from under itself which closing the LAST instance here
* would otherwise do, synchronously, mid-run would leave `process` with
* no listener for the signal for the remainder of that run. See
* {@link shutdownSignalHandlerActive}'s doc for the exact race this closes.
*/
private static deregisterShutdownHooksIfIdle(): void {
if (Brainy.instances.length > 0 || !Brainy.shutdownHooksRegisteredGlobally || Brainy.shutdownSignalHandlerActive) {
if (Brainy.instances.length > 0 || !Brainy.shutdownHooksRegisteredGlobally) {
return
}
if (Brainy.sigtermListener) process.off('SIGTERM', Brainy.sigtermListener)

View file

@ -34,10 +34,6 @@ describe('API Parameter Validation', () => {
})
})
afterAll(async () => {
await brain.close()
})
it('should use "where" parameter for metadata filtering', async () => {
const results = await brain.find({
where: { category: 'test-category' },

View file

@ -7,7 +7,7 @@
* - Backward compatibility preserved
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js'
@ -19,10 +19,6 @@ describe('Entity Confidence & Weight Exposure', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('Entity interface', () => {
it('should expose confidence when adding entity with confidence', async () => {
const id = await brain.add({

View file

@ -19,7 +19,7 @@
* index-served (a body field, or a bucketed timestamp), exactly the owing rows
* are read and the rest are still served from the index.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { describe, it, expect, beforeAll, vi } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType } from '../../src/types/graphTypes'
import { generateTestVector } from '../helpers/test-factory'
@ -59,10 +59,6 @@ describe('find/get({ fields }) — projection', () => {
await brain.flush()
})
afterAll(async () => {
await brain.close()
})
/** Count canonical record reads for one call. */
const countingReads = async <R>(body: () => Promise<R>): Promise<{ out: R; reads: number }> => {
const spy = vi.spyOn(brain as any, 'batchGet')

View file

@ -31,7 +31,7 @@
* never the legs. And the text leg is asked about the universe's ids only
* what it marshals is bounded by the universe, not by the store.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { describe, it, expect, beforeAll, vi } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType, VerbType } from '../../src/types/graphTypes'
import { rankIndicesByScore, reorderByIndices } from '../../src/utils/resultRanking'
@ -287,10 +287,6 @@ describe('hybrid find: filter before hydrate — the answer is unchanged', () =>
expect(typeof (brain as any).metadataIndex.getIdSetForFilter).not.toBe('function')
})
afterAll(async () => {
await brain.close()
})
it('the fixture does not truncate the text leg — the universe covers every text match', async () => {
const index = (brain as any).metadataIndex
const textMatches = await index.getIdsForTextQuery(QUERY)
@ -557,10 +553,6 @@ describe('hybrid find: the text leg ranks inside the filter, not around it', ()
}
})
afterAll(async () => {
await brain.close()
})
it('the old order let the filter consume the whole text leg', async () => {
const index = (brain as any).metadataIndex
const universe: string[] = await (brain as any).filterIdsBelted({ lane: 'alpha' })

View file

@ -9,7 +9,7 @@
* it). Now the anchor is fetched with its vector, and an anchor without one
* refuses by name instead of failing inside the index.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { describe, it, expect, beforeAll } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType } from '../../src/types/graphTypes'
import { v5 } from '../../src/universal/uuid'
@ -28,10 +28,6 @@ describe('find({ near }) uses the anchor vector', () => {
await brain.add({ id: 'far', data: 'far row', type: NounType.Thing, vector: generateTestVector() })
})
afterAll(async () => {
await brain.close()
})
it('returns the anchor\'s neighbours by its own vector', async () => {
const results = await brain.find({ near: { id: 'anchor' }, limit: 3 })
expect(results.length).toBeGreaterThan(0)

View file

@ -40,7 +40,7 @@
* the covering is ASSERTED from the leg's own output rather than assumed. This
* pin is about ordering, and it says nothing about recall.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { describe, it, expect, beforeAll } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType, VerbType } from '../../src/types/graphTypes'
import { resolveEntityId } from '../../src/utils/idNormalization'
@ -107,10 +107,6 @@ describe('find(): orderBy is the order on every path', () => {
}
})
afterAll(async () => {
await brain.close()
})
it('the fixture: the hybrid candidate set covers the whole filter universe', async () => {
const universe: string[] = await (brain as any).filterIdsBelted({ lane: 'alpha' })
expect(universe).toHaveLength(ROWS)

View file

@ -23,7 +23,7 @@
* against the adjacency before it is believed, so a not-serving graph refuses
* loudly instead of answering `[]` as truth.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
import { describe, it, expect, beforeAll, vi } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType, VerbType } from '../../src/types/graphTypes'
import { generateTestVector } from '../helpers/test-factory'
@ -56,10 +56,6 @@ describe('find(): the optional planner door', () => {
}
})
afterAll(async () => {
await brain.close()
})
/** Install a planner door for one call, then remove it. */
const withDoor = async <T>(
door: (...a: any[]) => Promise<any>,

View file

@ -48,7 +48,6 @@ describe('Unified Find() Integration Tests', () => {
afterAll(async () => {
await cleanup.cleanup()
await brain.close()
brain = null as any
})

View file

@ -18,7 +18,7 @@
* All entities carry explicit 384-dim vectors so no test invokes the embedder.
*/
import { describe, it, expect, afterEach } from 'vitest'
import { describe, it, expect } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType, VerbType } from '../../src/types/graphTypes.js'
import { v5, v7, isUUID } from '../../src/universal/uuid.js'
@ -37,15 +37,8 @@ async function makeBrain(): Promise<Brainy> {
}
describe('id normalization — transparent string-key round-trips', () => {
const opened: Brainy[] = []
afterEach(async () => {
for (const b of opened.splice(0)) await b.close().catch(() => {})
})
it('1. add() returns v5(key); get(key) and get(returnedId) both resolve; _originalId preserved', async () => {
const brain = await makeBrain()
opened.push(brain)
const returnedId = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
@ -67,7 +60,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('2. relate() by string keys; related(key) and related({from:key}) return the edge to v5(toKey)', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
await brain.add({ id: 'doc-1', vector: vec(2), type: NounType.Document })
@ -93,7 +85,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('3. update() by string key reflects on get(key)', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { role: 'admin' } })
await brain.update({ id: 'user-1', metadata: { role: 'owner' } })
@ -107,7 +98,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('4. remove() by string key deletes; get(key) is null', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
expect(await brain.get('user-1')).not.toBeNull()
@ -120,7 +110,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('5. find({ connected: { from: key } }) resolves the anchor key', async () => {
const brain = await makeBrain()
opened.push(brain)
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
await brain.add({ id: 'doc-1', vector: vec(2), type: NounType.Document })
@ -133,7 +122,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('6. transact() add+relate by string keys round-trips with consistent canonical ids', async () => {
const brain = await makeBrain()
opened.push(brain)
// Seed user-1 so the relate op has a target to point at.
await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person })
@ -161,7 +149,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('7. addMany() + relateMany() with string ids round-trip', async () => {
const brain = await makeBrain()
opened.push(brain)
const added = await brain.addMany({
items: [
@ -188,7 +175,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('8. determinism: same key maps to same UUID — two adds upsert ONE entity, not two', async () => {
const brain = await makeBrain()
opened.push(brain)
const id1 = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { n: 1 } })
const id2 = await brain.add({ id: 'user-1', vector: vec(1), type: NounType.Person, metadata: { n: 2 } })
@ -207,7 +193,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('9. valid-UUID passthrough: a real UUID is kept verbatim with NO _originalId', async () => {
const brain = await makeBrain()
opened.push(brain)
const realUuid = v7()
const returnedId = await brain.add({ id: realUuid, vector: vec(5), type: NounType.Thing })
@ -222,7 +207,6 @@ describe('id normalization — transparent string-key round-trips', () => {
it('10. no-id add() mints a v7; newId() mints a v7', async () => {
const brain = await makeBrain()
opened.push(brain)
const autoId = await brain.add({ vector: vec(6), type: NounType.Thing })
expect(isUUID(autoId)).toBe(true)

View file

@ -107,11 +107,7 @@ describe('Multi-process safety + read-only mode', () => {
const blocked = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir } })
await expect(blocked.init()).rejects.toThrow(/another writer holds/i)
// A rejected init() still registered `blocked` in Brainy's global
// instance registry (the constructor does that unconditionally) — close()
// is safe to call even though init() never completed, and is what
// deregisters it (and, once idle, the process-level shutdown hooks).
await blocked.close().catch(() => {})
// Don't track `blocked` for afterEach cleanup since init failed.
})
it('takes over a STALE foreign lock (dead PID + old heartbeat) and claims atomically', async () => {
@ -155,7 +151,6 @@ describe('Multi-process safety + read-only mode', () => {
const err: any = await blocked.init().catch((e) => e)
expect(err.code).toBe('BRAINY_WRITER_LOCKED')
expect(err.lockInfo?.pid).toBe(otherPid)
await blocked.close().catch(() => {})
})
it('release drains an in-flight heartbeat — no phantom lock re-created after unlink', async () => {

View file

@ -30,7 +30,6 @@ describe('related() with a verb-type array returns every requested type', () =>
})
afterAll(async () => {
await brain.close()
brain = null as any
})

View file

@ -59,8 +59,7 @@ describe('Relationship Intelligence', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
afterEach(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true })
}

View file

@ -9,7 +9,7 @@
* - addMany({ ifAbsent: true }) applies the flag to every item
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { RevisionConflictError } from '../../src/transaction/RevisionConflictError.js'
import { NounType } from '../../src/types/graphTypes.js'
@ -22,10 +22,6 @@ describe('7.31.0 — _rev CAS + ifAbsent', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('_rev initialization + surface', () => {
it('initializes _rev to 1 on add()', async () => {
const id = await brain.add({ data: 'hello', type: NounType.Document })

View file

@ -81,7 +81,6 @@ describe('repairContainment: batched pass 2', () => {
})
afterAll(async () => {
await brain.close()
brain = null as any
})

View file

@ -9,7 +9,6 @@ import * as XLSX from 'xlsx'
describe('VFS Debug', () => {
it('minimal VFS writeFile test', async () => {
const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
try {
await brain.init()
console.log('✅ Brain initialized')
@ -78,8 +77,5 @@ describe('VFS Debug', () => {
// THE REAL TEST: Can we query VFS?
expect(children.length).toBeGreaterThan(0)
expect(rootContents.length).toBeGreaterThan(0)
} finally {
await brain.close()
}
})
})

View file

@ -61,7 +61,6 @@ describe('writer-lock fencing', () => {
// Old rule: heartbeat-age eviction → silent takeover → split brain.
// New rule: live PID = live writer; the second opener throws typed.
const second = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false })
brains.push(second)
await expect(second.init()).rejects.toMatchObject({ code: 'BRAINY_WRITER_LOCKED' })
}, 120000)

View file

@ -17,7 +17,7 @@
* - Note limitations and edge cases
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { TypeAwareStorageAdapter } from '../../src/storage/adapters/typeAwareStorageAdapter.js'
import { FileSystemStorage } from '../../src/storage/adapters/fileSystemStorage.js'
@ -67,10 +67,6 @@ describe('TypeAware Performance Benchmarks', () => {
}
})
afterEach(async () => {
await brainMemory.close()
})
it('should measure type-based query performance', async () => {
// MEASURED: Query for one type (200 entities)
const start = performance.now()

View file

@ -5,7 +5,7 @@
* No mocks, no fakes, real implementation
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { NounType } from '../../src/types/graphTypes.js'
@ -21,10 +21,6 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('CRUD Operations', () => {
it('should create items with add', async () => {
const id = await brain.add({

View file

@ -19,19 +19,13 @@ import { prodLog } from '../../../src/utils/logger.js'
const UUID = (suffix: string): string => `00000000-0000-4000-8000-0000000000${suffix}`
describe('Finding 10 — degraded derived-index state is surfaced on reads', () => {
const opened: Brainy[] = []
beforeEach(() => {
process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true'
})
afterEach(async () => {
vi.restoreAllMocks()
for (const b of opened.splice(0)) await b.close().catch(() => {})
})
afterEach(() => vi.restoreAllMocks())
it('checkHealth() reports adopt-forward degraded ids as unhealthy', async () => {
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
;(brain as any)._indexDegradedIds.add(UUID('de'))
@ -43,7 +37,6 @@ describe('Finding 10 — degraded derived-index state is surfaced on reads', ()
it('find()/get() warn loudly while degraded, ONCE, then repairIndex() clears it', async () => {
const warn = vi.spyOn(prodLog, 'warn').mockImplementation(() => {})
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
await brain.add({ id: UUID('a1'), data: 'x', type: NounType.Document })
;(brain as any)._indexRebuildFailed = new Error('rebuild boom')
@ -66,7 +59,6 @@ describe('Finding 10 — degraded derived-index state is surfaced on reads', ()
it('persistSingleOp records receipt.degraded (widened return type, not dropped)', async () => {
const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false })
opened.push(brain)
await brain.init()
// Simulate a degraded receipt by wrapping the generation store's commitSingleOp.
const gs: any = (brain as any).generationStore

View file

@ -7,7 +7,7 @@
* soft-delete semantic: `field !== value` MUST include entities that have no
* such field at all.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { NounType } from '../../../src/types/graphTypes'
@ -26,10 +26,6 @@ describe('find() complement operators (ne / exists:false / missing:true)', () =>
ids.noField2 = await brain.add({ data: 'n2', type: NounType.Thing, metadata: { other: 2 } })
})
afterEach(async () => {
await brain.close()
})
it('ne returns everything except the matching value — INCLUDING entities without the field', async () => {
const rows = await brain.find({ where: { status: { ne: 'active' } }, limit: 100 })
const got = new Set(rows.map((r) => r.id))

View file

@ -12,7 +12,7 @@
* returns an id whose record matches NEITHER the type nor the where filter) and
* assert the phantom is dropped while the genuine matches survive.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { NounType } from '../../../src/types/graphTypes'
@ -48,10 +48,6 @@ describe('find() index-integrity guard (phantom row class)', () => {
})
})
afterEach(async () => {
await brain.close()
})
it('healthy index: the discriminant query returns only the staff Person', async () => {
const rows = await brain.find({ type: NounType.Person, where: { entityType: 'staff' }, limit: 100 })
expect(rows.map((r) => r.id)).toEqual([staffId])

View file

@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { createAddParams } from '../../helpers/test-factory'
import { NounType } from '../../../src/types/graphTypes'
@ -13,10 +13,6 @@ describe('Brainy.find()', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('success paths', () => {
it('should find entities by text query', async () => {
// Arrange

View file

@ -18,7 +18,7 @@
* exercised by cor's combined matrix); they inject probe/spy hooks onto the live JS
* metadata index, which has neither method by default.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { NounType } from '../../../src/types/graphTypes'
@ -34,10 +34,6 @@ describe('metadata-provider contract wiring (getIdsForFilter opts)', () => {
mi = (brain as any).metadataIndex
})
afterEach(async () => {
await brain.close()
})
it('RETIRED: a read never calls probeConsistency() / self-heals via detectAndRepairCorruption — that is the read-triggered dark rebuild the health-gate law forbids', async () => {
let probes = 0
let repairs = 0

View file

@ -8,7 +8,7 @@
* gate that hung getStats / readdir / readFile behind an unrelated family's
* migration until the wait timed out.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy.js'
import { MigrationInProgressError } from '../../../src/errors/brainyError.js'
@ -38,19 +38,12 @@ const jam = (provider: unknown) => {
}
describe('migration LOCK is family-scoped', () => {
const opened: Brainy[] = []
beforeEach(() => {
process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true'
})
afterEach(async () => {
for (const b of opened.splice(0)) await b.close().catch(() => {})
})
it('a stuck VECTOR migration does not block canonical or graph/metadata reads', async () => {
const brain = await seed()
opened.push(brain)
const childId = (
(await brain.vfs.readdir('/notes', { withFileTypes: true })) as Array<{ entityId: string }>
)[0].entityId
@ -67,7 +60,6 @@ describe('migration LOCK is family-scoped', () => {
it('a stuck VECTOR migration STILL blocks a read that needs the vector family', async () => {
const brain = await seed()
opened.push(brain)
jam((brain as any).index)
// A semantic query consults the vector index — it must wait, and (bounded by
@ -78,7 +70,6 @@ describe('migration LOCK is family-scoped', () => {
it('a stuck GRAPH migration blocks traversal but not vector/canonical reads', async () => {
const brain = await seed()
opened.push(brain)
const childId = (
(await brain.vfs.readdir('/notes', { withFileTypes: true })) as Array<{ entityId: string }>
)[0].entityId
@ -96,7 +87,6 @@ describe('migration LOCK is family-scoped', () => {
it('with no migration in flight, every read serves (the fast path is a no-op)', async () => {
const brain = await seed()
opened.push(brain)
await expect(brain.getStats()).resolves.toBeDefined()
await expect(brain.find({ query: 'doc' })).resolves.toBeDefined()
await expect(brain.vfs.readdir('/notes')).resolves.toHaveLength(1)

View file

@ -18,7 +18,7 @@ describe('Duplicate Check Optimization', () => {
})
afterEach(async () => {
await brain.close()
// Cleanup is automatic with memory storage
})
it('should detect duplicate relationships using GraphAdjacencyIndex', async () => {

View file

@ -7,7 +7,7 @@
* _indexRebuildFailed / _indexDegradedIds degraded states (mirroring
* validateIndexConsistency / checkHealth).
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType } from '../../src/index.js'
describe('getIndexStatus honest readiness (Finding 9)', () => {
@ -20,10 +20,6 @@ describe('getIndexStatus honest readiness (Finding 9)', () => {
await brain.flush()
})
afterEach(async () => {
await brain.close()
})
it('a not-ready provider makes populated honest (false) and exposes ready:false', async () => {
brain.index.isReady = () => false // count present, serving structure NOT loaded
const status = await brain.getIndexStatus()

View file

@ -8,7 +8,7 @@
* scan; and a one-shot probe self-heals a no-isReady provider whose adjacency
* did not cold-load.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType, VerbType } from '../../../src/index.js'
describe('graph fast-path honest readiness (Finding 2)', () => {
@ -33,10 +33,6 @@ describe('graph fast-path honest readiness (Finding 2)', () => {
await storage.getVerbsBySource(a)
})
afterEach(async () => {
await brain.close()
})
it('not-ready provider → shard scan returns the REAL edges, not a silent []', async () => {
const gi = storage.graphIndex
// Simulate a cold native provider: count/manifest loaded (isInitialized) but

View file

@ -15,7 +15,7 @@
* The 8.0 JS index cold-loads correctly, so we simulate the cold native failure
* mode by intercepting the provider's getIdsForFilter/rebuild.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType, MetadataIndexNotReadyError } from '../../src/index.js'
const V = () => Array.from({ length: 384 }, (_, i) => Math.sin(i * 0.1) + 0.001)
@ -31,10 +31,6 @@ describe('Metadata cold-read guard (#venue silent-[])', () => {
await brain.flush()
})
afterEach(async () => {
await brain.close()
})
it('warm brain: filtered find is correct and the guard does not rebuild', async () => {
const mi = brain.metadataIndex
let rebuilds = 0

View file

@ -18,7 +18,7 @@
* the production feature-detection reads it.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType, MigrationInProgressError } from '../../src/index.js'
import { GraphAdjacencyIndex } from '../../src/graph/graphAdjacencyIndex.js'
@ -39,12 +39,6 @@ describe('Migration LOCK (#18) — coordinated 7.x→8.0 auto-upgrade', () => {
await brain.init()
})
afterEach(async () => {
// The "close() is not gated" test already closes `brain` itself as its
// own assertion — closing an already-closed brain is a safe no-op here.
await brain.close().catch(() => {})
})
it('does not gate operations when no provider is migrating (fast path)', async () => {
const id = await brain.add({ data: 'hello', type: NounType.Concept })
expect(id).toBeTruthy()
@ -136,9 +130,6 @@ describe('Migration LOCK (#18) — coordinated 7.x→8.0 auto-upgrade', () => {
expect(e).toBeInstanceOf(MigrationInProgressError)
expect(e.retryable).toBe(true)
expect(typeof e.elapsedMs).toBe('number')
} finally {
// close() is proven not-gated by the test below — safe even mid-migration.
await shortBrain.close()
}
})

View file

@ -13,11 +13,10 @@ describe('EmbeddingSignal', () => {
signal = new EmbeddingSignal(brain)
})
afterEach(async () => {
afterEach(() => {
signal.clearCache()
signal.clearHistory()
signal.resetStats()
await brain.close()
})
describe('initialization', () => {

View file

@ -89,14 +89,12 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
})
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/installed but failed to load/)
await brain.close().catch(() => {})
})
it('installed but not a valid plugin (missing activate) → init() throws', async () => {
stubImport(async () => ({ default: { name: '@soulcraft/cor' } })) // no activate()
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/not a valid Brainy plugin/)
await brain.close().catch(() => {})
})
it('installed but activation fails → init() throws (activateAll posture applies)', async () => {
@ -110,7 +108,6 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
}))
const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
await expect(brain.init()).rejects.toThrow(/failed to activate/)
await brain.close().catch(() => {})
})
it('plugins: [] and plugins: false → no probe at all (explicit opt-out)', async () => {
@ -135,6 +132,5 @@ describe('Guarded plugin auto-detection (plugins: undefined)', () => {
silent: true
})
await expect(brain.init()).rejects.toThrow(/listed in config\.plugins but could not be loaded/)
await brain.close().catch(() => {})
})
})

View file

@ -143,6 +143,5 @@ describe('version coupling at init() — no silent fallback', () => {
plugins: ['@soulcraft/this-package-does-not-exist-xyz']
})
await expect(brain.init()).rejects.toThrow(/could not be loaded|config\.plugins/)
await brain.close().catch(() => {})
})
})

View file

@ -298,10 +298,9 @@ describe('Brainy plugin integration', () => {
// must surface as a failed init(), NOT a silent degrade to the default
// engine (the version-coupling guard; see plugin-version-coupling.test.ts).
await expect(brain.init()).rejects.toThrow(/failed to activate|native module not found/)
await brain.close().catch(() => {})
})
it('should use() return this for chaining', async () => {
it('should use() return this for chaining', () => {
const plugin: BrainyPlugin = {
name: 'chain-test',
activate: async () => true
@ -310,8 +309,5 @@ describe('Brainy plugin integration', () => {
const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
const result = brain.use(plugin)
expect(result).toBe(brain)
// Never init()'d — the constructor still registered it in Brainy's global
// instance registry, so it still needs a close() to deregister.
await brain.close().catch(() => {})
})
})

View file

@ -4,15 +4,12 @@
* The script's only real interface is its CLI (it has no importable
* exports by design one door, no parallel API to drift from it), so
* these tests spawn it exactly as scripts/release.sh does: as a child
* process, against a fixture CHANGELOG and a throwaway local bare repo
* standing in for git@source.soulcraft.com:soulcraftlabs/releases.git
* (--remote) plus a throwaway cache directory (--cache-dir) standing in
* for ~/.cache/soulcraft-releases never the real remote, never the
* real developer cache.
* process, against a temp copy of a wall file and a fixture CHANGELOG,
* never against the repo's real releases/*.json.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { execFileSync } from 'node:child_process'
import { mkdtempSync, rmSync, writeFileSync, readFileSync, chmodSync } from 'node:fs'
import { mkdtempSync, rmSync, writeFileSync, readFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@ -28,10 +25,6 @@ function run(args: string[], cwd: string): { status: number; stdout: string; std
}
}
function git(args: string[], cwd: string): string {
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8' }).trim()
}
const CHANGELOG_HEADER = '# Changelog\n\nAll notable changes, in this fixture.\n'
/** Build a CHANGELOG.md with one entry per [version, bullets[]] pair, newest first. */
@ -48,7 +41,11 @@ function buildChangelog(entries: Array<{ version: string; date: string; bullets:
}
function wallFile(product: string, entries: unknown[]): string {
return JSON.stringify({ product, entries }, null, 2) + '\n'
return JSON.stringify(
{ product, entries, history: 'Earlier releases are recorded in CHANGELOG.md in this repository.' },
null,
2,
) + '\n'
}
const BASE_ENTRY = {
@ -60,84 +57,31 @@ const BASE_ENTRY = {
thumb: null,
}
/** A throwaway bare repo standing in for the real soulcraftlabs/releases remote. */
function initBareRemote(): string {
const remoteDir = mkdtempSync(join(tmpdir(), 'wall-remote-'))
execFileSync('git', ['init', '--bare', '-b', 'main', remoteDir])
return remoteDir
}
/** Seed the bare remote with an initial <product>.json, via a throwaway clone. */
function seedRemote(remoteDir: string, product: string, entries: unknown[]): void {
const seedDir = mkdtempSync(join(tmpdir(), 'wall-seed-'))
execFileSync('git', ['clone', remoteDir, seedDir], { stdio: 'ignore' })
git(['config', 'user.email', 'seed@example.com'], seedDir)
git(['config', 'user.name', 'Seed'], seedDir)
writeFileSync(join(seedDir, `${product}.json`), wallFile(product, entries))
git(['add', `${product}.json`], seedDir)
git(['commit', '-m', 'seed'], seedDir)
git(['push', 'origin', 'main'], seedDir)
rmSync(seedDir, { recursive: true, force: true })
}
/** Read <product>.json back out of the bare remote's main tip, via a throwaway clone. */
function readRemote(remoteDir: string, product: string): any {
const readDir = mkdtempSync(join(tmpdir(), 'wall-read-'))
execFileSync('git', ['clone', remoteDir, readDir], { stdio: 'ignore' })
const data = JSON.parse(readFileSync(join(readDir, `${product}.json`), 'utf8'))
rmSync(readDir, { recursive: true, force: true })
return data
}
/** Reject every push stands in for any push failure (including a genuine
* non-fast-forward raced by a concurrent release rail), which this script
* treats identically: refuse loudly, name the cure, touch nothing further. */
function makeRemoteRejectPushes(remoteDir: string): void {
const hookPath = join(remoteDir, 'hooks', 'pre-receive')
writeFileSync(hookPath, '#!/bin/sh\necho "remote: simulated push rejection" >&2\nexit 1\n')
chmodSync(hookPath, 0o755)
}
let dir: string
let remoteDir: string
let cacheDir: string
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), 'wall-entry-test-'))
// wall-entry.mjs is run with this dir as its cwd, standing in for the real
// developer checkout it reads its commit identity from (process.cwd()) —
// give it a repo-local identity the same way seedRemote gives one to the
// seed clone, so the suite is deterministic on a host with no global git
// config (a bare CI box) as much as one with a developer's own.
execFileSync('git', ['init', '-q', dir])
git(['config', 'user.name', 'Wall Entry Test'], dir)
git(['config', 'user.email', 'wall-entry-test@example.com'], dir)
remoteDir = initBareRemote()
cacheDir = join(mkdtempSync(join(tmpdir(), 'wall-cache-')), 'soulcraft-releases')
})
afterEach(() => {
rmSync(dir, { recursive: true, force: true })
rmSync(remoteDir, { recursive: true, force: true })
rmSync(cacheDir, { recursive: true, force: true })
})
describe('wall-entry.mjs — generate + publish', () => {
it('derives headline from the first bullet and items from every bullet, hashes stripped, and pushes it to the remote', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY])
describe('wall-entry.mjs — generate + prepend', () => {
it('derives headline from the first bullet and items from every bullet, hashes stripped', () => {
writeFileSync(
join(dir, 'CHANGELOG.md'),
buildChangelog([{ version: '10.4.12', date: '2026-09-03', bullets: ['fix(wall): mechanize the entry', 'test(wall): pin the shape'] }]),
)
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', [BASE_ENTRY]))
const result = run(
['--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
['--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'],
dir,
)
expect(result.status).toBe(0)
expect(result.stdout).toMatch(/wrote v10\.4\.12.*pushed/i)
const wall = readRemote(remoteDir, 'open-brainy')
const wall = JSON.parse(readFileSync(join(dir, 'wall.json'), 'utf8'))
expect(wall.entries).toHaveLength(2)
expect(wall.entries[0]).toEqual({
version: '10.4.12',
@ -152,192 +96,68 @@ describe('wall-entry.mjs — generate + publish', () => {
})
it('prepends newest-first — the new entry lands at index 0 ahead of every existing one', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY, { ...BASE_ENTRY, version: '10.4.10' }])
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.5.0', date: '2026-09-03', bullets: ['feat: ten five'] }]))
writeFileSync(
join(dir, 'CHANGELOG.md'),
buildChangelog([{ version: '10.5.0', date: '2026-09-03', bullets: ['feat: ten five'] }]),
)
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', [BASE_ENTRY, { ...BASE_ENTRY, version: '10.4.10' }]))
run(['--product', 'open-brainy', '--version', '10.5.0', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir], dir)
run(['--product', 'open-brainy', '--version', '10.5.0', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'], dir)
const wall = readRemote(remoteDir, 'open-brainy')
const wall = JSON.parse(readFileSync(join(dir, 'wall.json'), 'utf8'))
expect(wall.entries.map((e: any) => e.version)).toEqual(['10.5.0', '10.4.11', '10.4.10'])
})
it('replaces an entry with the same version instead of duplicating it — idempotent re-runs', () => {
seedRemote(remoteDir, 'open-brainy', [
{ ...BASE_ENTRY, headline: 'stale headline, pre-fix' },
{ ...BASE_ENTRY, version: '10.4.10' },
])
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.11', date: '2026-09-02', bullets: ['fix: the corrected headline'] }]))
const result = run(
['--product', 'open-brainy', '--version', '10.4.11', '--date', '2026-09-02', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(0)
expect(result.stdout).toMatch(/replaced v10\.4\.11/i)
const wall = readRemote(remoteDir, 'open-brainy')
expect(wall.entries).toHaveLength(2) // not 3 — replaced, not duplicated
expect(wall.entries[0].version).toBe('10.4.11')
expect(wall.entries[0].headline).toBe('fix: the corrected headline')
expect(wall.entries[1].version).toBe('10.4.10')
})
it('a re-run with byte-identical content commits nothing and still succeeds', () => {
// headline always equals items[0] for a derived entry, so this fixture
// (unlike BASE_ENTRY, whose headline/items intentionally diverge for the
// shape-only tests below) has to keep the two in lockstep to ever roundtrip.
const stableEntry = { ...BASE_ENTRY, headline: 'A faster open.', items: ['A faster open.'] }
seedRemote(remoteDir, 'open-brainy', [stableEntry])
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.11', date: '2026-09-02', bullets: ['A faster open.'] }]))
const before = readRemote(remoteDir, 'open-brainy')
const result = run(
['--product', 'open-brainy', '--version', '10.4.11', '--date', '2026-09-02', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(0)
expect(result.stdout).toMatch(/nothing to commit/i)
expect(readRemote(remoteDir, 'open-brainy')).toEqual(before)
})
it('derives the public package-page permalink for the product engine (private repo, never null)', () => {
seedRemote(remoteDir, 'brainy', [{ ...BASE_ENTRY, version: '11.0.5', url: 'https://source.soulcraft.com/soulcraft/-/packages/npm/@soulcraft%2Fbrainy/11.0.5' }])
it('derives no URL (null) for a product with no known public release-page pattern', () => {
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '11.0.6', date: '2026-09-03', bullets: ['fix: a native-only fix'] }]))
writeFileSync(join(dir, 'wall.json'), wallFile('brainy', [{ ...BASE_ENTRY, version: '11.0.5', url: null }]))
const result = run(
['--product', 'brainy', '--version', '11.0.6', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(0)
run(['--product', 'brainy', '--version', '11.0.6', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'], dir)
const wall = readRemote(remoteDir, 'brainy')
expect(wall.entries[0].url).toBe('https://source.soulcraft.com/soulcraft/-/packages/npm/@soulcraft%2Fbrainy/11.0.6')
const wall = JSON.parse(readFileSync(join(dir, 'wall.json'), 'utf8'))
expect(wall.entries[0].url).toBeNull()
expect(wall.entries[0].thumb).toBeNull()
})
it('refuses a product with no permalink pattern, naming the cure', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY])
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '1.0.0', date: '2026-09-03', bullets: ['feat: first'] }]))
const result = run(['--product', 'mystery', '--version', '1.0.0', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir], dir)
expect(result.status).not.toBe(0)
expect(result.stderr).toMatch(/no permalink pattern for product "mystery"/)
expect(result.stderr).toMatch(/never carry url: null/)
})
it('refuses when the CHANGELOG has no entry yet for the target version, and touches no remote', () => {
seedRemote(remoteDir, 'open-brainy', [])
it('refuses by name when the version is already present, and leaves the file untouched', () => {
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.11', date: '2026-09-02', bullets: ['fix: whatever'] }]))
const beforeSha = git(['rev-parse', 'main'], remoteDir)
const before = wallFile('open-brainy', [BASE_ENTRY])
writeFileSync(join(dir, 'wall.json'), before)
const result = run(
['--product', 'open-brainy', '--version', '99.0.0', '--date', '2026-09-02', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
['--product', 'open-brainy', '--version', '10.4.11', '--date', '2026-09-02', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/refusing.*10\.4\.11.*already present/i)
expect(readFileSync(join(dir, 'wall.json'), 'utf8')).toBe(before) // untouched
})
it('refuses when the CHANGELOG has no entry yet for the target version', () => {
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.11', date: '2026-09-02', bullets: ['fix: whatever'] }]))
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', []))
const result = run(
['--product', 'open-brainy', '--version', '99.0.0', '--date', '2026-09-02', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/no CHANGELOG entry yet/i)
expect(git(['rev-parse', 'main'], remoteDir)).toBe(beforeSha)
})
it('refuses by naming the cure when the remote cannot be cloned', () => {
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.12', date: '2026-09-03', bullets: ['fix: whatever'] }]))
const noSuchRemote = join(tmpdir(), 'wall-remote-does-not-exist-' + Date.now())
const result = run(
['--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', noSuchRemote, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/cannot clone/i)
expect(result.stderr).toMatch(/cure:/i)
})
it('refuses by naming the cure, and touches no remote, when the fetched wall fails shape validation', () => {
const seedDir = mkdtempSync(join(tmpdir(), 'wall-seed-broken-'))
execFileSync('git', ['clone', remoteDir, seedDir], { stdio: 'ignore' })
git(['config', 'user.email', 'seed@example.com'], seedDir)
git(['config', 'user.name', 'Seed'], seedDir)
writeFileSync(
join(seedDir, 'open-brainy.json'),
JSON.stringify({ product: 'open-brainy', entries: [{ version: '10.4.11', date: '2026-09-02', items: ['x'], url: null }] }, null, 2),
)
git(['add', 'open-brainy.json'], seedDir)
git(['commit', '-m', 'seed broken'], seedDir)
git(['push', 'origin', 'main'], seedDir)
rmSync(seedDir, { recursive: true, force: true })
const beforeSha = git(['rev-parse', 'main'], remoteDir)
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.12', date: '2026-09-03', bullets: ['fix: whatever'] }]))
const result = run(
['--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/fails shape validation/i)
expect(result.stderr).toMatch(/missing key\(s\) headline/i)
expect(git(['rev-parse', 'main'], remoteDir)).toBe(beforeSha)
})
it('refuses by naming the cure when the remote rejects the push (stands in for a raced non-fast-forward)', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY])
makeRemoteRejectPushes(remoteDir)
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.12', date: '2026-09-03', bullets: ['fix: whatever'] }]))
const result = run(
['--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/push to .* failed/i)
expect(result.stderr).toMatch(/cure:/i)
})
it('refuses a cross-product write when the file\'s "product" field does not match --product', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY])
const seedDir = mkdtempSync(join(tmpdir(), 'wall-seed-mismatch-'))
execFileSync('git', ['clone', remoteDir, seedDir], { stdio: 'ignore' })
git(['config', 'user.email', 'seed@example.com'], seedDir)
git(['config', 'user.name', 'Seed'], seedDir)
const corrupted = JSON.parse(readFileSync(join(seedDir, 'open-brainy.json'), 'utf8'))
corrupted.product = 'brainy'
writeFileSync(join(seedDir, 'open-brainy.json'), JSON.stringify(corrupted, null, 2) + '\n')
git(['add', 'open-brainy.json'], seedDir)
git(['commit', '-m', 'corrupt product field'], seedDir)
git(['push', 'origin', 'main'], seedDir)
rmSync(seedDir, { recursive: true, force: true })
it('refuses a cross-product write when --product does not match the target file', () => {
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '1.0.0', date: '2026-09-03', bullets: ['fix: wrong repo'] }]))
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', [BASE_ENTRY]))
const result = run(
['--product', 'open-brainy', '--version', '1.0.0', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
['--product', 'brainy', '--version', '1.0.0', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--file', 'wall.json'],
dir,
)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/product "brainy".*--product "open-brainy"/i)
})
})
describe('wall-entry.mjs — --dry-run', () => {
it('prints the entry and the target path, and touches neither the cache dir nor the remote', () => {
seedRemote(remoteDir, 'open-brainy', [BASE_ENTRY])
writeFileSync(join(dir, 'CHANGELOG.md'), buildChangelog([{ version: '10.4.12', date: '2026-09-03', bullets: ['fix: a dry run'] }]))
const beforeSha = git(['rev-parse', 'main'], remoteDir)
const result = run(
['--dry-run', '--product', 'open-brainy', '--version', '10.4.12', '--date', '2026-09-03', '--from-changelog', 'CHANGELOG.md', '--remote', remoteDir, '--cache-dir', cacheDir],
dir,
)
expect(result.status).toBe(0)
expect(result.stdout).toMatch(/would write to/i)
expect(result.stdout).toMatch(/"version": "10\.4\.12"/)
expect(git(['rev-parse', 'main'], remoteDir)).toBe(beforeSha)
expect(result.stderr).toMatch(/product "open-brainy".*--product "brainy"/i)
})
})
@ -349,28 +169,21 @@ describe('wall-entry.mjs — --check', () => {
expect(result.stdout).toMatch(/OK/)
})
it('passes a file where "thumb" is entirely absent (optional per the HQ contract)', () => {
const { thumb, ...noThumb } = BASE_ENTRY as any
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', [noThumb]))
const result = run(['--check', '--file', 'wall.json'], dir)
expect(result.status).toBe(0)
})
it('catches a missing entry key', () => {
const broken = { version: '1.0.0', date: '2026-09-03', headline: 'h', items: ['i'] } // no "url"
const broken = { version: '1.0.0', date: '2026-09-03', headline: 'h', items: ['i'], url: null } // no "thumb"
writeFileSync(join(dir, 'wall.json'), wallFile('open-brainy', [broken]))
const result = run(['--check', '--file', 'wall.json'], dir)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/missing key\(s\) url/)
expect(result.stderr).toMatch(/missing key\(s\) thumb/)
})
it('catches an unexpected top-level key (e.g. the retired "history" field)', () => {
it('catches an unexpected top-level key', () => {
const raw = JSON.parse(wallFile('open-brainy', [BASE_ENTRY]))
raw.history = 'retired field'
raw.extra = 'not allowed'
writeFileSync(join(dir, 'wall.json'), JSON.stringify(raw))
const result = run(['--check', '--file', 'wall.json'], dir)
expect(result.status).toBe(1)
expect(result.stderr).toMatch(/unexpected key\(s\) history/)
expect(result.stderr).toMatch(/unexpected key\(s\) extra/)
})
it('catches entries that are not newest-first', () => {

View file

@ -7,7 +7,7 @@
* hydration (zero per-entity reads when unfiltered). Both must preserve the exact
* pagination contract: same order, cursor continuation, filters, totalCount.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { Brainy, NounType } from '../../../src/index.js'
describe('paginated enumeration — parallel hydration + id-only (cortex heal-cost)', () => {
@ -30,10 +30,6 @@ describe('paginated enumeration — parallel hydration + id-only (cortex heal-co
storage = brain.storage
})
afterEach(async () => {
await brain.close()
})
/** Page the whole dataset through a small limit via cursor and collect ordered ids. */
const pageAll = async (fn: (opts: any) => Promise<any>, key: 'items' | 'ids') => {
const out: string[] = []

View file

@ -4,7 +4,7 @@
* Tests to verify that brain.find({ type: NounType.X }) correctly filters entities
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType } from '../../src/index.js'
describe('Type Filtering (A Consumer Team Issue)', () => {
@ -17,10 +17,6 @@ describe('Type Filtering (A Consumer Team Issue)', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
it('should filter entities by NounType.Person', async () => {
// Add 3 people
await brain.add({ data: 'John Smith', type: NounType.Person, metadata: { name: 'John' } })

View file

@ -48,10 +48,6 @@ describe('the indexable-array bound', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
describe('BELOW the bound: the array indexes, every element of it', () => {
it('the eleven-element array that used to vanish is searchable', async () => {
// ELEVEN — one over the old silent limit, the whole shape of the defect.

View file

@ -42,7 +42,7 @@
* column store adopts the field. It is named in `getIdsFromChunksForRange`'s
* doc comment rather than papered over.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { NounType } from '../../../src/types/graphTypes'
import { SparseIndex, ChunkManager } from '../../../src/utils/metadataIndexChunking'
@ -122,10 +122,6 @@ describe('legacy sparse index: range queries order values, or refuse', () => {
expect(index.columnStore.hasField(FIELD)).toBe(false)
})
afterEach(async () => {
await brain.close()
})
describe('(a) a long BOUND against ordinary short values', () => {
// 'apple' < 'mango' < 'zebra', and every bound below is compared against
// these three raw keys.

View file

@ -6,7 +6,7 @@
* validateInvariants(), and repairIndex() maps a failing invariant with heal:'rebuild'
* to that provider's rebuild(). "healthy-while-broken must be impossible."
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType } from '../../src/index.js'
import type { ProviderInvariantReport } from '../../src/index.js'
@ -48,10 +48,6 @@ describe('validateIndexConsistency delegates to provider validateInvariants() (P
await brain.flush()
})
afterEach(async () => {
await brain.close()
})
it('a broken provider report makes the store unhealthy and names the failing invariant', async () => {
brain.index.validateInvariants = async () => brokenReport('vector')
const v = await brain.validateIndexConsistency()

View file

@ -12,7 +12,7 @@
* signal (from either strategy) THROWS VectorIndexNotReadyError immediately,
* with no rebuild attempt in between never a silent empty result.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType, VectorIndexNotReadyError } from '../../src/index.js'
const V = (): number[] => Array.from({ length: 384 }, (_, i) => Math.sin(i * 0.1) + 0.001)
@ -28,10 +28,6 @@ describe('Vector cold-read guard (verifyVectorLive) — silent-[] on cold semant
await brain.flush()
})
afterEach(async () => {
await brain.close()
})
it('warm brain: semantic find is correct and the guard does not rebuild', async () => {
const vi = brain.index
let rebuilds = 0

View file

@ -4,7 +4,7 @@
* Tests to verify VFS import behavior and identify if VFS creates only wrappers or also graph entities
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy, NounType } from '../../src/index.js'
describe('VFS Multi-instance Diagnostic', () => {
@ -17,10 +17,6 @@ describe('VFS Multi-instance Diagnostic', () => {
await brain.init()
})
afterEach(async () => {
await brain.close()
})
it('should verify VFS creates document wrappers AND allows entity filtering', async () => {
console.log('\n🔬 VFS Multi-instance Diagnostic Test\n')
console.log('='.repeat(70))

View file

@ -3,7 +3,7 @@
* Ensures tree methods prevent recursion and work correctly
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
import { VFSTreeUtils } from '../../src/vfs/TreeUtils.js'
@ -24,10 +24,6 @@ describe('VFS Tree Operations', () => {
await vfs.init()
})
afterEach(async () => {
await brain.close()
})
describe('Critical: No Self-Inclusion Bug', () => {
it('should NEVER return a directory as its own child', async () => {
// Create test structure

View file

@ -6,7 +6,7 @@
* - Issue #2: File read decompression error
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
@ -25,10 +25,6 @@ describe('VFS Bug Fixes', () => {
await vfs.init()
})
afterEach(async () => {
await brain.close()
})
describe('Issue #1: Duplicate Directory Nodes', () => {
it('should not create duplicate directory entries when writing multiple files to same directory', async () => {
// Write multiple files to the same directory (reproduce the bug scenario)

View file

@ -12,7 +12,7 @@
* other operations in parallel batches.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, beforeEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
@ -30,10 +30,6 @@ describe('VFS bulkWrite Race Condition Fix', () => {
await vfs.init()
})
afterEach(async () => {
await brain.close()
})
describe('operation ordering', () => {
it('should create directories before files when mixed in same batch', async () => {
// This is the exact scenario that triggered the race condition: