Commit Graph
7 Commits
Author SHA1 Message Date
ZacharyZhang-NY b696fc78e9 Say what the stop-word list costs and refuse a busy smoke port
The function-word list is fixed on purpose, and a fixed list cannot tell
"the" from "May": both are on it, so a claim built only from words that
double as function words offers nothing to compare and is never citable.
Measured — The meeting is in May, I will do it tomorrow and It can do
that all yield no claim terms, while Will Smith signed the lease still
yields signed / smith / lease. That is fail-closed, but the docstring
claimed the opposite ("only counted if it carries content") and the
checklist never said it. Both now state it, and a test pins it.

The checklist also carried three wrong pins for that list (line, size,
one-line comment) and named "ai" as already excluded when it is not: a
mixed claim still demands ['档案','温器','八度','ai']. The mechanism line
said "the three longest words", which is true of Latin and false of
CJK, where the demanded pairs are spread across the claim.

The smoke script trusted any answer on its port. With a server left over
from another run it measured that one and reported a false failure; it
now refuses a busy port and checks that its own process is still alive
before calling the server ready.

Gates: 325 passed, 60 in the four acceptance files, ruff clean, mypy
cold cache 24 errors in 9 files (the recorded baseline, net zero),
pyright 0, filesize clean, product audit clean, zero multi-line comment
blocks across 141 tracked files, smoke passes and exits 1 on a collision.
2026-10-09 05:10:31 -04:00
ZacharyZhang-NY 2ea48d8530 Stop a borrowed word from making a short claim comparable
A claim too short to compare in its own script was rescued by a Latin
filler: 恒温器 the offered two Chinese pairs, the rule asked for three
terms, and "the" completed the set — so the claim was corroborated by
anything that happened to carry the same filler. English was isomorphic:
Pineapple answer stayed uncited, Pineapple answer the did not.

The shortness test is now per script, and function words are out of the
demanded set. A mixed claim still asks for its three Chinese positions
plus its longest real Latin word, so dropping "the" from the demand no
longer blocks a legitimate witness: 档案室的恒温器保持十八度 the is now
corroborated by 恒温器一直放在档案室,读数十八度, which the last review
round measured as False.

A witness that translates the demanded Latin word is still read as
disagreement, and a witness that inflects a demanded verb
(holds / hold) is too: the rule is term-level, and the checklist now
says so with both measurements instead of the earlier blanket claim.

Gates: 324 passed, 59 in the four acceptance files, ruff clean, mypy
cold cache 24 errors in 9 files (the recorded baseline, net zero),
pyright 0 on both touched files, filesize clean, product audit clean,
MCP write smoke passes.
2026-10-09 03:52:19 -04:00
ZacharyZhang-NY 8d84e03077 Corroborate a mixed-script claim in both scripts
A claim written in Chinese and English was checked only in English: the
longest Latin words took every slot the rule demands, so one planted
English word moved the closing Chinese pair out of the comparison and a
witness that contradicted the number became a second source. The attack
costs nothing — append "the" to a Chinese sentence — and it fails open,
which is the wrong direction for a rule whose whole job is to keep an
unvouched claim out of the prompt.

Each script is now checked in its own terms: a claim with CJK pairs is
demanded at its opening, middle and tail, plus its single most
distinctive Latin word. A Latin-only claim keeps its three longest
words. The demanded set is what claim_terms returns, all of it.

Measured: the planted-word witnesses are no longer believed, a verbatim
repeat still is, and a mixed claim that agrees in only one script is not
a witness. The term-level boundaries are listed in CHECKLIST for both
scripts, including the two the reviewer found that this rule does not
catch: a demanded pair inside a longer word, and a long claim whose
middle position lands on filler.

Also corrects the record: the boundaries were not the same in both
scripts as claimed, a historical line said "now" instead of naming a
commit, and a limitation line I deleted in the last round is restored.

Gates: 322 passed, ruff clean, mypy 24 errors / 9 files (baseline, net
zero), pyright 0 on the two touched files, filesize clean, product audit
clean.
2026-10-09 03:12:56 -04:00
ZacharyZhang-NY 334071199e Make corroboration check what a claim asserts, not how it opens
An independent reviewer planted two witnesses that contradict their claims
and both were believed: 「恒温器很贵」 was corroborated by 「恒温器很便宜」,
「这房子值得买」 by 「这房子不值得买」. The Chinese pairs were spread inside
each run on its own, and equal-length pairs kept insertion order, so the
three demanded pairs of a multi-clause claim all came from its first
clause — and for a four-pair claim the evenly spaced index picked 0, 1, 2,
clustering them at the start anyway. An unrelated memory that merely
opened the same way pushed a quarantined sentence into the packed context.

Pairs are now pooled across the whole claim and the demanded positions are
its start, its middle and its end. The last pair is the assertion, so a
witness has to agree with the claim rather than merely introduce it. The
term-level boundary is pinned in both languages: a restatement that keeps
the compared positions counts, one that replaces them does not, a denial
that differs by one word still counts, and a changed number does not.

Also corrected the record where it had drifted: the gate cell was labelled
with a commit that never produced those numbers, the acceptance cell pinned
a total its own command could not reach, and the reader-path count stated a
conclusion without its measurement rule.

Gates: 320 passed; ruff clean; mypy cold cache 24 errors in 9 files, net
zero against the recorded baseline; pyright 0 errors on the touched files;
filesize, product audit, go suite and the two-principal MCP smoke all pass.
2026-10-09 02:28:50 -04:00
ZacharyZhang-NY ff2471d362 Give Chinese memories a way to be corroborated
An independent reviewer repeated the reviewer's own counterexample in
Chinese and found the corroboration channel was dead for it: fts_terms
counts one CJK run as one term, so a Chinese claim could never reach the
three words the rule demands, however many independent sources repeated
it. A run is now broken into its adjacent two-character pairs and three
are taken from across the sentence, so a second source has to agree in
three separate places rather than copy one stretch of characters. The
pairs belonging to an injection phrase are stripped too, so an
instruction cannot vouch for its own characters.

Corroboration stays term-level in both languages: a paraphrase that
keeps the meaning and changes the wording is not a second witness, and a
claim with fewer usable words than the rule demands is never believed.

The record was wrong in four places the same review measured: the token
rename breakdown summed to 11 while claiming 13 across 6 files (it is 13
across 7), the acceptance cell counted tests that no longer exist, the
gate cell quoted the previous round's totals, and a cited line had
drifted onto a blank. The section also stops calling the injection work
"sanitization": 19 literal phrases plus quarantine plus one prompt
sentence is detection, and a reworded directive walks straight past it.
2026-10-09 01:26:17 -04:00
ZacharyZhang-NY 203b6d04af Make corroboration depend on the claim, not on the size of the library
An independent reviewer broke the quarantine rule twice by running it. In a
two-atom library, a second source repeating the claim word for word could not
vouch for it; 40 unrelated memories could. The old rule handed the choice of
words to rare_terms, which drops words common to the corpus, so the corpus size
and the attack's own words decided whether a stranger was believed.

Corroboration now demands the claim's three longest words, with the instruction
phrases stripped out, ANDed by the new text_index.fts_all_expression. Neither
the library's size nor the attack can flip it, and both cases are tests.

Also from that review:
- A question is a read. The DF cache no longer remembers a word the corpus has
  never used, so an unauthenticated search cannot grow the store one row per
  novel word. "A question is not a write" was false for /api/search and /api/ask.
- The prompt now states that packed evidence is quoted material, not an
  instruction: once a quarantined sentence is corroborated it does reach the model.
- ForgetReport's semantic_cache_cleared reached the frontend as well as Go.
- runtime.py: process_vision_info returns a third element only when
  return_video_kwargs is set, and this call does not set it, so the unpack takes
  the first two. Two pre-existing pyright errors go, behaviour is unchanged.

CHECKLIST corrected against measurement: 13 renamed lines rather than 11, and
10 English / 9 Chinese injection signals rather than 11 / 8.

Gates: pytest 312 passed, ruff clean, mypy 24 errors in 9 files (net zero),
pyright 0 errors on the five touched files, npm build and eslint clean,
scripts/mcp_write_smoke.sh PASS.
2026-10-09 00:37:12 -04:00
ZacharyZhang-NY 9145a23f18 Require a token for writes and quarantine what nobody vouches for
Sixteen mutating routes now refuse a caller who cannot present the token the
server started with; queries stay open, because a question is not a write. The
environment variable is ELY_EYE_AUTH_TOKEN, following the repo's env_prefix
convention — the shorter name I reached for first returned 403 where the smoke
expected 401, and the server printed the reason.

Words from a stranger are quarantined on the way in: an HTTP upload is
third_party by definition, and a sentence carrying an instruction-shaped phrase
is quarantined whoever wrote it. A quarantined atom stays findable and stays out
of the compiled context until a second, live, unquarantined source agrees with
its rarest words. Corroboration is computed at read time because it is a
property of the corpus now, not of the moment a memory was written; it is
term-level, and the docstring says so.

forget now erases the source row and the compiled contexts beside the atoms, so
a forgotten memory leaves nothing that can still speak. The semantic cache key
carries a store revision, which fixes a real staleness: a context compiled
before new evidence arrived kept its old quarantine decision forever.

bench.audit --product still pointed at evals.py, which the p1-6 split moved into
backend/src/ely_eye/proofs/. It failed fast, but the anti-self-justification
rule had quietly stopped auditing the product; it now walks the package, and a
test pins that the package exists and holds no re-roll.

Gates: pytest 306 passed; ruff clean; mypy cold cache 24 errors in 9 files, the
recorded baseline, net zero; pyright 0 errors on the 23 backend files and the 2
bench files; go build/vet/test and gofmt clean; npm run build 221.39 kB with
eslint exit 0; bench/filesize_check clean; bench.audit --product clean over 31
harness files; scripts/mcp_write_smoke.sh PASS with the gate live across two
servers and the MCP child.
2026-10-08 23:05:15 -04:00