Releases: jedisolana/board-of-directors
Release list
Board of Directors 0.1.1
On PyPI: https://pypi.org/project/jedi-board-of-directors/
pipx install jedi-board-of-directors && boardOn PyPI, under a different name
pip install board-of-directors was never going to work: PyPI compares a proposed name against
existing ones with the separators removed, and boardofdirectors is already taken by an
unrelated project. The install name is now jedi-board-of-directors. The commands are
unchanged - board and board-of-directors - and so is the import, boardofdirectors.
Releases are cut by tagging. The workflow runs the test suite, refuses a tag that disagrees with
the version here, refuses a wheel that is missing the console or the model list, and uploads
with no API token in existence: PyPI verifies the repository and workflow directly.
The licence named a project that does not exist
It read freeboard contributors, a name left over from before this one. It now reads
jedisolana, and there is a test that walks every shipped document and source file and fails on
a stray date or year - the check that would have caught it.
Security review (CodeQL)
Code scanning was switched on and raised twenty alerts. Two were real and both are fixed; the
other eighteen are guards the scanner cannot see through, and each now has a test that tries to
get past it.
- The CI workflow declared no permissions, so its token inherited the repository default.
It now statescontents: read, which is all a checkout-and-test job needs. - A short secret showed four of its characters.
mask()reveals the publicsk-or-v1
prefix and four trailing characters of a real key, which is worth nothing to a stranger — but
the same rule applied to a sixteen-character string gives away a quarter of it. Short values
are now shown as****. - Session ids and patch paths were already contained, and the tests that say so were thin.
They now attack with URL encoding, a null byte, Windows separators, the doubled-dot trick,
and a symlink pointing out of the folder. Every one is refused or stripped. SECURITY.mdsays what is deliberately not restricted: the folder you point the board
at is any folder you like, because you are the one naming it — and the console listens on
loopback only, which is what makes that safe.
Board of Directors 0.1.0
Released as Board of Directors.
First release.
The board
- Independent answers from models in disjoint families — at most one seat per company,
because a jury of one family is one model with a stutter. - Blind peer ranking: members see each other as "Member A", "Member B", so a name on an
answer cannot move a ranking. The mapping is kept for the audit trail. - A chair that did not vote synthesises the decision, and falls back to another model if
the first refuses rather than discarding answers that were already given. - A member who failed is not a member who agreed. Failures are counted and named; too few
answers returns NO QUORUM instead of a confident-looking result from the survivors. - Two kinds of question: decide (a jury) and make (a competition — every member
attempts the task, the ranking judges whether they actually did it, the chair delivers the
winning attempt).
The console
- Runs on
127.0.0.1from your own machine, so it can hold your key without publishing it. - One conversation, two modes: a single model at one request a turn, switching to the board
on any turn and back again. Only the chair's verdict enters the history. - The cost of the next turn is shown before you send it.
- Live free-model list, sortable by coding or thinking benchmark, with the models that are
free-priced but not board material filtered out. - The board and saved-sessions panels fold away, so the model list can take the whole pane
while you browse. The fold is remembered.
Recipes
- Eight presets on top of the same engine, one call each. Competitions:
dream(several
companies' models each dream on a theme; you keep all of them),brainstorm(an idea-making
team),build(a code-making team),red_team(six attackers, one plan). Juries:
check_idea,review,audit(over a folder, refusing one that holds secrets). And
supply_chain— not a board at all: a different model works each step and hands down the line. - "Send anyway" now means it. The console's override for a folder with secrets passed the
server and was refused again by the engine's own seam. The engine takes the override as an
explicit argument; the server and the recipes pass it through.
Reading code
- Point it at a folder; it packs the files into one message and asks one model or the board.
A loader, not a harness — you choose what it reads. - Every file is scanned for secrets first. A folder with findings is refused, with an explicit
per-send override.
The audit before release
Two deep passes over the finished program, hunting the mistakes a first release ships with.
Eleven found, each one fixed with a test that fails without the fix:
- A
pip installserved a 404. The console page was never packaged; every test ran
against the source tree. CI now builds the wheel, installs it clean, and asks the installed
copy for a page. - Paid models could be called with paid switched off — through the everyday chat turn and
through the OpenAI-compatible endpoint, with the spend cap at $0.00. Both doors now check
at the request, and a zero cap overrules even an explicit yes. - The one endpoint that writes files could write anywhere on disk. Containment is now one
rule, enforced by the writer itself, symlinks included. - The folder scanner read through symlinks pointing outside the folder, which would have
put a linked credentials file into a prompt. Skipped now, and said so. - Every member ranked a line-up containing its own answer, under a prompt promising
otherwise. Each ranker now judges only the others. - A wall of chair failures ran the whole catalogue. Four in a row end the session with
the members' answers kept. - Closing the tab printed a traceback in the terminal. It no longer looks like a crash.
- Credentials written into a URL (
scheme://user:password@host) passed the secret seam.
Caught now. - A stale build directory shipped deleted files in the wheel. The build step cleans first,
and CI checks the artifact's contents. - One tooltip took catalogue data unescaped. Benchmark scores from the feed went straight
into a title attribute. Scores are now numbers or nothing at the server, and the page
escapes every interpolated attribute — structurally tested. - The library guide documented an attribute that does not exist (
scan.count). Every
example in the guide now runs as part of the test suite, so it cannot drift again.
Honesty about limits
- Free-tier allowance is read from the account (
is_free_tier) rather than asked of the user. - The request counter says estimated until a 429 hands over the real number, then keeps
subtracting from it. - Models the catalogue calls free but the API refuses are remembered and not picked again.
- The daily limit is account-wide: more models buys independence, not headroom. Said plainly.
Watching it happen
- The board streams: seats fill one at a time, each taking the colour of the vote it
declared, with a live line saying who is being asked and what stage the session is at. - A broken display can never fail a session.
The vote
- Members declare for / against / conditional; the board counts it and shows one dot per
seat, the totals, and whether the motion carried. - Read from what was declared, never inferred from prose. Undeclared is shown as undeclared.
- A tie reads as split, not as a decision.
- The chair is handed the count instead of recounting five essays.
- The tally persists in saved sessions and in exported minutes.
Paid models
- Every model on OpenRouter can hold a seat, each with its price on the row. Off by
default; the permission and the individual send must both allow it. - The estimate is shown before you send, rounds up, and a spend cap refuses anything over
it. Unpriced models are refused rather than costed at zero. - The chair follows the members' permission, so it can never turn a free session paid.
- Your balance is read from OpenRouter with an ordinary key.
- Routers (
openrouter/*) can never hold a seat: two of them can pick the same underlying
model and one-seat-per-family would guarantee nothing.
The board
- Six seats by default, with a picker that FILLS the board rather than asking you to tick
checkboxes, and an edit panel for choosing who sits in each one. The ceiling is the number of companies available,
since one seat per family means there is no honest board bigger than that.
Three model tiers
- A search box, because 400 models in a scroller is not a way to find one.
- Every paid model shows its price, plus what one call costs in cents, and a cheapest sort.
- free only · paid only · both. Paid-only is a real want: somebody paying for quality may
not want free models on the board at all. - An unrecognised tier falls back to free — a typo must not silently widen what may be spent.
Locking spending off
- A
$0.00cap makes spending impossible, not merely unselected — it overrules the paid
toggle and any saved board, because someone who locked spending has said so about their
money, not about a checkbox. - For anyone who bought the $10 purely for the 20× rate limit: the balance is a key, not a
wallet, and the header says so.
Honest metering
- Optional management key makes the request meter exact, reading OpenRouter's own
analytics instead of counting locally. Opt-in, stored separately, and only ever sent to the
analytics endpoint to read. - Retries no longer count as separate requests, and a 429 from an upstream provider is
told apart from OpenRouter's own limit — it costs nothing and does not move the meter. board reset-count(and a button) starts the meter clean, because a count that was already
wrong is not repaired by counting correctly afterwards.- The ledger is never clamped; going past the allowance is said in words, not hidden.
An OpenAI-compatible endpoint
POST /v1/chat/completionswithmodel: "board"— any OpenAI client gets a whole board's
decision in the shape it already parses.board:make,board:3select the shape.usagereports requests, not guessed tokens. Aboardobject carries every member's
answer, the tally, and who failed. No quorum is a409, not a confident200.
Local server hardening
- Cross-site
Originand non-loopbackHostheaders are refused: loopback keeps the network
out, but not a web page you visit, and not DNS rebinding.
Writing changes
- Describe a task and the board writes the fix, returned as a diff per file with an apply
button. Nothing is written until you press it, one file at a time. - Path traversal, files the board never saw, and files that moved since the scan are all
refused. The previous contents are kept before every write.
Sessions
- Conversations are saved locally and reopen with the whole proceeding — every member's
answer, every failure and its reason, the chair — not just the verdict. - Export a session as markdown for a pull request or a decision log.
- Renaming the project carries an existing install's key, tier and call count across.
Keeping the key out
- The key lives outside the repo by construction — there is no path by which committing the
project commits your credentials. - A pre-commit hook reads staged content and refuses a commit carrying anything
credential-shaped.
Documentation
README.mdis the product;docs/library.mdis the Python API.- Score tabs say how many models they can rank, and separate scored from unscored — the
published scores are sparse and they change.
Audited before release
- Atomic writes everywhere. Every store shared one temp filename; under the parallel board
twelve threads lost 91% of their writes to the file holding the API key. Unique scratch
names, fsync before rename, a thread lock and an floc...