Turning a per-episode citation gate into a corpus that accumulates, so a reach that was already found or already refuted is never re-reported as new.
The OS could already prove that every sentence about an episode pointed at a real transcript line. It could not do two other things, and both were costing real work. It could not cite anything outside its own episodes, so a Harmon interview or a Saved by the Bell parallel had to be asserted rather than shown. And every artifact was scoped to one episode, so a connection found while reading S03E04 was invisible when S05E01 came up next.
Report the corpus and the gate as two separate numbers. The corpus holds 27 real episodes, 7,212 transcript lines, 0 outside sources, 1 claim, 1 hypothesis. That is every transcript the fandom wiki carries, across seasons 1, 2, 3, 4 and 6.
Two of them are truncated at source and flagged PARTIAL: S01E05 at 34 lines and S02E04 at 52, against a median of 264. Those support a claim about what is present and never about what is absent. Season 5 has no transcripts at all, so that stretch of the show is simply not in evidence.
evidence/ immutable, line-indexed, never overwritten
<EP>/ Community episode transcripts
_sources/<ID>/ interviews, commentary, articles, other shows
interpretation/<EP>/ per-episode artifacts, every line cited
claims/ accumulated cross-episode findings, every line cited
hypotheses/ proposed but unconfirmed, must name its own test
rejected/ failed connections, with the reason, never re-proposed
log.md append-only ledger of every add, promotion, rejection
Sources ingest into their own namespace and cite exactly like an episode.
[HARMON-AVCLUB-2011-09-22:L88] [COMMENTARY-S02E19:L14-L21]
[SBTB-S03E12:S3] [TVTROPES-COMMUNITY-SHOUTOUTS:L7]
The two namespaces are disjoint by construction. An episode code is
S<dd>E<dd> with no hyphen, a source id is uppercase with at
least one hyphen, so nothing can be read as both. Tested exhaustively rather
than by sampling: zero ids parse as either.
Dialogue sources reuse the existing episode parser, so speakers, scenes and timestamps work unchanged. Prose is addressed by paragraph instead of by physical line, because a hard-wrapped article addressed per line has every id invalidated the moment somebody re-wraps it.
| Command | What it does |
|---|---|
canon check | Scores a proposal: NEW, ALREADY-KNOWN, PREVIOUSLY-REJECTED, or SIMILAR, with the match and its score |
canon add | Records a source-backed claim. Refuses a duplicate by default |
canon hypothesis | Records a proposal. Refused unless it names its own test |
canon reject | Moves a record to rejected. Refused unless it carries a reason |
canon promote | Hypothesis becomes a claim once its test is met |
lint_canon.py | The cross-episode audit the per-episode gate structurally cannot run |
This is enforced by grammar rather than by policy. A canon id is lowercase and
hyphenated. A citation token requires an episode code or an UPPERCASE source id.
So a canon id cannot parse as a citation token at all, and one mistaken synthesis
physically cannot travel through twenty linked pages. The lint additionally fails
any line that leans on a [[see-also]] link without carrying its own
evidence citation.
Scoring a proposal only against accepted claims makes every rejected idea return as NEW on the next pass, and the loop never converges. Scoring runs against claims, hypotheses and rejections, with rejections winning ties, so a refuted reach can never come back as a discovery.
$ os.py canon check --text "Pierce's streets ahead line comes from Saved by the Bell"
VERDICT: PREVIOUSLY-REJECTED (score 0.95)
rejected because: No Saved by the Bell episode contains it.
The phrase is original to Community.
This is NOT a new discovery. Do not report it as one.
Scoring is deterministic and offline. Text similarity is the primary signal and shared citations boost it, because two findings pointing at the same evidence lines are usually the same finding. No model call, so a verdict is reproducible and testable.
Both defects are now regression-tested, and both are written into the skill so they cost nothing next time.
The citation normalizer keeps apostrophes on purpose, because a quote match
needs annie's to stay one word. That pushed episode's
straight into a generated record id, producing
claim-abed-names-the-episode's-form, which the id pattern then
refused. Every claim whose text contained an apostrophe was unrecordable.
It passed every unit test and failed on the first live command. The fix strips in the slug builder alone, since the normalizer and the id pattern are both load bearing elsewhere, and the id builder now validates its own output so the whole class fails loudly instead of on one unlucky input. The standing lesson: unit tests passing is not the command line working.
python3 x.py | tail -3; echo $? reports the exit status of
tail, which is always zero. That looked like a script returning
success on failure and sent me chasing a defect that did not exist. Exit codes
now get measured with no pipe.
The first real episode ingested failed the citation gate on five quotes that
were genuinely in the transcript. The quote builder rewrites an inner
" as ' so the token stays parseable, and the normalizer
stripped " while keeping '. Producer and checker
disagreed, so every real line containing a quoted phrase became uncitable.
An apostrophe between two word characters is a contraction and stays
(it's, annie's). An apostrophe at a word boundary is a
quotation mark and goes. Both sides of every comparison run through the one
normalizer, so they cannot drift apart again. That change loosens matching, so it
shipped with four rejection tests: swapping one real word, dropping a word
mid-quote, an empty quote, and a punctuation-only quote all still fail.
The transcript converter delinked [[Category:Season 2 Transcripts]]
into plain text before dropping housekeeping links, and the ingester read
Category: as a speaker prefix. One line of invented evidence, one
fictional character named CATEGORY. Housekeeping links now get dropped first, and
the episode was re-ingested clean. The old copy is parked rather than
overwritten, which is the standing rule for evidence.
The fandom pages are not uniform, and assuming they were broke the ingest.
The colon sits inside or outside the bold depending on the page
('''Pierce:''' against '''Pelton''':), and handling only
one shape produced a full-length transcript in which every line had lost its
attribution. It passed the citation gate, because the gate verifies that cited
lines exist and has no way to know the speakers went missing.
This is the difference between a tool running and a tool working. The importer now refuses at zero speakers and below 80 percent of content lines accounted for, and the metric counts dialogue plus stage direction plus continuation, since a speech broken around a stage direction resumes with no prefix and the ingester inherits the speaker. Getting that last part wrong read a sound transcript as 29 percent broken.
dossier.md is a merge of the eleven artifacts and no tool
produces it, so re-analyzing regenerates every artifact and leaves the dossier
describing the previous evidence. When S01E04 was re-ingested from the worked
sample to the real transcript, the stale dossier failed on 110 citations into
lines that no longer existed. Analyze now parks a stale dossier rather than let
it sit there reading as current.
The essay tool scrubs banned constructions out of its own prose, and its list
includes contrastive while. It ran over the whole composed sentence,
including the inside of the citation token, so a real line reading "peed on my
car while I was parking it" was published as "peed on my car, I was
parking it". Three episodes failed the gate on quotations that were genuine until
the style filter touched them.
A citation token carries evidence, not prose, and is never subject to a voice rule. The scrubber now cleans only the gaps between tokens.
The quote builder has to remove inner double quotes or the token stops
parsing, and it used to substitute an apostrophe. In a line like
she got "B"s that yields 'B's, whose second apostrophe
now sits between two word characters, reads as a contraction, and survives
normalization, while the evidence side became b s. One more genuine
line reported as a fabrication. It substitutes a space now, matching what the
normalizer does to a double quote on the other side.
test_source_gate.py 41/41 exit 0 widening the corpus did not widen the gate
test_canon.py 59/59 exit 0 refuted reaches stay refuted
validate_citations.py PASS exit 0 27 of 27 episodes, 19,800 tokens
lint_canon.py CLEAN exit 0 2 records, 5 tokens
Both proof suites are weighted toward the refusals rather than the happy path, because the failure mode that matters is a gate quietly starting to accept what it used to reject. An invented line number, a range past the end, an un-ingested source id, an invented scene and a fabricated quote are all asserted to still fail.
The method earns credibility when it can reject one of its own proposed connections.
That is the whole point of the rejected layer. A canon that only records what it accepted has no way to show its own discipline, and no way to stop the same attractive-but-wrong reach from being rediscovered every few months. The hypothesis layer does the matching job for claims that are interesting and not yet supported: it forces the proposal to name what would settle it, and it flags itself as stale after ninety days so a permanent maybe never quietly hardens into a settled reading.
Phase 3 is the ingest backlog, and it needs real transcripts rather than the sample. In order: the remaining episodes, then the Harmon interviews and commentary tracks, then the outside-show comparison episodes. One source per pass, with all four gates green each time.