项目介绍
Agent 配套工具 · 2026-11-09 更新 · 本站尚未实测
Nobulex Registry 是 Nobulex 早期方向的原型仓库,主题是“面向自主金融的决策完整性”(历史原型参考,非当前线上产品)。README 明确说明:Nobulex 当前重点是按用量计费产品的账单准确性,本仓库保留的是网关(gateway)与验证套件(suite)的旧有实现,不是当前对外产品。README 记载另一主仓库访问受限,本页只介绍此仍可访问的原型,不为相关版权争议作判断。
仓库里实际包含两部分,README 提醒不应混为一谈:gateway/ 是判断某个具体动作此刻能否执行的旧原型,会核验动作背后的证据、执行有界策略,并输出 PERMIT、BLOCK 或 ESCALATE,同时生成哈希链回执;suite/ 是给第三方工具打分的验证套件,属于早期方向,README 明确它不是仍在维护的线上产品。
核心能力
点击能力名称查看输入、输出和原文依据。
- 决策分层:先聚合证据状态(
PASS/FAIL/INDETERMINATE),再由策略映射为动作决策(PERMIT/BLOCK/ESCALATE),两者刻意不合并。 - 失败关闭原则:README 列出的三条属性是——零项检查不算通过;无法评估的限制不算满足;数据源明确拒绝不算事实失败,而是
INDETERMINATE。 - 故障语料自检:
gateway/fault_challenge.py对固定的故障语料做自检,并按 README 所述比对EXPECTED_CORPUS_HASH,语料变化时以非零退出码结束。 - 离线试点命令:
gateway/pilot.py使用自定义数据与权威数据做离线对比;除非显式传入标签,否则不打印捕获率与误拦率。 - 观察模式:通过
observe.guard包装调用,默认观察模式下结果与异常行为保持不变,只报告would_block。 - 内置 HTTP 接口:仅用标准库起服务,提供
/healthz与/v1/live-check等端点。
README 还指出该原型的能力边界:在十三条“格式正确但内容错误”的故障语料上,一段约三十行的自研版本同样全部命中且无误拦;本原型的优势在于“放弃判断”(证据缺失或不可读时正确升级或拦截),而非更强的内容识别。
实际应用场景
适用场景
- 研究或评估“金融动作执行前的证据校验与决策分层”这一思路。
- 学习失败关闭、证据状态与决策状态分离、可复现语料哈希等工程做法。
- 作为内部讨论“静默语义损坏”问题的参考样本。
它不适用于:核验第三方工具或供应商是否可靠(README 明确说明演示只验证单个分类器,不构成工具实测或登记证明);也不应作为生产环境的账单准确性工具——该方向位于另一个当前不可访问的仓库。
安装与接入
需要 Python 3.11 或更新版本。README 给出的自检不需要安装任何包:
git clone https://github.com/arian-gogani/nobulex-registry.git
cd nobulex-registry
python3 suite/selftest.py
示例脚本(原文给出的命令形式)包括 python3 examples/wrong_window.py、python3 examples/unknown_outcome.py、python3 examples/decision_demo.py、python3 gateway/selftest_gateway.py、python3 gateway/selftest_serve.py、python3 gateway/fault_challenge.py,以及离线试点 python3 gateway/pilot.py --subject your_bars.json --authority authority_bars.json。HTTP 服务需先按原文以 --policy gateway/policy.example.json 启动后再访问 localhost:8830/healthz;面向 live-check 的策略文件与端口在 README 中有单独示例。
关于 MCP 配置、客户端配置与发布包:本次采集的对应资料返回 GitHub HTTP 404,仓库 metadata 中的 topics 虽含 mcp、model-context-protocol,但 README 未给出可用的 MCP 接入说明,相关接入方式待核验。live_adapter 示例需要联网获取行情,具体数据源与可用性待核验。
使用条件与限制
- 是否需要部署
- 需自行准备运行环境,具体要求见正文
- 是否需要注册
- 账号和认证要求见正文;未明确说明的项目仍待核验
- 是否需要 API Key
- 待核验
- 费用与额度
- 费用边界见正文,项目许可不代表模型或托管服务免费
- 所需权限
- 待核验
- 许可证与服务条款
- 待核验
权限费用与限制
- 仓库代码以 MIT 许可证发布(见 LICENSE),仅覆盖仓库内代码;托管站点 nobulex.com 上的服务、付费产品与第三方数据源均不在该许可证范围内,其费用与条款待核验。
- README 中提及的“买方指定验证”“持续验证”均以条件式表述,并明确说明迄今没有任何售出记录、仓库内不存在调度器,持续验证只是规格而非运行中的服务。
- 公开登记册被描述为分发面,且所发记录均被保留;README 也说明登记册不会自动把过期结论改为
EXPIRED,读者需自行核对有效期。 - 本仓库为先前原型,README 说明登记册方向不再作为线上产品维护;
auth_degradation故障类别标注为“保留、未实现”。 - 结论不构成安全、合规或适配性保证;记录不涉及任何货币化的信任额度或评分。
国内使用情况
网友核验 用户经验分享
与站长实测独立,不代表官方认证或本站已实测。
最近核验:暂无有效核验。参与人数按当前项目有效身份去重;不同核验项分别统计,不合并成用户总结果。仅统计审核通过且未过期的记录。
分项统计、有效期与样本量
| 核验项目 | 有效人数 | 成功 / 部分 / 失败 / 无法判断 | 成功率 | 最近核验 / 有效期 |
|---|---|---|---|---|
| 官方网站访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| GitHub 仓库访问 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 安装与运行 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 官方服务国内连接 / MCP 连接 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 国内网络情况 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 7 天 |
| 国内模型兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 自定义 API | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外服务依赖 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 海外账号要求 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 中文界面 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 客户端兼容性 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
| 账号及费用信息 | 0 | 0 / 0 / 0 / 0 | 样本不足 | 暂无 / 30 天 |
成功率 = 成功 ÷(成功 + 部分成功 + 失败);无法判断不计入分母。至少 5 份可判断结果才显示成功率。账号、费用、海外依赖与语言等事实项,成功表示核查结论与资料吻合,具体观察另行统计;不表示免费或没有海外依赖。不同网络、客户端和版本的结果不能直接等同。
最近有效核验
暂无审核通过且未过期的网友核验。欢迎提交真实使用记录。
查看全部有效核验 →国内实测状态:国内可用性待核验。协议兼容和官方资料说明不等于国内网络可用。
- 官方网站访问
- 尚未实测 待核验
- GitHub 仓库访问
- 尚未实测 待核验
- 官方服务国内连接
- 尚未实测 待核验
- 国内网络情况
- 尚未实测 待核验
- 国内模型兼容性
- 尚未实测 待核验
- 自定义 API
- 尚未实测 待核验
- 海外服务依赖
- 尚未实测 待核验
- 海外账号要求
- 尚未实测 待核验
- 中文界面
- 尚未实测 待核验
- 国内最后核验时间
- 尚未实测
许可与国内使用
仓库代码采用 MIT 许可证,可在遵守许可证条件的前提下使用、修改与再分发;这不代表其关联的网站服务、商业模块或第三方行情数据免费或可自由使用,上述内容待核验。README 未说明服务部署地域、数据存储位置或对中国大陆网络环境的适配情况,相关可用性与合规要求待核验。若用于真实金融数据校验,需自行核实数据授权与监管要求。
项目资料与来源
字段依据与待核验事项
access:来源确认 · git clone https://github.com/arian-gogani/nobulex-registry.git
project_type:来源确认 · The gateway prototype checks the financial evidence behind a proposed action, evaluates a deterministic policy, and decides whether the action may execute.
原始 README(折叠技术参考)
# Nobulex
**Earlier prototype: decision integrity for autonomous finance.** Nobulex's
current focus is billing accuracy for usage-based products.
This repository preserves the gateway and verification harness; it is not the
current product offer.
A well-formed lie passes every schema check. The gateway prototype checks the financial evidence behind a proposed action, evaluates a deterministic policy, and decides whether the action may execute.
Financial data first, because it is the one place the right answer is checkable against an outside authority.
[THESIS.md](THESIS.md) holds the argument underneath this, written for the registry rather than the gateway and marked as such at the top. The part that survived the change is the load-bearing one: a signature proves who made a claim, not that the claim was true. Its falsification section still tests the registry thesis, so as an argument it is history rather than the current one, and it says so. What is **not** history is the header's three gateway falsification conditions: as of 20 October 2026 all three are measured in code and gated, and the third one came back against the gateway. See [gateway/inhouse_control.py](gateway/inhouse_control.py) before taking the fault-corpus catch rate as the decision layer's contribution, because it is not.
[The method](https://nobulex.com/methodology) · [Why this role has to exist, as first argued](THESIS.md)
### What is in this repository
Two surfaces live here, and reading them as one thing is the fastest way to
come away confused about what is being offered.
**The gateway is a prior prototype.** `gateway/` decides whether a specific action
may execute right now: it verifies the evidence behind the action, evaluates a
bounded policy, returns `PERMIT`, `BLOCK` or `ESCALATE`, and emits a
hash-chained receipt. The current billing-accuracy thesis and tools live in
the main Nobulex repository, which is **currently unavailable**: it returns HTTP 451 under a DMCA takedown recorded 20 October 2026, so no link to it is given here.
**What it is actually for, stated narrowly because a control narrowed it.**
Not catching bad data. On a corpus of thirteen well-formed-but-wrong faults, a
thirty-line in-house version catches all thirteen, exactly as many as this
does, with no false blocks. That comparison is in
[gateway/inhouse_control.py](gateway/inhouse_control.py) and it runs.
The gateway's margin is **abstention**: deciding correctly when the evidence is
absent or unreadable rather than wrong. On five such conditions, the short
version permits all five and this blocks or escalates all five. The reason is
one shape, and it is not a lack of care in the short version: `all([])` is
True, a missing field reads as zero and zero is under every cap, and an
incomparable value falls through its `except`. Each default is individually
defensible. Together they fail open exactly when evidence is missing, which is
the condition the thing was bought to cover.
The narrow technical argument for this prototype is "you can write this in
thirty lines, it will pass your tests, and the cases it gets wrong are the ones
nobody writes a test for." Anything broader than that is not supported by a
run in this repository.
**The registry is a prior direction, kept rather than deleted.** `suite/` is a
verification harness that grades third-party tools, and the sections further
down about records, right of reply, publication gates and who pays belong to
it. Its method is real and its two records are real, so deleting it to make
the repository tidier would be the wrong instinct: the gateway's classifiers
*are* the suite's classifiers, and the suite is where they were proven. But
nothing about the registry is being maintained as a live product, and the site
says as much. If you are evaluating the gateway, `gateway/` and `examples/`
are the whole story.
**On the verdict vocabulary,** which looks contradictory across the two and is
not. Three layers, each narrower than the last:
| Layer | Values | Where |
|---|---|---|
| Classifier outcome, per probe | `PASS` `FAIL_UNSAFE` `FAIL_SAFE` `INDETERMINATE` `OUT_OF_SCOPE` | `suite/harness.py` |
| Evidence status, aggregated | `PASS` `FAIL` `INDETERMINATE` | `gateway/decide.py` |
| Decision, per action | `PERMIT` `BLOCK` `ESCALATE` | `gateway/decide.py` |
The five collapse into the three: `FAIL_UNSAFE` becomes `FAIL`, while
`FAIL_SAFE`, `INDETERMINATE` and `OUT_OF_SCOPE` all become `INDETERMINATE`,
because a source that refused, a probe that could not read its input, and a
question that did not apply are three different facts and none of them is a
pass. The policy then maps evidence status to a decision. Evidence status and
decision are deliberately never merged; collapsing them loses the difference
between "the facts are wrong" and "your rule is strict," which need different
responses from an operator.
Run the part that decides whether a verdict means anything:
```bash
git clone https://github.com/arian-gogani/nobulex-registry.git
cd nobulex-registry
python3 suite/selftest.py
```
No package install is required for the self-test. If you find a corrupted response it calls clean, open an issue with the smallest reproduction you can make. If you want to follow the work, star the repository.
### Try one failure in a minute
```bash
python3 examples/wrong_window.py
python3 examples/unknown_outcome.py
```
This offline example feeds fictional data into the suite's actual truncation classifier. It shows three cases: matching sessions (`PASS`), silently missing sessions (`FAIL_UNSAFE`), and equal row counts covering entirely different dates (`INDETERMINATE`). The last case passes a count-only comparison, but this probe cannot establish a clean result from it.
The script checks all three expected outcomes and exits nonzero if any changes. It needs only Python 3.11 or newer. No credentials or downloads are needed after cloning. This demonstrates one classifier, not a live tool test or a registry attestation. Even its `PASS` does not establish that the prices are correct.
Found an input that produces the wrong verdict? [Open an issue](https://github.com/arian-gogani/nobulex-registry/issues/new/choose) with fictional or shareable input, the command you ran, the actual output, and the result you expected. Please leave credentials and private data out.
### See the decision layer
```bash
python3 examples/decision_demo.py
python3 gateway/selftest_gateway.py
python3 gateway/selftest_serve.py
```
The fault challenge is the fixed suite a pilot replays, pinned by hash and
self-checking: thirteen well-formed-but-wrong conditions through the real
classifiers into the real decision layer, with catch rate, false-block rate
and policy-evaluation latency printed as measurements. It exits nonzero the
day any fault stops being caught or any clean case stops being permitted.
**On "pinned by hash", which was false twice over until 22 October 2026.** The
hash covered only the corpus version, the observation time, the session dates
and the closes. It did **not** cover the fault cases, so a fault could be
renamed, altered, added or removed and the printed hash would not move; verified
by altering a fault in a temp copy and getting a byte-identical value. And
nothing compared it to anything: it was computed, truncated and printed.
"Pinned" described a number that could drift freely in both directions.
It now covers every fault and clean case by name and by the subject rows they
carry, and `gateway/fault_challenge.py` asserts it against a pinned
`EXPECTED_CORPUS_HASH` and exits nonzero when it moves. Verified load-bearing:
renaming one fault case produces exit 1 and prints both hashes. The reason this
matters is narrow and real: every published catch rate, false-block rate and
indeterminate rate was measured on one specific corpus, and without the pin a
reader could not tell whether they still were.
**The pilot is now a command rather than a promise.** This sentence described
something that nothing implemented until 21 October 2026: every artifact here ran
against a fictional offline corpus, so you could not get a number about your own
system without trusting ours.
```bash
python3 gateway/pilot.py --subject your_bars.json --authority authority_bars.json
```
Offline, nothing phoned home, no network calls to us. It prints the verdict per
check, the decision, the indeterminate rate against the same 25% ceiling, and
the policy latency.
**It refuses to print a catch rate or a false-block rate.** Those are the two
numbers a vendor most wants to show you, and over your data they are not
quantities the program can know, because your rows are not labelled: that is why
you are running it. Both report `NOT_EVALUATED` unless you pass `--labels`, and
with labels the output says the numbers are about your labels rather than about
reality. A tool that printed them anyway would be the exact defect this project
exists to find.
It exits 2 rather than 0 on absent, empty or unreadable input, because a run
that examined nothing has established nothing. Checks it could not run stay in
the indeterminate denominator rather than vanishing from it. Controls:
`python3 gateway/selftest_pilot.py`.
### A worked example with no customer in it
`examples/unknown_outcome.py` reproduces a real defect from this repository,
before and after, in one run. Not a hypothetical, and not found in someone
else's code: `aggregate()` walked a fixed precedence order and returned the
first member present, so an outcome outside that order was never looked at.
**One unreadable outcome beside one pass aggregated to PASS, and PASS is a
permit to execute a financial action.** Three of its eight cases changed
decision, each from PERMIT to ESCALATE.
The "before" column is computed by the original five-line function, included
verbatim in the file rather than described, so the comparison is reproducible
rather than asserted. Two rows exist to show the fix is not blunt: `FAIL_UNSAFE`
still dominates and `FAIL_SAFE` still escalates rather than blocking.
It survived 409 suite cases and seventeen self-test files. That is the point of
publishing it.
```bash
python3 gateway/fault_challenge.py
```
One case in it exists because the suite refused my first version of this
file: an authority carrying a null close was written as a clean case, and
the fidelity probe would not permit it, because a comparison is only as
readable as its weaker half. That is recorded in the corpus as the
degraded-evidence case that must ESCALATE, with the reasoning inline.
Another case exists because an adversarial audit found that
`classify_truncation` only ever checked for a session deficit, never a
surplus: a subject serving every real session plus one fabricated bar dated
a Saturday, priced roughly 300% off, passed every classifier and reached
`PERMIT`. Fixed in `suite/harness.py`: a subject date strictly inside the
authority's own covered window that the authority does not carry is now
`FAIL_UNSAFE` with cause `surplus_session`, bounded to inside the window on
purpose so a session past the authority's own latest date (which may just
be a session the authority hasn't caught up to yet) is not flagged the same
way. Pinned in `suite/selftest.py` (409 cases, run it), mutation-tested
by removing the check and confirming the exploit reopens, and recorded in
the corpus as `fabricated_session`.
The remaining half of that same gap is closed too: `classify_fidelity`
compared Close only, so a subject that fabricated a plausible High or Low
around a correct Close, on a real session, would still pass. `harness.py`
now has a second probe, `classify_range_fidelity`, comparing Open, High and
Low against the authority's own values the same way Close already was, with
the same tolerance and the same rule that an absent field on either side is
not compared rather than treated as agreement. Wired into
`gateway/live_adapter.py`: `authority_bars()` now carries the authority's
real Open/High/Low instead of dropping them, and `subject_from_authority()`
mirrors them faithfully instead of flattening all three to Close, which is
what let this gap go unnoticed by the live demo for as long as it did.
Verified live: a 2% price corruption is now caught independently by both
`fidelity` and `range_fidelity`. Pinned in `suite/selftest.py` (409 cases
now), mutation-tested, and recorded in the fault corpus as
`fabricated_range`.
The first thing here that touches a live source:
```bash
python3 gateway/live_adapter.py AAPL
python3 gateway/live_adapter.py AAPL --inject corrupt
```
It fetches a real daily series, runs the real classifiers and the real
decision layer, and PERMITs when the transport is faithful. `--inject`
corrupts the live series one plausible way, and the same pipeline blocks
it, naming the classifier and, for a value corruption, the exact session
and deviation. It does not verify a third-party tool: the subject is the
authority's own series unless you point it elsewhere, and where the only
reference is the subject's own upstream a value verdict is OUT_OF_SCOPE,
not PASS. Its tests stub the network, so the suite still runs offline.
The integration story for a shadow pilot is one wrapper:
```python
import sys; sys.path.insert(0, "gateway") # or install the package
from observe import guard
fetch_bars = guard(fetch_bars, checks=my_checks, policy=my_policy,
on_decision=log_or_webhook)
```
Every other command here runs from the repository root, so that first line
is doing real work: `observe` lives in `gateway/`, and without it this
snippet is the one thing in this README that fails when pasted as written.
In observe mode, the default, the wrapped call is the call it was before:
same result, same exceptions, even when a fault fires, even when the
gateway's own code raises. A shadow that can break production is not a
shadow, and that property is pinned by `gateway/selftest_observe.py`,
including the case where the decision consumer itself is broken. In
enforce mode the gateway's own failure is the caller's stated choice,
block by default, never a silent approval.
There is an HTTP surface too, standard library only, no install:
```bash
python3 gateway/serve.py --policy gateway/policy.example.json
curl -s localhost:8830/healthz
```
`policy.example.json` gates on quote freshness and turnover fields a live-check has no reason to supply, so it reports those limits `UNEVALUATED` and blocks, correctly. For live-check specifically, start the gateway on a policy scoped to it. Stop the first server before this, or it binds the same port and Python raises `OSError: Address already in use` rather than anything this project would call a clean refusal:
```bash
python3 gateway/serve.py --policy gateway/policy.live-check.example.json --port 8831
curl -s -XPOST localhost:8831/v1/live-check -d '{"ticker":"AAPL"}'
curl -s -XPOST localhost:8831/v1/live-check -d '{"ticker":"NOTAREALTICKER"}'
```
The checks answer "were these facts correct". `gateway/decide.py` answers the question a caller has to act on: may this specific action execute now. It keeps two surfaces apart on purpose.
evidence_status PASS | FAIL | INDETERMINATE
decision PERMIT | BLOCK | ESCALATE
Collapsing them loses the difference between "the facts are wrong" and "your rule is strict", which need different responses.
Three properties the tests exist to pin down, because each one is a way for a gateway to report success without deciding anything:
- **Zero checks is not a pass.** An evidence status aggregated over an empty set is `INDETERMINATE`. `all([])` being `True` is the defect, not the design.
- **A limit that cannot be evaluated is not satisfied.** A missing field or an incomparable value blocks and is counted in `unevaluated_limits`, never skipped.
- **An explicit refusal is not a failure of fact.** A source that declines gives `INDETERMINATE`, not `FAIL`. Reading it as `FAIL` blocks on every hiccup and trains operators to bypass the gateway; reading it as `PASS` is the defect this repository is about.
Observe mode computes the whole decision and reports `would_block` without ever claiming it stopped anything. Receipts hold hashes, not payloads, and an unsigned receipt says `UNSIGNED` rather than looking signed.
Want to contribute a test, fix, or method correction? Start with [CONTRIBUTING.md](CONTRIBUTING.md).
---
## The failure this exists for
An agent calls a tool. The tool returns a response that is well formed, plausible, and materially wrong.
Empty where it should have been populated. Stale where it claims to be current. Scoped to a different entity than the one requested. Truncated with no signal that anything was cut. Filled from a fallback path that was never disclosed.
Nothing raises. Nothing logs. The schema validates, because a well formed lie validates perfectly. The agent has no way to distinguish this from a correct answer, so it acts on it, and every downstream step inherits the error with full confidence.
This registry has one subject: **silent semantic corruption**.
The governing test is one question:
> **Does it fail loud, or does it lie quiet?**
A tool that raises an error is usable. You can retry it, route around it, surface it to a human. A tool that returns a confident wrong answer is not usable, and it is not currently measured by anything. Uptime does not measure it. Stars do not measure it. A green CI badge does not measure it. Schema validation does not measure it. Latency percentiles do not measure it.
Every existing reliability signal in this ecosystem answers "did it respond." None of them answer "was the response true."
## Why financial data first
Not because it is the largest market. Because it is the only place where the ground truth is unambiguous, timestamped, and independently obtainable. A stock price at a given second either matches an authority or it does not. There is no rubric, no judgment call, and no argument about whether the verdict was fair.
Categories where correctness is a matter of opinion come later, or never. A registry that starts somewhere contestable spends its first year defending verdicts instead of accumulating them.
---
## What gets graded
Never a project. Never a company. Never a name on a repository.
The subject of a Nobulex record is a **tuple**, and every field in it is load bearing:
- **package** identity
- **exact version or commit SHA**, never a floating tag or branch
- **configuration** used during the run
- **upstream data source** the tool was reading from
- **execution environment**
- **test suite** identifier and version
- **observation time**
Drop any one field and the record stops being reproducible, which means it stops being evidence. A grade attached to "acme-mcp" is a rumor. A grade attached to `acme-mcp @ 3f21b09, config X, upstream Y, env Z, suite v0.2, observed 2026-08-02T14:11Z` is a record.
This is also the defamation boundary. Nobulex grades a commit under stated conditions. It does not characterize a company, a maintainer, or a product line.
---
## The five verdicts
| Verdict | Meaning |
|---|---|
| `PASS` | Under this suite, in this window, the subject returned results correct within a tolerance pinned before execution, against a named ground truth source. |
| `FAIL_SAFE` | The subject failed, and the failure was loud. Errors raised, degradation disclosed. Recoverable, and still a failure. |
| `FAIL_UNSAFE` | The subject returned a well formed answer that was materially wrong, with no signal. This is the category. |
| `INDETERMINATE` | The run could not establish either result. Harness fault, upstream outage, insufficient ground truth. Not a soft fail. |
| `OUT_OF_SCOPE` | The suite does not cover this subject's behavior under these conditions. No claim is made in either direction. |
Two rules that are not negotiable:
**`FAIL_SAFE` is better than `FAIL_UNSAFE`, and it is not the same as `PASS`.** A tool that is down and says so has left you something to route around. It is still failing. Anyone who reads `FAIL_SAFE` as a pass has been told something false, and the presentation layer is responsible for preventing that reading.
**`INDETERMINATE` is a real verdict, not an absence.** A registry that quietly drops the runs it could not resolve is publishing a survivorship curve, not evidence. Indeterminates get recorded, counted, and shown.
---
## Three attestation types, never merged
- **TOOL** attestation covers one subject tuple called directly, with no model in the loop.
- **COMPATIBILITY** attestation covers whether a specific client reads the tool's description and schema correctly, which is a different failure from the tool returning bad data.
- **WORKFLOW** attestation covers a model, client, server, configuration, and task executing end to end.
These answer different questions and carry different warranties. The reason they stay separate is not tidiness. It is that merging them lets a vendor dispute whose fault a failure was: the model misread the description, the client mangled the call, the server returned garbage. Three separate records make that argument unavailable.
Averaging them into a single number is how a registry becomes a star rating, and a star rating is exactly the signal that already exists and already fails.
---
## What a verdict warrants, and what it does not
A Nobulex record **does** say:
- These specific inputs were sent to this exact subject tuple at this time.
- These outputs came back.
- They were compared against this named ground truth source.
- Deviations were classified under this published taxonomy.
- Anyone with the record can rerun the suite and get the same result.
A Nobulex record **does not** say:
- That the subject is safe, secure, or free of vulnerabilities.
- That the subject is fit for your use case.
- That the subject will behave the same way tomorrow, or on a different commit, or against a different upstream.
- That the maintainer is trustworthy or untrustworthy.
- Anything at all in a currency. Nobulex publishes no dollar-denominated trust figure, no coverage limit, and no score that looks like one.
Nobulex is the **evidence provider, not the custodian.** It holds no funds, insures nothing, and settles nothing. Where a remedy is warranted, that is a licensed counterparty's product, and the record is the input to it.
---
## Validity windows and expiry
Records carry validity metadata. The current runner assigns a seven-day window to results that do not require right of reply. Results held for reply initially have no expiry deadline; clearing assigns a seven-day window when one is missing.
The current register displays the stored verdict and its validity deadline. It does not automatically change the displayed verdict to `EXPIRED` when that deadline passes. Readers must check the deadline, and a historical `PASS` must not be treated as evidence of current behavior.
Automatic expiry at lookup is intended behavior, not implemented enforcement in this register. The stored observation remains historical evidence; whether it is still current needs a separate check.
Correction: this section previously claimed that an out-of-window query returns `EXPIRED` and that the schema cannot represent an expired pass. The current implementation does not support those claims.
**There will never be a portable badge image.** A PNG a maintainer can copy into a README is a claim that outlives its evidence, keeps rendering green after the verdict is withdrawn, and cannot be revoked. That mechanism is the specific way this category of business has failed before. Verification resolves against the register, live, or it does not resolve.
---
## Who pays
**The party relying on the verification pays. Never the graded party for its own verdict or re-test.**
A maintainer cannot commission a verdict on its own package, cannot preview one before publication, and cannot negotiate one. The moment revenue depends on the subject's satisfaction, every verdict becomes a negotiation, and the registry's output is worth precisely nothing. This constraint costs real money early. It is what the entire thing is made of.
Maintainers can submit fixes and evidence for review, but cannot buy a re-test of their own tool. Re-tests follow the published methodology and retain the same funding separation.
Correction: this README previously allowed maintainer-funded re-tests. That exception contradicted the buyer-funded policy stated on the site. It is removed here. This is a policy correction, not evidence that any paid re-test took place. Independent verification also requires reproducible evidence and a checkable method; the funding rule alone does not establish it.
---
## What would be sold
Two things, neither of which is the public register. **Nothing here has been
sold.** Section "Not yet true" below states that no buyer has paid for a
verification, and this section is written in the conditional to match it. It
described both products in the present tense until 22 October 2026, which read
as a price list over an empty order book.
**Buyer-pinned verification.** You name the server and the exact release you intend to deploy, plus the conditions you require: which upstreams, which configuration, which tolerance, which environment. Nobulex would test that tuple independently and return an attestation. This is intended as the first paid product, because it is the only one where somebody has already decided to spend money and needs a reason to sign.
**Continuous verification.** A pass three months ago says nothing about today. Upstreams change shape, maintainers ship, adapters absorb drift silently. Continuous verification would re-run the pinned suite against the pinned subject on a schedule and alert when the verdict changes. **No scheduler exists in this repository**, verified by searching for cron, schedule, celery, systemd and launchd across every Python file: this is a specification, not a running service. It is intended as the recurring product, and it is the one that would matter most, because it is the only version of this that keeps being true.
The public register would be the distribution surface for both. It is marketing, and it has distributed nothing: every record it has issued is held. It is not the business, and confusing the two is how registries with beautiful dashboards run out of money.
---
## The loss cause taxonomy
Every deviation is classified before it is recorded. The taxonomy is versioned from the first record it is applied to, because a classification scheme that changes silently makes its own history unreadable.
`v0` covers:
| Code | Failure |
|---|---|
| `silent_empty` | Returned an empty result where data existed, with no signal. |
| `stale_value` | Returned cached or outdated data presented as current. |
| `wrong_entity` | Returned data for a different entity than the one requested. |
| `fabricated_field` | Returned a field populated with a value that has no upstream basis. |
| `partial_truncation` | Returned a subset of the result with no truncation signal. |
| `unsignaled_fallback` | Served from a fallback source without disclosing the substitution. |
| `auth_degradation` | Silently downgraded to a lower privilege tier and returned reduced data as complete. **Reserved, not implemented.** |
| `schema_drift` | Upstream shape changed and the adapter absorbed it into a wrong but valid response. |
| `surplus_session` | Returned a session the authority does not have, inside the requested window. Emitted by `classify_window_span`; it was absent from this table until 22 October 2026 while the harness was emitting it. |
Each code is designed to be decidable from a run artifact without a human judgment call, which is what keeps the corpus consistent as it grows.
Seven of the eight are decidable that way and the suite emits them. `auth_degradation` is not, which is why it is reserved rather than implemented. Deciding it requires knowing which tier the subject was entitled to, and a run artifact does not carry that: a shorter history is indistinguishable from a tier that was always shorter. What the suite can see, it already grades as `partial_truncation`, which describes the symptom without asserting a cause it cannot establish. Naming the reason a code is absent is more useful than quietly shipping seven under a heading that says eight.
The accumulated history of which subjects fail in which ways is the part of this that cannot be reconstructed later. Anyone can copy the taxonomy in an afternoon. Nobody can copy three years of observations they did not make.
---
## The indeterminate reason list
The taxonomy above says what a subject did wrong. This one says why no claim was issued, and it exists for a reason THESIS.md states as an attack on this project rather than as a feature of it. Precommitting runs stops a verifier from suppressing an unfavourable pass or fail. It does nothing about an unbounded indeterminate, which would let any awkward result be filed as undecidable. So an indeterminate requires a reason drawn from a closed, published list. This is that list.
It was not implemented until 22 October 2026, and THESIS.md said so in writing for two days before it was. Fifty return sites in `suite/harness.py` resolved INDETERMINATE and every one of them put a null in the reason field and free prose beside it. Three of the fifty said "unclassifiable response" word for word. Four more were four different wordings of one condition: "no bars", "subject returned no bars to compare", "authority returned no bars to compare". That prose is published, not internal, because `suite/render_register.py` prints it into every probe row of the register. So the public page was carrying fifty varieties of prose where this document promised a vocabulary.
`v0` covers:
| Code | Why no claim was issued |
|---|---|
| `unparseable_response` | No structured payload was obtained at all. |
| `unclassifiable_response` | A payload was obtained and is not the documented shape, so no documented branch of the classifier applies. |
| `subject_no_data` | The subject returned no record the probe can read its field from: no records, or no record carrying a readable value in that field. |
| `authority_no_data` | The independent authority returned no record to compare against, on the same terms. |
| `control_unusable` | The probe's own baseline read, the control it compares the test condition against, errored, was unreadable, or returned nothing. |
| `insufficient_observations` | Records were readable but too few for the measurement. A span needs two dates; an ordering needs two. |
| `date_unreadable` | A session date was present and could not be read as an ISO 8601 calendar date, so chronology, span and recency cannot be established from it. |
| `overlap_too_thin` | The two sides align on too few common sessions to separate a wrong value from a date-basis misalignment. |
| `date_basis_mismatch` | The two sides agree at a uniform session lag and not at lag zero, which is a calendar-basis difference rather than wrong numbers. |
| `reference_value_degenerate` | The authority's value was zero on every aligned session, so relative deviation was undefined and nothing was compared. |
| `coverage_incomplete` | Part of the payload could not be examined, so a clean result over the examined part does not establish a claim over the whole. |
| `precondition_unmet` | A parameter needed to attribute a shortfall to the subject rather than to the world was absent or was not a usable measurement. |
| `evidence_ambiguous` | The evidence is consistent with the finding and with its absence and supports neither. |
| `authority_unavailable` | The authority did not answer, so no comparison was performed. A property of the authority and of the network the run was made from, not of the subject. **Reserved, not set yet.** |
| `subject_not_observed` | The shared live pull returned nothing, so the probe never observed the subject. This is the `NOT_EVALUATED` reason, not an `INDETERMINATE` one. **Reserved, not set yet.** |
| `harness_exception` | An unexpected exception inside a probe, recorded with its traceback. A fact about this run, not about the subject. **Reserved, not set yet.** |
A reason code is not an accusation. It shares the record field `loss_cause` with the taxonomy above, because that field already existed and was null at every indeterminate, but the two vocabularies are disjoint and the outcome beside the code says which one is being read: a loss cause for `FAIL_SAFE` and `FAIL_UNSAFE`, an indeterminate reason for `INDETERMINATE` and `NOT_EVALUATED`, null for `PASS` and `OUT_OF_SCOPE`.
**The codes are not mutually exclusive, and claiming otherwise would be the dishonest version of this.** A payload can be short of records and carry unreadable dates. A classifier reports the first condition that stopped it, so branch order inside the classifier is the tie-break, and the free text beside the code still says which records and which values. Three sites choose between two codes with a conditional rather than report a vaguer one that covers both: where either side could be the one with nothing readable, the code names the side.
Thirteen of the sixteen are emitted, at all fifty indeterminate return sites in `suite/harness.py`. The last three are reserved in the same sense as `auth_degradation` above and for a narrower reason: they are conditions `suite/run.py` and `gateway/pilot.py` resolve rather than `suite/harness.py`, both files were outside the change that introduced this list, and all five of their sites still pass a null. They are `run.py:870` and `run.py:934` (the authority did not answer), `run.py:834` and `gateway/pilot.py:160` (nothing was obtained to judge), and `run.py:360` (an exception inside a probe). Naming them with the reason they are not yet set is more useful than publishing a list that cannot cover the record a reader is holding.
**Closed how, exactly.** `suite/selftest.py` holds it closed two ways, because one of them is not enough. Every case that gets an `INDETERMINATE` back asserts the cause is a member, which covers the conditions the suite has a fixture for and nothing else. So a second check walks `suite/harness.py` with `ast` and fails if any `INDETERMINATE` return carries a second element outside the set, including a null, a misspelling, or anything that is not a readable string literal. That one is the load-bearing half: a classifier added next month cannot widen the list by arriving. A third check compares the set against this table, so a code added to the code and not to this document goes red rather than shipping a register whose vocabulary is wider than its documentation. The `ast` sweep covers `harness.py` only; extending it to `run.py` and `gateway/pilot.py` would fail today, which is why the three codes above are marked reserved rather than why the sweep is silent about them.
Verified load-bearing by mutation: changing one reason code in `suite/harness.py` to a string that is not on this list makes `suite/selftest.py` exit 1 and print the file, line, function and offending value. The unmutated tree exits 0.
---
## Status
Written plainly, because a registry whose README overclaims has already failed its own test.
**Designed and specified:** the verdict system, the subject tuple, the attestation types, the loss cause taxonomy v0, the warranty scope, the expiry model, the publication gate, and the public register surface.
**Built:** a verification harness that speaks raw JSON-RPC to a subject over stdio and never imports the subject's own code, with probes for nonexistent entities, malformed input, whitespace-padded arguments, oversized windows, OHLC invariants, monotonic dates, silent truncation, Close-price fidelity and entity identity against independent authorities, channel hygiene, and freshness. **Two probes this sentence used to claim are not built.** *Closed-market dates*: the classifier exists at `suite/harness.py:750` and has no caller, because its own docstring explains that wiring it to the market-holiday reading would produce false FAIL_UNSAFE accusations. *Cross-endpoint agreement*: no probe compares two of the subject's own endpoints; P09 compares the subject against an outside authority and P11 compares two calls to the same endpoint. Both belong in the designed list above and were in this one until 22 October 2026, which is exactly the line this section exists to hold. A self-test that runs those probes against fixtures known to be bad, because a probe that cannot fail is worse than no probe at all. A record generator that writes the subject tuple from the environment the run actually happened in rather than from what the subject reports about itself, since a subject that self-reports its version can be wrong about it, and one of them was. A register page compiled from the records on every build, with a check mode that fails when the page and the records disagree. A publication gate that keeps a held record off the public page by construction, and refuses to write the page at all if a held record's identifier or its subject's name reaches it by any path. Held records themselves live outside version control, and what is committed in their place is a manifest that fixes each one by sha256 without disclosing a word of what it says, so a record can be **fixed by digest** and
unreadable at the same time. Not *unalterable*, which this sentence said until 23
October 2026: the record lives outside version control, so anyone holding the file can
edit it. What the manifest buys is that the edit then disagrees with a digest already in
git history. That is tamper-evident, and it is the same distinction this project insists
on everywhere else.
That gate was adversarially audited on its own, hunting specifically for a way a held record could reach the public page. No live exploit was found; the file's existing defense in depth is real, and the full compiled HTML is re-scanned for a held identifier as the last step before every write, catching anything upstream code might do wrong. One structural gap was real but not exploitable today: a record_id could exist in both `records/` and `records/held/` with nothing checking for the collision. Traced through by hand and confirmed the build already refused in that exact case, one step later than it should have and by coincidence rather than by design: the public copy's own id happens to be printed on the page, which is what the final rescan actually catches. `load_all()` now refuses the collision directly, at its source, rather than relying on the colliding id happening to be visible page text. Said plainly: this closes a coincidence, not a leak that was reachable, and the difference matters enough to state rather than let the fix read as more urgent than it was.
**Built, the HTTP surface:** `gateway/serve.py`, standard library only. `POST /v1/decisions`, `POST /v1/live-check`, `POST /v1/receipts/verify`, `GET /healthz`. Observe is the default and `--enforce` is required to block; the server refuses to start on a policy it cannot evaluate rather than running with a permissive gap. Every error path, including an unhandled exception in its own handler, returns an explicit non-permit rather than anything a caller could read as approval. That property is the one most heavily covered by its tests: mutating the failure shape to a permit fails 25 of them.
`/v1/live-check` is where the HTTP surface and `gateway/live_adapter.py` meet: `{"ticker": "AAPL"}` fetches that ticker's real daily series from the same authority the suite uses, runs the real classifiers against it, and judges the result under whichever policy the running gateway was started with, not a policy fixed in the CLI script. A caller can supply its own `subject` (the series its own tool produced) to have that judged instead of the authority's own faithful echo, and a `context` object for policies that gate on more than notional. The authority not answering is `502` with `AUTHORITY_UNAVAILABLE`, not a permit and not a crash: there is no reference to compare against, so there is no verdict, not even by default. Two mutations were run against this path: making the authority-unavailable branch answer 200 instead of failing closed, and dropping the caller's context so a real policy's limits go unevaluated. Both are caught.
The authority fetch is cached per ticker for 30 seconds (`healthz` reports the TTL), found by hitting it: testing this endpoint a handful of times in a row was enough to get Yahoo's own rate limit back, which would otherwise show up as the gateway itself looking unreliable. A failed fetch is never cached as an answer, so an authority that comes back does not stay reported down. Two more mutations were run: dropping the cache write entirely, and caching a failed fetch as if it were a real one. Both are caught.
**Built, the decision layer:** `gateway/decide.py`, a bounded policy evaluator with a fixed operator set, no loops and no lookups outside the supplied context, so evaluation over an authenticated context is deterministic and terminating by construction. Ed25519 receipt signing and independent verification, hash-chained. A self-test whose cases were each checked by re-introducing the defect they guard against and confirming they go red.
**Run:** the suite has been run live against a pinned commit of a third party MCP server, twice, under two separately resolved dependency sets for the same source code. Each run produced a record. Both records are held under right of reply, and what they found is not stated here, for the same reason it is not stated on the register: a finding its subject has not yet seen is not one they can answer.
**Not yet true:**
- **Nothing is published.** Every record this registry has issued is held, the notices to their subject are written and unsent, and no reply window has opened. The register today carries its rules and a count of what is being withheld, and no subject at all. A registry that has issued verdicts and published none of them has not yet done the thing it exists to do.
- **No verdict here is checkable by a stranger yet.** The method is, as of this repository: the harness, the probes, the self-test, the record generator, the renderer and the publication gate are all here to be read, run, and attacked. The records are not, because every one of them is held. Until the first one publishes, anything this project says about what it found is a claim about runs that exactly one machine has seen, and an uncheckable verdict is an opinion with a logo on it.
- **There is no persistent issuing identity.** Records are not signed by a key with durable, publicly anchored provenance, and until they are, a signature proves only that the same ephemeral key signed twice.
- **No buyer has paid for a verification.** Nobody has stated what they would pay, or at what point in their process they would want it.
- **The gateway is not inline anywhere.** `gateway/decide.py` decides correctly on inputs handed to it, and `/v1/live-check` decides correctly on a live series it fetches itself, over HTTP, under a real policy. That is all that has been demonstrated. Nothing routes production traffic through it, no HTTP surface is deployed anywhere reachable but a developer's own machine, and nobody is blocking live orders or live data with it. Observe mode is a mode in the code, not a running deployment.
The first record this registry ever issued was withdrawn, because its subject tuple named a version that could not be resolved to anything real. The withdrawal was not deleted and will not be, because a registry that erases its mistakes is asking to be trusted instead of checked. It is not on the register today either, and the reason is worth stating: it is about the same package as the two records under reply, and it is the only subject the page would name at all, so publishing it tells a reader that findings are being withheld about one identified project while showing none of the evidence. That is the accusation the reply window exists to prevent, delivered without the detail its subject would need to answer it. It publishes on the day its successors do.
Those five gaps are the actual state of this project. Everything above is the design they are being built toward.
---
## Run it
Python 3.11 or newer. No dependencies for the self-test.
```
python3 suite/selftest.py
```
That is the first thing worth running and the first thing worth attacking. It runs every classifier in the harness against fixtures that are known to be bad and against fixtures that are known to be clean, and it fails if a classifier misses a planted failure or fires on clean input. A probe that cannot fail is worse than no probe at all, so the self-test is the part of this repository that decides whether any verdict it issues means anything. If you can construct a corrupted response that the suite calls clean, that is a bug in the registry and it is the most useful thing you could send.
To run the suite against a live subject, clone the subject yourself, at a commit you pin, into an environment you resolved:
```
python3 suite/run.py --subject-dir <path-to-subject> --python <path-to-its-interpreter> --entry server.py \
--upstream "<the service the subject reads from>" \
--tool-history <tool that returns price bars> --tool-info <tool that returns entity metadata>
```
The harness speaks raw JSON-RPC over stdio and never imports the subject's code, because a harness that imports its subject is measuring a process it is also part of. The subject tuple in the resulting record is read from the environment the run actually happened in and not from what the subject says about itself.
`--python` accepts an executable path or a command on your `PATH`, such as `python3`. Relative paths are resolved from the directory where you invoke the runner, before it switches to the subject directory. Use the subject's own environment, with its dependencies already installed. A missing or nonexecutable interpreter is refused with exit code 2 before any probes run or a record is written. Correction: the runner previously treated a bare command as a local filename and let invalid interpreter paths reach subject inspection.
`--subject-dir` must be an existing directory. `--entry` is resolved relative to that directory, or may be an absolute path. A missing entry is refused with exit code 2 before observations or recording. Correction: a typo in the entry path previously produced a `COMPATIBILITY` record with a `FAIL_SAFE` startup finding, even though the named script did not exist. This preflight checks paths, not whether the existing subject can start or has working dependencies.
`--upstream` is required and has no default, which is deliberate. Every other field of the tuple is read off the run: the package from the directory, the commit from git, the resolved dependencies from the interpreter you pointed at. That one cannot be, because a server does not have to say where its data comes from and can be wrong when it does. A default there would be a value the record asserts and nobody observed, which is `fabricated_field`, which is one of the causes this suite grades other software for. It was a hardcoded string here until it was caught, and it is named in this paragraph rather than quietly fixed because a registry that hides its own defects has no standing to publish anyone else's.
`--tool-history` and `--tool-info` are required for a worse reason, and it is worth reading before running anything. The probes call the subject by tool name. A name the subject does not expose comes back as a protocol error, and a protocol error is what several of these probes count as *correct* behavior: a tool that refuses a nonexistent ticker through the error channel has passed P01 by design. So a run pointed at a subject that does not have these tools answered PASS on four probes, on evidence that consisted entirely of the tools not being there, and wrote a record with a full subject tuple that looked exactly like a real one. That is a verdict that fails quiet, produced by the suite whose only purpose is to catch verdicts that fail quiet.
The names used to be hardcoded, so this could only happen to someone pointing the suite at their own server, which is the first thing this README tells a stranger to do. The names are now supplied by the operator and checked against the subject's own `tools/list` before a single probe runs, so every error a classifier sees afterward is the subject refusing a question it was actually asked. Run without the flags and the harness prints the tools the subject does expose, then exits without writing a record. The aggregate verdict in that scenario was `INDETERMINATE` rather than `PASS`, because the data-comparison probes had no bars to compare and `INDETERMINATE` dominates `PASS`, so the damage was bounded. Four probe-level PASSes on a subject that was never tested is still the wrong answer, and it is written down here rather than fixed quietly.
The record is written to `--out`, numbered one past the highest number already there. Numbers are never reused, including by records that were withdrawn, and a run whose number is already taken is refused before the first probe rather than allowed to overwrite what is on disk. A record id is an identity that other documents cite, so overwriting one would destroy the evidence while leaving every reference to it resolving, which is the exact shape of failure this suite exists to catch.
### Check that the published page has one author
`brand/register.html` in this repository is the file that is served at `https://nobulex.com/register`. Not a copy of it, not a version of it. The same bytes.
```
shasum -a 256 brand/register.html
curl -sS https://nobulex.com/register | shasum -a 256
```
**This check does not hold, and the reason is structural rather than a lapse.** The
committed file prints
`8659a0b7925debde324cd5ef07871f3e7a7fe27b4d27aab05d8406e99262a637`.
The deployed page prints a different value, and **that value changes every time
the site is deployed**, so pinning it here was always going to go stale. It did:
a figure pinned on 21 October was wrong by 22 October because the site shipped a
correction, and an audit on the same day measured a third value again.
So stop comparing hashes to a number in this file, and run the comparison
instead:
```bash
diff <(cat brand/register.html) <(curl -sS https://nobulex.com/register)
```
**What a reader should expect that diff to contain, and nothing else.** Measured
22 October, 26 changed lines in six categories, none of them a record or a
verdict, though one of them is a retracted claim:
| Category | Why it differs |
|---|---|
| `<meta name="robots" content="noindex,follow">` | present only on the deployed page |
| `<meta name="description">` | different text on each |
| two Vercel analytics `<script>` blocks | injected at deploy, not emitted by the generator |
| an HTML comment | present only in the committed copy |
| the `<p class="note">` paragraph and the direction banner | rewritten on the deployed page |
| the `<div class="oneline">` headline | regenerated on 26 October. The committed headline no longer contains a retracted claim. `suite/check_retracted_claims.py` exits 0 on this file |
**If the diff contains anything outside those six, say so loudly.** This
enumeration said five until later the same day, and the missing sixth was the
page headline. An audit found it by running the command this section
publishes, which is the only reason a hand-counted table of a moving target
ever gets corrected. It should be a script; it is not yet. That is the
property this check exists to protect: the register is compiled from the records
by `suite/render_register.py`, which writes identical bytes to every publish
target in one build, specifically so no hand reaches the page between the
records and the reader.
**What was wrong here before, recorded rather than smoothed over.** This section
previously pinned a specific deployed hash, said the difference was "eleven
lines", and blamed a banner added by hand to the website copy only. All three
were false by 22 October: the hash had moved twice, the diff is 26 lines, and
the banner is present in **both** copies. The committed hash above is checked by
`suite/check_readme_pins.py`, which exits nonzero when it drifts. **The deployed
hash is deliberately not pinned and not gated**, because it is a moving target
and a gate that cannot pass is worse than no gate.
**A third copy disagrees, and it is the one in version control.** On 22 October
the template was corrected to drop two retracted claims and the live site was
redeployed with the fix, but `brand/register.html` was never regenerated. So the
only copy still asserting that Nobulex "is now an inline decision-integrity
gateway... before the action executes" and that it "proves what happened on the
other side" is the committed one. Both are retracted in this file's own words.
`suite/check_retracted_claims.py` now gates that, and **it currently exits 1.**
That is the correct state: the defect is real and the gate is red. It cannot be
fixed in a public checkout, because `render_register.py` rightly refuses to
compile a held count from records it cannot read, so the regeneration has to run
in the tree that holds them. It is deliberately NOT wired into the pre-push hook
until then, because a gate that blocks every push on a known, separately-tracked
defect teaches people to bypass gates.
The fix for the underlying asymmetry is still the one named below: the banner now
lives in `brand/register.template.html` so the generator owns it, and `--publish`
writes it identically to every target.
Correction provenance: the expiry explanation was changed in the template and identically in the committed and website HTML copies, without rerendering held records. The local copies were compared byte for byte. This was a direct static-copy correction, not a fresh record build; the command above independently checks the deployed copy.
This is worth two minutes because it is the one claim on this project that a stranger can settle right now, without waiting on a reply window and without taking anyone's word for anything. The register is compiled from the records by `suite/render_register.py`, which writes identical bytes to every publish target in one build, specifically so that no hand can reach the page between the records and the reader. A downstream copy step is a second author, and a second author of that page is a second chance to publish a name that is under embargo. If those two hashes ever disagree, something edited the published page after the generator produced it, and you should say so loudly.
The hash will change whenever the register legitimately changes, which today means when a record publishes. The value above is the current one.
### Rebuild the register
```
python3 suite/render_register.py
```
On a clone this refuses and writes nothing. The held records are not in version control and the held count on the page is compiled from them, so building here would replace a page stating that four records are held with one stating that none are, which reads as findings having been quietly dropped. The refusal names the records the manifest commits to and could not read. Rebuilding is meaningful where the records live; the hash comparison above needs no rebuild, which is why it is the check a stranger can run.
That refusal is recent. Until it existed the generator built happily in a clone and overwrote the page with a zero count, and the README called that diff expected and told you to `git checkout` it back. It is the same defect this suite exists to find in other people's tools, in this one: an input that could not be read, counted as agreement.
The generator refuses at build time to write a page carrying a held record's identifier, a held subject's name, or a verdict token in the embargo block, and refuses to build at all from a checkout that cannot read every held record the manifest commits to. Those refusals are in `suite/render_register.py` where the conditions can be read rather than taken on trust.
---
## The register
In the original registry concept, the test suite was the product and the
register was where its results were published.
A record is a reproducible, independently checkable object with a subject tuple, a verdict, a loss cause classification, a validity window, and a suite reference. Anyone will be able to pull it, rerun it, and disagree with it in public. If a record cannot be independently reproduced by a stranger, it was never evidence, and it does not belong in the register.
---
## Right of reply
Before any record with an adverse finding is published, the maintainer of the subject receives the full run artifact and has seven days to respond. The reply is published alongside the record, unedited. Adverse means `FAIL_UNSAFE`, `FAIL_SAFE`, or `INDETERMINATE`: any verdict that says in public that a named person's software did not do what it should.
This used to say `FAIL_UNSAFE` only, and the narrower rule was wrong twice over. It was wrong on the merits, because a `FAIL_SAFE` record still tells the world that someone's project is broken, and severity is not what earns a subject the right to be heard; being publicly criticised by a stranger is. It was also wrong mechanically. The register says how many records it is holding, and if the only records ever held were `FAIL_UNSAFE`, then saying "one record is held" published the verdict without publishing the evidence. A finding a reader can infer but nobody can check is worse than one that is fully published, because there is nothing for the subject to answer.
The register may say that a record is held and how many are held. It may not say what a held record found, not even its verdict, and it may not name who the record is about.
The second half of that rule was learned the hard way, one layer down from the first. Stripping the verdicts was not enough. The page still named a subject elsewhere, and the page still said records were being held under right of reply, and right of reply now covers every adverse verdict, so those two published facts join into a third that was never written anywhere: a named project has an adverse finding against it that nobody can see. Whether a verdict token appears on the page is beside the point. The identity is part of the finding, so while any record is held, the register names no held subject at all. Today that means it publishes no subject whatsoever, which is the true state of a registry whose every record is waiting on someone else. The renderer refuses to write the page if a held subject's name reaches it, in the same way it already refuses on a held record's identifier.
Each held record is committed to by sha256 in a manifest that is in version control while the record itself is not. When one publishes, anyone can hash it and check it against the value committed the day it was issued. That is what turns "the verdict was fixed before the window opened, and nothing was quietly softened while the subject was drafting a reply" from something the registry asserts into something a stranger can check. It is not a formal commitment scheme, since there is no nonce and it leans on the record's own entropy, and it is not a timestamp, since the commit date is worth exactly what the person who set it is worth. Both of those are gaps, and they are listed as gaps rather than papered over.
The reply is **not adjudicative.** It cannot alter the verdict, and no part of it is negotiated. The verdict describes what the suite observed under pinned conditions, and the only thing that produces a different verdict is a different run: a new commit, or new conditions, pinned and executed again. The request, the delivery, and the re历史前置条件说明(补充参考)
Python 3.11 及以上;自检与示例无需安装依赖包;live_adapter 示例需联网获取行情数据(数据源与可用性待核验);MCP/客户端配置资料缺失,接入方式待核验;商业服务与第三方数据费用待核验;无证据表明存在中国大陆网络或服务适配。
项目来源与清单版权许可
https://github.com/arian-gogani/nobulex-registry
整理来源:https://github.com/slavakurilyak/awesome-ai-agents
清单版权与许可:
MIT License
Copyright (c) 2024 Slava Kurilyak
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.