@333eco/corpus 1.2.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -0
- package/dist/corpus.json +1420 -127
- package/package.json +3 -2
- package/src/base-tools.mjs +72 -0
- package/src/program-tools.mjs +181 -0
- package/src/prompts.mjs +134 -0
- package/src/report.mjs +104 -0
- package/src/resources.mjs +215 -0
- package/src/results.mjs +40 -0
- package/src/search.mjs +176 -0
- package/src/server.mjs +143 -65
package/README.md
CHANGED
|
@@ -48,8 +48,94 @@ manufactured on a schedule.
|
|
|
48
48
|
| Tool | Returns |
|
|
49
49
|
| --- | --- |
|
|
50
50
|
| `search_corpus` | matching documents, provenance envelope, and an excerpt around each match |
|
|
51
|
+
|
|
52
|
+
⭐ **`search_corpus` matches the phrase first, then all terms.** An exact substring hit
|
|
53
|
+
always wins and ranks above everything else — which keeps `B-Heart` and `Re-Tip` precise,
|
|
54
|
+
since the tokeniser holds hyphenated marks together. If the phrase is absent, a document
|
|
55
|
+
matches when it contains **every** term somewhere (AND, never OR), ranked by how tightly
|
|
56
|
+
those terms cluster. Each hit reports `match: "phrase" | "terms"`, because a term match's
|
|
57
|
+
excerpt need not contain the words you typed.
|
|
58
|
+
|
|
59
|
+
⚠️ **This replaced a bare `indexOf`, and a real caller paid for it.** The first external
|
|
60
|
+
client to reach the hosted endpoint searched `"gratitude alignment human wellbeing kindness"`
|
|
61
|
+
and got **zero results** — while `gratitude` alone returns 109, `alignment` 50, `kindness` 50.
|
|
62
|
+
Nothing was missing from the corpus; the matcher demanded that exact five-word string appear
|
|
63
|
+
verbatim. **A zero result was recording the matcher's limits while being read as a gap.**
|
|
64
|
+
|
|
65
|
+
⭐ On a zero result the response now names **which terms appear in no document at all**
|
|
66
|
+
(`absent_terms`), so a dead end says something instead of nothing.
|
|
51
67
|
| `get_document` | one document in full — canonical text, never a summary |
|
|
52
68
|
| `list_documents` | slugs, titles, genres, licences, provenance summaries |
|
|
69
|
+
| `list_predictions` | the research program's pre-registered predictions, with falsifiers and status |
|
|
70
|
+
| `get_prediction` | one prediction, plus the provenance envelope of the paper that registered it |
|
|
71
|
+
| `get_program` | the program's hard core, chapters, stopping rule and count reconciliation, verbatim |
|
|
72
|
+
|
|
73
|
+
⭐ **The three program tools return the STATING PAPER's envelope, not the register's.**
|
|
74
|
+
That is the register's own instruction rather than a design flourish: *"verify the
|
|
75
|
+
stating paper against its stored proof rather than trusting this register — this
|
|
76
|
+
file is a convenience index, and the proofs are the evidence."* A prediction's
|
|
77
|
+
authority is the paper that registered it, so that is the hash, DOI and
|
|
78
|
+
OpenTimestamps command a caller gets back. Every field is a verbatim table cell,
|
|
79
|
+
and the build refuses to emit one that is not — a field must match a *complete*
|
|
80
|
+
cell of its source, because a fragment of a cell is still a substring of it.
|
|
81
|
+
|
|
82
|
+
The program tools appear only when the index carries a program block. An index
|
|
83
|
+
built over a corpus without one advertises three tools, not six.
|
|
84
|
+
|
|
85
|
+
## Resources
|
|
86
|
+
|
|
87
|
+
Every document is also an MCP resource at `corpus://<slug>` — listed by
|
|
88
|
+
`resources/list` (paged), described by the `corpus://{slug}` template, and read by
|
|
89
|
+
`resources/read`.
|
|
90
|
+
|
|
91
|
+
⭐⭐ **A resource carries its provenance IN THE TEXT, not beside it.** A tool
|
|
92
|
+
response wraps a document in an envelope and the caller reads the envelope. A
|
|
93
|
+
resource is consumed differently: clients hand its contents straight to a model as
|
|
94
|
+
context, and a `mimeType` field does not travel with a quotation. So every read
|
|
95
|
+
returns a `[PROVENANCE — corpus.333.eco]` header — licence and whom to attribute,
|
|
96
|
+
sha256 and **what it does not cover**, both DOIs, the OpenTimestamps command, and
|
|
97
|
+
the one-line `curl … | shasum` check — followed by the document verbatim.
|
|
98
|
+
|
|
99
|
+
This is the letters' rule applied a second time. Voice in the letters is marked
|
|
100
|
+
inline rather than in metadata because *with a field an agent must LOOK to know;
|
|
101
|
+
with a marker it must STRIP not to.* The same asymmetry decides this.
|
|
102
|
+
|
|
103
|
+
`subscribe` and `listChanged` are deliberately not declared: the corpus is fixed
|
|
104
|
+
for the life of a build, so a subscription would promise notifications that can
|
|
105
|
+
never fire.
|
|
106
|
+
|
|
107
|
+
## Prompts
|
|
108
|
+
|
|
109
|
+
Three worked examples of the API — `orient`, `verify_a_quote(slug)`,
|
|
110
|
+
`what_would_falsify(claim)` — surfaced as slash commands in clients that support
|
|
111
|
+
them, with `slug` autocompleted by `completion/complete`.
|
|
112
|
+
|
|
113
|
+
⛔ **A prompt here may describe the API. It may never describe the subject
|
|
114
|
+
matter.** The moment one says something about the corpus's claims, this server has
|
|
115
|
+
begun editorialising on its own documents — which is precisely what the provenance
|
|
116
|
+
envelope exists to make unnecessary. There is deliberately no "verify before
|
|
117
|
+
citing" prompt: that would be a *rule* where the server already has a *property*,
|
|
118
|
+
since every document by every route arrives behind a header the reader must
|
|
119
|
+
actively strip.
|
|
120
|
+
|
|
121
|
+
## Attribution is a build-time property
|
|
122
|
+
|
|
123
|
+
Seven documents are CC-BY and the rest CC0. A CC-BY document that names no author
|
|
124
|
+
hands every consumer an obligation nobody can discharge, so **the build fails**
|
|
125
|
+
rather than serving it — the same reasoning as the licence gate: a property, not a
|
|
126
|
+
rule someone has to remember.
|
|
127
|
+
|
|
128
|
+
| `list_predictions` | the research program's pre-registered predictions, with falsifiers and status |
|
|
129
|
+
|
|
130
|
+
**Results are structured.** Every tool returns `structuredContent` — the typed
|
|
131
|
+
object — and uses `content` for the human form: the document text with its
|
|
132
|
+
provenance header for `get_document`, readable excerpts for `search_corpus`, a
|
|
133
|
+
compact serialisation for the rest. ⛔ **Nothing is sent twice.** The spec's
|
|
134
|
+
back-compat advice is to serialise the JSON into `content` as well; measured, that
|
|
135
|
+
doubles every response (1.76×–2.00×), so this server splits by role instead —
|
|
136
|
+
`content` is read, `structuredContent` is validated, and the two carry different
|
|
137
|
+
things. `outputSchema` is deliberately not declared yet: a schema binds the server
|
|
138
|
+
on every future change, and the envelope is still moving.
|
|
53
139
|
|
|
54
140
|
**Text is returned verbatim and is never summarised.** Not a stylistic
|
|
55
141
|
preference — a summary cannot be hash-verified, so summarising at the server
|
|
@@ -190,6 +276,104 @@ npm run deploy # sync + wrangler deploy
|
|
|
190
276
|
{ "mcpServers": { "corpus": { "url": "https://corpus.333.eco/mcp" } } }
|
|
191
277
|
```
|
|
192
278
|
|
|
279
|
+
## Telling us what is missing, or broken
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
npx @333eco/corpus --report-gap "what you looked for and did not find"
|
|
283
|
+
npx @333eco/corpus --report-bug "what went wrong, and what you expected instead"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
⭐⭐ **A command, not telemetry, and the difference is the whole point.** The most
|
|
287
|
+
useful thing a corpus server can learn is what someone went looking for and did
|
|
288
|
+
not find. The hosted endpoint learns that from its own callers as a property of
|
|
289
|
+
being the server they called. This package runs on *your* machine, so collecting
|
|
290
|
+
it here would be an outbound report about your private reading — and the guard
|
|
291
|
+
against that is not a consent prompt or an opt-out flag. **It is that the serving
|
|
292
|
+
path cannot reach the code that sends.** `server.mjs` loads `report.mjs` with
|
|
293
|
+
a dynamic import inside the argv branch, so a normal session never reads the file
|
|
294
|
+
off disk at all.
|
|
295
|
+
|
|
296
|
+
⭐ **Both flags share one module and one dynamic import**, so the second kind
|
|
297
|
+
added no second way into the network — which is why generalising was right and
|
|
298
|
+
copying the file would have been wrong.
|
|
299
|
+
|
|
300
|
+
The command prints the entire payload before sending it, and the payload is the
|
|
301
|
+
text you typed plus the version you have. ⚠️ **A bug report carries exactly the
|
|
302
|
+
same payload as a gap report, deliberately** — attaching a node version and
|
|
303
|
+
platform would be useful to whoever fixes it, but it would give the command two
|
|
304
|
+
different promises about what it sends, and the promise is the valuable part.
|
|
305
|
+
Anything about your environment that matters, put in the text; then you have
|
|
306
|
+
said it on purpose. No machine id, no username, no
|
|
307
|
+
hostname, no path. ⭐ The receiving end deliberately does not record the country
|
|
308
|
+
it could resolve for free: a voluntary note about a missing document has no use
|
|
309
|
+
for where the sender was standing, and collecting a thing because it is available
|
|
310
|
+
is how a narrow purpose widens.
|
|
311
|
+
|
|
312
|
+
⚠️ **The honest limit, because the claim changed shape when this was added.**
|
|
313
|
+
Before it, *"this package makes no network call"* was verifiable by
|
|
314
|
+
`grep -r fetch src/` returning nothing — the strongest kind of evidence, since it
|
|
315
|
+
needs no reasoning. The claim is now narrower: **there is exactly one `fetch` in
|
|
316
|
+
the package, it is in `src/report.mjs`, and that file is imported from exactly
|
|
317
|
+
one place — a branch requiring an explicit flag.** Still checkable in under a
|
|
318
|
+
minute, but it is a chain of two facts rather than one absence.
|
|
319
|
+
|
|
320
|
+
## What the remote server records
|
|
321
|
+
|
|
322
|
+
⛔ **The npm package records nothing and sends nothing.** `npx @333eco/corpus`
|
|
323
|
+
runs on your machine, reads a local file, and makes no outbound request of any
|
|
324
|
+
kind. Everything in this section is about `corpus.333.eco` and only about it.
|
|
325
|
+
|
|
326
|
+
The asymmetry is deliberate. The hosted endpoint already sees every request it
|
|
327
|
+
answers, so writing down what it was asked adds no reach it did not have. The
|
|
328
|
+
same lines inside the package would be an outbound report about a stranger's
|
|
329
|
+
private reading, which is a different artifact — and not one this is going to
|
|
330
|
+
become.
|
|
331
|
+
|
|
332
|
+
⭐⭐ **No per-caller identity is computed anywhere.** The client label is the
|
|
333
|
+
*software's* name, taken from the `clientInfo` it volunteers at handshake —
|
|
334
|
+
`claude-code`, `cursor` — never an IP, never a hash of one, never a cookie. Every
|
|
335
|
+
user of a given client is one label. That is not a promise to behave well: there
|
|
336
|
+
is no code path in `worker/src/telemetry.mjs` that derives a per-caller id, so
|
|
337
|
+
there is nothing to leak, sell, subpoena or regret later. The question the server
|
|
338
|
+
wants answered is *which clients reach it*, and that question needs no persons in
|
|
339
|
+
it.
|
|
340
|
+
|
|
341
|
+
⭐⭐ **The only search text ever stored is a search that found nothing.** The
|
|
342
|
+
reason to log queries at all is to learn what the corpus is missing; a query that
|
|
343
|
+
*succeeded* tells you only what a caller was reading, which is their business.
|
|
344
|
+
So the successful query has no storage path — an absent branch, not a redaction
|
|
345
|
+
step someone has to remember to keep. Remove the enforcer and nothing breaks,
|
|
346
|
+
because there is no enforcer.
|
|
347
|
+
|
|
348
|
+
| Channel | Carries | Why it exists |
|
|
349
|
+
| --- | --- | --- |
|
|
350
|
+
| Analytics Engine | one row per JSON-RPC call: method, tool, slug, client label, country, protocol, corpus version, result count, error flag, duration — plus the query text **when and only when it matched nothing** | counting; queried by SQL, stays at Cloudflare |
|
|
351
|
+
| `thonly.org/api/track` | `corpus_connect` (a handshake) and `corpus_error` (the corpus asset failed to load) | the two things worth interrupting someone about |
|
|
352
|
+
|
|
353
|
+
⛔ **Not one beacon per tool call.** An agent working through the corpus fires
|
|
354
|
+
dozens of calls in seconds, and a notification channel that reports each of them
|
|
355
|
+
is a channel nobody reads. The beacon fires on the *handshake*, and the receiving
|
|
356
|
+
function pushes only the **first sighting of a client label**, counting every one
|
|
357
|
+
after it in silence — so a notification means *a client we have never seen
|
|
358
|
+
appeared*. `corpus_error` is exempt and always pushes: it is rare by construction,
|
|
359
|
+
and silence is the wrong default for an outage.
|
|
360
|
+
|
|
361
|
+
⚠️ **A dead beacon must not look like a quiet one.** If the receiving allowlist
|
|
362
|
+
changes, the POST 403s and the pushes simply stop — indistinguishable from *no new
|
|
363
|
+
clients this week*, which is exactly the reading that would let it stay broken for
|
|
364
|
+
months. So the delivery status is written to Analytics Engine as its own row:
|
|
365
|
+
silence on the phone is then something you can go and check rather than infer.
|
|
366
|
+
|
|
367
|
+
⚠️ **`ANALYTICS` is unbound under `wrangler dev` without `--remote`.** The module
|
|
368
|
+
degrades to a no-op rather than throwing, so a local session looks entirely normal
|
|
369
|
+
and records nothing — expected, and worth knowing before reading an empty dataset
|
|
370
|
+
as a finding.
|
|
371
|
+
|
|
372
|
+
The endpoint discloses all of this in its own `GET /` response, under `records`.
|
|
373
|
+
A privacy policy is a page someone has to go and find; this is the endpoint
|
|
374
|
+
describing itself, in the one response a caller gets for free before doing
|
|
375
|
+
anything, so the disclosure travels with the thing it is about.
|
|
376
|
+
|
|
193
377
|
## Client configuration
|
|
194
378
|
|
|
195
379
|
```json
|