Preregistration notes
The analysis decisions we write down before the data they govern exists: exclusions, changes to how data is collected, the stopping rule. This is the file exactly as it is in our repository, unrendered, so what you read is what was hashed.
Checking a version
Download the document and the .ots proof for its hash, then run node verify-anchor.mjs file preregistration.md <hash>.ots (the script is linked from the methodology page), or ots verify from the OpenTimestamps client.
A pass means that exact text existed by the Bitcoin block the proof names. Versions from before 2 October 2026 were first stamped that day; any earlier date they carry is git's, which we control.
Every version
862233158b010af6ced167184b710fe6148ce7050ca2d5a6c2abfefde6e5cd3fthis one
first in commit 4282eda, dated 2026-10-09T18:14:37+02:00 by git · in Bitcoin block 970662 · download .ots
7ee1a63f1b7c31adc0d732abd17a87126fbea94dd1b318f4946636cd68063a20
first in commit 95fa408, dated 2026-10-09T15:41:42+02:00 by git · in Bitcoin block 970639 · download .ots
15eb1525a80f0ca585ce1270d95c49c0e89c4010e2e7ddca77691be6512f1244
first in commit 59eceb1, dated 2026-10-09T10:27:47+02:00 by git · in Bitcoin block 970602 · download .ots
292ee4d590c5354e8dc4f2197131a8b63e6c5b089d39be5f827f21b5ed44b0a3
first in commit 24d5ff4, dated 2026-10-08T16:00:42+02:00 by git · in Bitcoin block 970502 · download .ots
e7fde64184261ac0cf094cb6fc4a9c8e5ee87ce490112fa787ef23bec126324f
first in commit 8655025, dated 2026-10-08T14:29:48+02:00 by git · in Bitcoin block 970491 · download .ots
ac17a2a8764e6832ea6e6170acc3a2286de1482926ae39a66f531e6035f2d51a
first in commit f459945, dated 2026-10-06T20:13:54+02:00 by git · in Bitcoin block 970214 · download .ots
4209ef4c94cd5913ed78309c223c34688141c96a236592eab0489a96f435c324
first in commit 6e48ec6, dated 2026-10-06T20:11:57+02:00 by git · in Bitcoin block 970214 · download .ots
bd345e5b27cd65b5b287d167ef2ca1910da2442067c3dbfdbd1cd8dfea59e812
first in commit 039fffc, dated 2026-10-04T15:49:15+02:00 by git · in Bitcoin block 969860 · download .ots
87877873029092ca8815a38c1417a45ac15b3c8029b253af57651265b43d1876
first in commit 26f9c5f, dated 2026-10-04T13:16:05+02:00 by git · in Bitcoin block 969834 · download .ots
0d8d81d07fcd0df5fb31d3cb4be7b808447db79be5818e715c4224c3fc68f20c
first in commit 1c0fb79, dated 2026-10-03T00:54:53+02:00 by git · in Bitcoin block 969644 · download .ots
76467f10f875b71e306fb00f7e2923690631dead4dcf8fd6ec84d85704197d6b
first in commit 17ac666, dated 2026-10-03T00:12:15+02:00 by git · in Bitcoin block 969641 · download .ots
ebf42729b95e114e61d4558f62dac6f1a699123764ee4d390f6495f19c00416f
first in commit e62c8d2, dated 2026-10-02T02:41:38+02:00 by git · in Bitcoin block 969641 · download .ots
70daeee5ef116db194bd4c12ad8df549b83c9ab5fb3578be04967caadbb720f3
first in commit 2e2b166, dated 2026-10-02T00:30:06+02:00 by git · in Bitcoin block 969641 · download .ots
d16cce0aa885f4a33ccdd3fe2ed642a34c5df033c4b510f4b4302df652b4bc62
first in commit 581e114, dated 2026-10-01T23:20:24+02:00 by git · in Bitcoin block 969641 · download .ots
28c8213540a22f129d3d7ebe40e7252cbf475eb611fb561e6e307bc2f598e5df
first in commit 84ece43, dated 2026-10-01T00:36:05+02:00 by git · in Bitcoin block 969641 · download .ots
422ba6428556938500a1e2cc8f1420c1b0081a0babd55c3b09ac579985bd9ce4
first in commit cd34590, dated 2026-10-01T00:12:06+02:00 by git · in Bitcoin block 969641 · download .ots
6cf9c7936636c365420672ae358e9cc55df52d545112c158c4f4a132b5826737
first in commit f64eeba, dated 2026-10-01T00:02:41+02:00 by git · in Bitcoin block 969641 · download .ots
c5d0a079ea4c8d5c8fc467913fa78b3d93cce8dd12d237bdd70a01eafe7c5940
first in commit b32289d, dated 2026-09-24T23:27:54+02:00 by git · in Bitcoin block 969641 · download .ots
91bf2a73d4fd665bc5c6dd57dbd6aef06ec0bcaaf1e75b1335c347eef33b0a30
first in commit 6353995, dated 2026-09-23T19:30:08+02:00 by git · in Bitcoin block 969641 · download .ots
5721813474d6ecf284c0fbb2756c362271c0969566ce2f6d7c69280eca4f5b23
first in commit 6c5faaa, dated 2026-09-22T10:11:57+02:00 by git · in Bitcoin block 969641 · download .ots
The document
SHA-256 862233158b010af6ced167184b710fe6148ce7050ca2d5a6c2abfefde6e5cd3f · download the file
# Preregistration notes
The standing record of analysis decisions taken **before** the data they govern
exists, per `CLAUDE.md` §13.
## What this document is, and is not
It is **not** a preregistration. Nothing has been lodged with the
[KPU Study Registry](https://koestlerunit.wordpress.com/study-registry/) or OSF,
and §13 requires that before any result is asserted publicly — hypotheses,
target pool, judging method, stopping rule, exclusion criteria and primary
statistic, declared in advance and unchanged after seeing data.
It is the place those declarations accumulate in the meantime, so that when one
is lodged it is assembled from decisions that were already on the record rather
than reconstructed afterwards.
§10 draws the line this document sits on. **Looking** at results needs no
preregistration and never has. **Publishing** one does, because once you have
looked, the moment you choose to publish was chosen by looking at the data.
Results are private to the owner today (§1), so nothing here is required yet.
The exclusions below are declared now anyway, for the only reason that matters:
declared later, they would be worthless.
## Declared exclusions
Sessions belonging to these accounts are excluded from **all** analysis —
confirmatory, exploratory, and any count of sessions collected.
| Account | User id | Declared | Sessions in production at declaration |
|---|---|---|---|
| `verification-probe` | `40f69165-67c7-407d-ba00-25641b1579a1` | 2026-09-22 | 0 |
| `fittiziofattucchi` (pre-launch only) | `09ae2cc4-78b3-4d07-8083-c129663adb1e` | 2026-09-23 | 3 |
### Pre-launch operator sessions — declared 2026-09-23
Production carries three sessions that pre-date any third-party use of the site.
All three are the operator's own, made while building and deploying blocks 3 to
5. **None is remote viewing practice.** They are excluded from the leaderboard
and from every analysis, confirmatory and exploratory, and from any count of
sessions collected.
| Session id | Account | Status | What it was |
|---|---|---|---|
| `b8d4f88b-cdc9-4735-b58a-89312ea21de2` | `verification-probe` | revealed | scripted end-to-end check of the deployed path |
| `0245ce19-3ab3-4883-bbd5-40aec29b3b21` | `fittiziofattucchi` | revealed | screens clicked to see the flow work; no paper, no impressions recorded |
| `6ff59b53-3159-4c92-979f-1811ab3edd58` | `fittiziofattucchi` | opened, expired unsealed | same; left to run out |
**The criterion is provenance, never outcome.** What qualifies a session for this
exclusion is *who made it and when* — the operator, before launch, while testing
the machinery — and that is decidable without looking at how it scored. It is
recorded here as a closed list of three ids rather than a rule, so it cannot
quietly grow later.
An exclusion keyed on outcome would be the thing §13 exists to prevent: drop the
sessions that went badly and what remains is not a dataset. None of the three
ids above was chosen by its rank. Two were revealed and one expired without ever
being sealed, and §2.6 would ordinarily require that third one to be counted as
an explicit null — it is excluded for the same reason as the other two, because
nobody was viewing.
**Why this is a declaration and not a deletion.** Removing these rows was
considered and rejected. The immutability triggers refuse it, and §2.4 forbids
disabling a trigger to force the write; rebuilding production onto a fresh
branch would have been legitimate but was disproportionate to the problem.
Declaring is the conservative route — it circumvents nothing, and it leaves the
rows in place where anyone can check that the list above is the whole list.
**This route closes at launch.** Once a third party registers, "operator sessions
before launch" stops being a category that can be extended, because there is no
longer a before.
### `verification-probe`
Registered on 2026-09-22 to verify the deployed production path end to end —
open, upload, seal, rank, reveal — at the promotion of blocks 3, 4 and 5.
Its sessions are driven by tooling against the HTTP API, not worked by a human
on paper. There is no viewer, no impressions, and no ranking in any meaningful
sense: the "impressions" are a generated test sheet and the ranking is whatever
order the verification script submits. Including them would put rows in the
dataset that answer a question nobody asked.
**Why declaring it now is what makes it legitimate.** §13's rule is not that
exclusions are forbidden — it is that they are declared in advance and
unchanged after seeing data, because an exclusion chosen after looking is
indistinguishable from an exclusion chosen *because* of what it removes. At the
moment of this declaration production held **zero sessions of any kind**, guest
or viewer, and zero rankings. The figure is in the table above so the claim can
be checked rather than taken on trust.
This is the opposite of the cautionary case §13 cites. Honorton & Schechter
examined 27 variables and selected post-hoc the ones that worked; what makes
that illegitimate is the selection happening after the numbers were visible.
Here there are no numbers to see.
**This is not a licence to exclude anything else.** Any further exclusion must
be added to the table above *before* the sessions it would remove exist, and an
exclusion proposed after the fact is a finding about the data, not a decision
about the protocol. §13: if an effect appears only after excluding sessions, it
is an artifact — report it as one.
**It is not an escape hatch for abandoned sessions either.** §2.6 and §8 are
unaffected: every session this account does not own is counted, including the
ones that go nowhere, as explicit nulls. The structural defence against a file
drawer is that the platform logs every initiated session server-side, and
nothing here weakens it.
**This declaration stands, and its wording still holds.** A rebuild of production
was planned that would have removed the probe's row outright, which would have
retired this section along with it. That rebuild was abandoned as
disproportionate, so the row stays — and a declaration covering a session that
is still in the database is exactly what is needed. Nothing above is revised: it
was written on 2026-09-22 against zero sessions, and it is left as it was
written.
## The declarations are readable by the code
Since migration `0026` a declaration exists as a row in `declared_exclusions`,
not only as the prose above. This section used to say the opposite — "nothing in
the code knows about the table above" — and that is no longer true.
| | |
|---|---|
| `session_id` | the unit of a declaration is the session, never the account |
| `reason` | an enum, so a new kind of exclusion is a migration rather than a sentence |
| `declared_at` | when the declaration was **made** — for the rows below, the author date of the commit that put it in this file |
| `prereg_ref` | the section above, with the commit hash, so it can be checked with `git log` |
The table is **append-only by trigger**, the same shape as `commit_log`. A
declaration that could be edited or withdrawn after the numbers were visible
could not carry §13's claim of being unchanged after seeing data.
It sits beside `sessions` rather than in it because §2.4 makes a sealed session
immutable and migration `0007` enforces that against the owner role: marking a
row in place would require an UPDATE the database must keep refusing.
**Guest sessions are refused.** §8 keeps them out of the dataset already, so a
row declaring one excluded would imply it had once been counted.
### Marked, not filtered — and where each applies
`research_outcomes()` returns every non-guest session and **marks** the declared
ones: `r_excluded`, `r_exclusion_reason`, `r_declared_before_seal`. Panel B
holds them out of the statistics and reports how many were removed and under
which declaration. Filtering in SQL would have made that count unavailable to
state, and a declaration that removes an unstated number of sessions is not
checkable. §2.6 works the same way: abandoned sessions are counted precisely
because they do not disappear.
`viewer_history()` and the home page **drop** them. That surface is not an
audit — it shows a viewer their own practice, and a row they have been told not
to count is a footnote about methodology in the middle of it. The rows remain in
`sessions`, in Panel B and in every count; only that one view is quiet.
### `declared_before_seal` is computed, never enforced
There is deliberately **no constraint** refusing a declaration made after the
session it removes. Two of the three rows below are later than the session they
cover, and a rule would have made this backfill impossible — it would not have
made the lateness absent, only unrecordable.
§13 turns on the difference between an exclusion declared before the data was
visible and one declared after. That difference is worth showing on every row.
The value is three-valued: `NULL` means the session was never sealed, so there
is no seal for a declaration to precede, which is a different statement from
"declared late".
## Amendments
Changes to how data is collected, dated, in the order they were made. Nothing
here edits a declaration above; each entry records what changed, when, and how
many sessions the change could have touched.
### 2026-09-30 — how the self-ranking is collected
**What changed.** The blind self-ranking (§8) used to be collected by assigning
each candidate a position with buttons 1–5. The assignment could be partial while
the viewer worked, and the lock button stayed disabled until all five had distinct
positions. It is now an **ordered list**, best match first. The list **starts in
presentation order**, the viewer reorders it with up/down in the list or in the
full-screen lens, and locking submits the whole order. The server rule is
unchanged: before and after, `validateRanking` accepts only a complete ranking,
all five candidates with positions 1–5 each used once. What changed is how the
page collects it.
**When.** Commit `a63c3eb`, authored 2026-09-30 23:40:55 +0200, merged to `main`
at `442da3d` and pushed 2026-09-30 23:52:15 +0200. Production deploys on push;
the exact deploy time is in Vercel, not here.
**Sessions included in the dataset at the moment of the change: 1.** That is,
non-guest sessions not listed in `declared_exclusions`. Verified against
production on 2026-10-01 with this query, run read-only:
```sql
select count(*) from sessions s
where not s.is_guest
and s.opened_at < timestamptz '2026-09-30 23:52:15+02'
and not exists (select 1 from declared_exclusions d where d.session_id = s.id);
```
In the same read: 4 non-guest sessions opened before the change, 3 of them the
declared pre-launch sessions above, and 0 non-guest sessions opened since.
*Correction, 2026-10-01.* This entry first said **0**, as the owner's figure,
unverified. The query returns **1**. The dataset therefore holds **one** included
session collected under the old method. Its status, and whether it was ever
ranked, are not recorded here: they were not read. Whether it stays in the
dataset is not decided by this entry. It is included unless a declaration made
on provenance says otherwise, and this document's rule is that an exclusion
proposed after the fact is a finding, not a decision.
**A lock without a single move produces a chance-level ranking, not a biased
one.** The starting order is the presentation order. `candidateOrder` sorts the
pack's five target ids by HMAC-SHA256 over the session id and each target id,
under a key derived from a secret. The target is not an input. It is chosen
separately, by `crypto.randomInt` over the pack's members, in `assignTarget`.
Given the presentation order, the target is equally likely to be any of the five,
so its position in an unchanged list is uniform on 1–5. Its expected rank is 3
and its contribution to the sum of ranks is exactly what chance contributes.
Such a session costs power, as any uninformative ranking does, and shifts nothing.
Checked on the development branch, not assumed. In 2 × 2,200 sessions opened
through `openSession` and ranked through the real endpoint, the target's
presentation position was:
| position | 1 | 2 | 3 | 4 | 5 | χ² (4 df) | p |
|---|---|---|---|---|---|---|---|
| run 1 | 440 | 448 | 475 | 437 | 400 | 6.59 | 0.159 |
| run 2 | 437 | 435 | 406 | 457 | 465 | 4.78 | 0.310 |
| both | 877 | 883 | 881 | 894 | 865 | 0.50 | 0.974 |
A second test, not requested and added in run 1, compared presentation position
with the target's slot in its pack (5×5, 16 df). It gave p = 0.030 in run 1, one
of three tests, so not significant after Bonferroni. It was then repeated as a
declared replication in run 2 and gave p = 0.479. Both are reported, because
reporting only the second would be the selection §13 forbids.
**What cannot be told apart yet.** The presentation order is not recorded. It is
recomputed from the secret key on each request, so after the fact nobody,
including the operator reading the tables, can say which locked rankings were
left exactly as presented. Any tendency to leave candidates near where they
start, a primacy or inertia effect, is therefore invisible for now. It cannot
bias the target's rank, for the reason above; it is a question about viewers,
not about the channel. If it is ever looked at, it is **EXPLORATORY**.
### 2026-09-30 21:36 — first external account; `pre_launch_operator` is closed
**What happened.** The first account not belonging to the operator was
registered at 21:36. The owner had shared the link with a few friends. The
account is not the owner's, is not a test account, and has never had access to
the pool. Its nick is deliberately not recorded here: the account's identity is
not needed to date the event.
**What it closes.** The pre-launch operator declaration said this route closes at
launch, because once a third party registers there is no longer a "before". That
moment is now fixed: **no session made after 2026-09-30 21:36 can be added to
`pre_launch_operator`**, whoever made it and for whatever reason. The closed list
of three ids above is the whole of that category and stays that way.
Nothing above is edited. The declaration already ended on this rule; this entry
records the time it took effect, so a later row under that reason can be checked
against it rather than argued about.
### 2026-10-01 — the presentation order is derived from the nonce, recorded, and published
**What changes.** The previous entry ended on a gap: the order the five
candidates were shown in was stored nowhere and could not be checked. From this
change:
1. **Derived from the committed nonce** (order scheme 2). Each member's key is
`SHA256(nonce[32 bytes] || "order"[5 ASCII bytes] || target_id[16 raw bytes])`
and the order is the five ids ascending by key, compared bytewise. The nonce
is fixed at open and bound by the commit, so the order is fixed before any
user input too.
2. **Recorded when it is shown.** The five ids, in the order served, are written
to `private.presentation_order` in the same transaction as the
sealed → ranking step. The table is append-only by trigger (no UPDATE, no
DELETE, no TRUNCATE). It refuses guests, any other state, and ids outside the
session's pack.
3. **Published at reveal.** The reveal returns the pack's five member ids, the
recorded order, the scheme, and whether the order recomputes from the
published nonce. With the nonce and the members, anyone can check it.
4. **Versioned.** `sessions.order_scheme` is fixed at creation by trigger. Every
session that existed before migration `0027` is **scheme 1**: the old keyed
derivation, which cannot be checked outside the server. It keeps that order,
so a list is never reordered mid-ranking. Scheme-1 sessions that make the
ranking step after the change have their order recorded all the same; the
ones already past it have none, and their reveal says so.
**Effective** from the production deploy of the change and the application of
migrations `0027`–`0028` to production. Both are pending at the time of writing,
and the date of the deploy is the one that counts. Sessions opened before it are
scheme 1.
**Why this does not weaken the commitment.** The commit's preimage is
`target_id || nonce || session_id` (64 bytes); the order's is
`nonce || "order" || target_id` (53 bytes). Different layout and length, so
neither hash can stand in for the other. Before reveal the viewer sees only the
resulting permutation of five images. That is under 7 bits about the nonce,
against its 256, and only relative to ids they do not have. Binding rests on
SHA-256 collision resistance and is untouched.
**Why it creates no link to the target.** The nonce and the target come from
separate CSPRNG draws: `randomBytes(32)` for the nonce, `crypto.randomInt` over
the pack for the target. The order is computed from the nonce and the members,
never from which member is the target. Measured on the development branch,
2,200 scheme-2 sessions through `openSession` and the real ranking endpoint:
| position | 1 | 2 | 3 | 4 | 5 | χ² (4 df) | p |
|---|---|---|---|---|---|---|---|
| scheme 2 | 444 | 423 | 455 | 440 | 438 | 1.21 | 0.876 |
Declared before the run: position uniformity as the test, slot × position as a
secondary check (χ² 16.64, 16 df, p = 0.410). Recorded order equal to served
order in 2,200 of 2,200, all on scheme 2.
**What publishing the members does and does not reveal.** Checked before the
change was written:
- The viewer has just seen all five images and has been told the target. The four
decoy ids add pack membership and no image.
- No viewer-facing route accepts a target id; images are addressed by
per-session handles. No target id appears in any storage key (0 of 1,150).
- The reveal goes only to the session's own viewer.
- Every session draws its target independently, so another session on the same
pack learns nothing about its own target from these ids.
- Guest reveal is a different route and is unchanged: it still returns the target
only (§8).
**What it makes possible.** Whether a locked ranking was left exactly as
presented is now answerable for every session recorded under this change, and so
is any tendency to leave candidates near where they start. Both remain
**EXPLORATORY**. Neither can bias the target's rank, for the reason above.
### 2026-10-03 — external anchoring with OpenTimestamps
**What changes.** Until this entry, two claims in this document rested on records
the owner controls. That no session was rewritten after the fact rested on the
database triggers of migration `0007` onward. That each declaration here was made
when it says rested on git dates. The owner can drop a trigger on the site's own
database and rewrite the site's own repository. From this change, both are
anchored outside the owner's control: in the Bitcoin blockchain, through the
public OpenTimestamps calendars.
1. **What is anchored.** Every hour, every record not yet anchored becomes a leaf
of one Merkle tree. There are five kinds of leaf, each a fixed, versioned text
laid out in `src/anchor/merkle.ts`:
- a `commit_log` row: `seq`, the commit, `logged_at` (migration `0030`);
- a **seal**: the hashes of the photographed sheets in order, the hash of the
typed text, `content_hash` and `sealed_at` (migration `0031`);
- a **locked ranking**: the five target ids in the viewer's order, the
presentation order recorded at the ranking step, the order scheme and
`ranked_at` (`0031`);
- each row of `declared_exclusions`: reason, `declared_at`, the reference,
`recorded_at` (`0031`);
- each row of `stopping_checks`: phase, *n*, cap, BF₁₀, decision, data digest,
rule version, `checked_at` (`0031`).
Timestamps are kept to the microsecond. No leaf holds a session id, a viewer
id or a nick. Every session leaf carries the session's commit, and a stopping
check its random id and data digest, so each leaf has at least 256 bits that
cannot be guessed and the neighbouring hashes in a Merkle path reveal nothing.
The exclusion leaf leaves out `declared_by`, which is free text naming a
person.
2. **How.** Each hour's root is chained to the previous hour's:
`batch_hash = SHA256(prev_batch_hash ‖ root ‖ "bearemoteviewer/anchor-batch/v1/<n>")`.
The batch hash is submitted to four public calendars and kept only if at least
two answer; the proof is upgraded once Bitcoin confirms it. Batches, leaves and
proofs live in four tables, append-only by trigger. The database recomputes
every leaf from its source rows, every root and every chain link before
accepting it, and an upgraded proof is a new row, never an edit. An hour with
nothing new has no batch. If the calendars do not answer, the batch waits for
the next hour. Nothing on a session's path waits for any of this.
3. **At reveal**, through the API only, like the nonce, the viewer receives the
leaf and Merkle path of every record of their own session — the commit, the
seal, the ranking, and an exclusion if one is declared — with each batch, its
hash and its proof. Stopping checks are never handed to a viewer: they stay
behind the research door. `scripts/verify-anchor/verify.mjs`, a second
implementation with no code in common with the site, checks all of it down to
the Bitcoin block header. It also checks that the anchored ranking is the one
the reveal reports, and that the seal's `content_hash` recomputes from its
sheet hashes and text hash. The public endpoint `/api/v1/anchor/batches` lists
each batch's number, chain values and proof, and nothing else.
4. **This document.** Every push to `main` that changes `docs/preregistration.md`
stamps the SHA-256 of the file as git stores it, and commits the proof as
`docs/stamps/<sha256>.ots`. A scheduled job upgrades pending proofs. The page
`/preregistration` publishes the file unrendered, with every version's hash,
commit and proof.
The guest purge (§8) and the erasure (§9) are unaffected. The purge sets
`commit_log.session_id` to null, which is why the session id is not in any leaf:
a purged guest's leaf recomputes exactly as before. A guest is never sealed,
ranked, declared out or checked by the stopping rule, so no other leaf ever
refers to one. The erasure changes the account and deletes the photographs from
storage. It keeps every row a leaf is built from, the sheet hashes included, so
every leaf of an erased account's sessions recomputes exactly as before.
**Effective.**
- *This document:* from **2026-10-02 23:41 +02:00**. At that time every version then
on `main` — nine, from `6c5faaa` to `e62c8d2` — was stamped by four calendars.
Each later version, this one included, is stamped when it reaches `main`.
- *Everything else:* from the **first batch built in production**. That follows
the production deploy of this change and the application of migrations `0030`
and `0031`, all pending at the time of writing. The time that counts is that
batch's, which is batch 1 in the public list and is dated by its Bitcoin block.
**The past, stated as it is.**
- Every record written before the first production batch — commitments, seals,
rankings, exclusions, stopping checks — goes into that batch. Its anchor proves
it existed **by the date of that batch**, and not before. For those records the
earlier evidence is what it always was: the triggers, and the commit each
viewer was shown at open.
- Every declaration made before this entry carries only its git date: those above
it, and the stopping rule below it. So does every version of this document from
before 2026-10-02 23:41. The stamps prove those versions existed by 2026-10-02.
Whether any of them existed earlier still rests on git, which the owner
controls. Nothing here makes them older.
**What it covers.**
- An anchored record cannot have been changed since its batch reached a block.
For a commitment, that is the target behind it and the time it was logged. For
a seal, the sheets submitted, the text typed and the time of sealing. For a
ranking, the order the viewer gave, the order the candidates were shown in, and
the time it was locked. For an exclusion or a stopping check, every field
listed above.
- Nothing can have been added to, removed from or reordered within an anchored
batch, or any batch before it. Anyone holding the full record can check this,
and §13 requires the record to be published with any result. Each viewer can
check it for their own sessions.
- A stamped version of this document existed by the block of its stamp.
**What it does not cover.**
- *The window between writing and the block.* A record waits up to an hour for its
batch, then usually a few hours for Bitcoin. A record that reaches a batch long
after it was written — a late-committing transaction, an outage of the job — is
anchored only from its batch's time, and the verifier prints the gap. A session
is usually sealed, ranked and revealed well inside one window. So the anchor
does not by itself prove that a ranking was locked before its reveal: the
database enforces that order and the timestamps record it, and the anchor fixes
the timestamps from the block on.
- *The photographs themselves.* Only their hashes are anchored. After an erasure
the photograph is gone, and the hash shows only that a sheet with that hash was
sealed.
- *Records outside the five kinds.* Pre-session state, the audit log and the rest
of the database are protected by triggers only. So is a session row's status
between its anchored events: an abandonment, for instance, is recorded but not
anchored.
- *Records that were never written.* A session the operator never opened leaves
no row, and a declaration never entered leaves no leaf. What anchoring rules out
is a record appearing in, or vanishing from, a batch after the fact.
- *The operator before the record.* The trust model on the methodology page is
unchanged. Anchoring narrows what can be changed afterwards. It does not make the
server trustworthy.
**What the public list discloses.** Which hours had at least one new record,
guests included, since a guest session is logged at open: an hour without one
has no batch. And how many batches there are. It shows no counts of any kind and
no times finer than the batch. A viewer's own path lengths show, to within a
factor of two, how many records shared their batch.
**Data collection: unchanged.** Sessions are opened, sealed, ranked and revealed
exactly as before. No session row, and nothing about sessions already collected,
is altered.
### 2026-10-08 — the candidate images are anchored
**What changes.** The commitment fixes the target's identifier. Until this entry,
which image an identifier names rested on the server: the id points at a storage
key, the key at an object in the bucket, and nothing a viewer could check tied the
object to the id. From this change the bytes are anchored too.
1. **What is recorded.** For every target, the SHA-256 of the exact bytes stored
and served by the site, padding included, and the time it was recorded
(migration `0036`). The table is append-only, and once a target's hash is
recorded its storage key cannot change.
2. **The leaf.** A sixth kind, `bearemoteviewer/target-image-leaf/v1`, holding
the target id, that hash, a 32-byte random salt and the recorded time. The
salt is in the leaf, not in the hash: it gives each leaf 256 bits that cannot
be guessed, so a leaf hash seen beside another in a Merkle path confirms
nothing. The leaves go into the same tree and chain, built at each scheduled
run.
3. **At reveal**, through the API only, the viewer receives the leaf and path of
each of the five images of their pack, and can save the five files.
`verify-anchor.mjs --image` hashes each saved file and finds it among the
five leaves.
4. **A pack is drawn only once all its targets are hashed** (migration `0041`).
5. **Checked when served** (migration `0037`). Before a pack is shown, all five
objects are hashed against their records; if one fails, none of the five is
served to that session and the refusal is recorded. Each image is hashed
again as it is served. A periodic audit hashes the whole pool, and the
monitoring job goes red on any mismatch.
**Which sessions are covered.** A session is covered only if the leaves of all
five of its candidates are in a batch no later than the batch of its commit, and
were recorded no later than its commit was logged. The verifier says, session
by session, whether it is covered. For a session that is not, which image the
target id names remains the server's word, and the verifier says so rather than
failing.
**Effective.** From the first batch that contains the leaves of the whole pool
as it stood when it was hashed, batch 5, built at `2026-10-07T01:42:02+02:00`.
The hashes of the 3810 targets were recorded on 2026-10-07 from 00:59:50 to
01:12 and from 01:27:35 to 01:32:19, and 0 sessions were opened between the
start of that recording and batch 5, so none is left uncovered by the interval.
Each image is proven to exist, as hashed, from the Bitcoin block of its batch,
not from when it was ingested.
**What it does not cover.**
- *What was on the screen during the ranking.* The leaves fix the bytes served
after their batch, and the serve-time check refuses bytes that differ from
them. A server that set both aside would not be caught by files saved after
the reveal; files saved during the ranking would catch it.
- *Order within one batch.* A session whose commit shares a batch with its
images is covered on the recorded times, which the anchor fixes from the block
on but does not itself order.
- *The operator.* As for every anchor: a change after the batch is detectable;
the server's behaviour at the time is not proven.
**Data collection: unchanged.** No session row is altered. Packs whose targets
are not all hashed are not drawn until they are.
## Stopping rule — declared 2026-10-02T02:41:38+02:00
**Declared at `2026-10-02T02:41:38+02:00`, the author date of the commit that
added this section** (`git log --format=%aI` on it). This replaces the
"Stopping rule" entry that stood under "Not yet declared". It is applied by
code: `src/stopping/rule.ts` (the rule), `src/stopping/run.ts` (the hourly
job), migration `0029` (the record). The draft it was chosen from, with the
alternative that was not chosen, is `docs/stopping-rule-draft.md`.
### Design: one study per viewer, two phases
Each viewer account is an independent sequential study, with its own data and
its own outcome. There is no pooled, site-wide test and no figure aggregated
across viewers (`CLAUDE.md` §13: "Show the distribution per viewer").
### What counts
The datum is **the target's rank, 1 to 5**, in the viewer's blind
self-ranking of the five candidates (§8). Chance is uniform on 1..5: mean rank
3, direct-hit rate 1/5.
A session contributes a datum when all of the following hold:
- it is not a guest session (§8);
- it has no row in `declared_exclusions`;
- it was **opened after the declaration instant above**. Sessions opened
before it stay **EXPLORATORY** (§10: a declared analysis applies to data
collected after the declaration);
- its ranking is locked **and revealed**, and a rank is stored.
Data are ordered by **reveal time**, ties by session id. The *k*-th datum is
fixed at the *k*-th reveal and never changes afterwards.
Sessions that produce no datum are never dropped from any accounting:
- **abandoned** (expired while open or sealed, §8): counted and reported as an
explicit null, **never imputed a rank, not counted towards *n***;
- **locked but not revealed:** counted, no datum until revealed;
- **revealed with no rank stored:** counted as abandoned and reported as an
anomaly.
Abandonment cannot inflate the false-positive rate. The viewer abandons, or
leaves a ranking unrevealed, without knowing the target, so under H0 that
choice is independent of the rank the session would have produced, and the
ranks that remain are still uniform and independent.
### Statistic and test
- **Primary statistic:** sum of ranks (equivalently mean rank), lower = better
(§13; Solfvin, Kelly & Burdick 1978). Direct-hit rate is secondary and plays
no part in stopping.
- **Decision statistic: BF₁₀, directional**, computed by `bayesFactor()` in
`src/stats/bayes.ts`:
- H0: ranks i.i.d. uniform on 1..5;
- H1: ranks i.i.d. from a linear tilt, P(rank = *k*) = 1/5 + *c*·(3 − *k*),
*c* = ES·√2 / 10, with ES Rosenthal's per-trial effect size,
ES = (3 − mean rank) / √2;
- prior on ES: **half-normal, scale 0.1, truncated at 0.7** — all mass on
ES > 0, "better than chance";
- likelihood from the full vector of rank counts; Simpson's rule on 2,001
points in [0, 0.7], the prior normalised on the same grid.
- **One-sided.** BF₁₀ < 1 means the data do not favour a positive effect;
results at chance and below chance both produce it.
- **Frequentist figures are reported, not used to stop.** The exact one-sided
p-value of the sum of ranks is reported beside BF₁₀ at nominal α = 0.05, with
Benjamini–Hochberg at q = 0.05 for any statement covering more than one
viewer. With up to 21 looks per phase, the nominal p at a stop is not a
fixed-sample p-value and is not interpreted as one.
### The rule
**Phase 1.** BF₁₀ is evaluated at **30, 40, …, 230** revealed data. The first
checkpoint with **BF₁₀ ≥ 10** ends phase 1 at *n*₁. Reaching **230** without
it ends the study **inconclusive**.
**Phase 2**, only after a phase-1 pass. It starts from zero on the data that
follow *n*₁ in reveal order — phase-1 data are never reused — and applies the
same rule: BF₁₀ ≥ 10 at 30, 40, … data of phase 2. Its cap is **the packs
left**: 230 minus the distinct packs the viewer had burned when the *n*₁-th
datum was revealed, abandoned sessions and sessions from before the
declaration included. When that cap is not a multiple of ten, it is itself the
last checkpoint. **If fewer than 30 packs are left, the outcome is
"replication not possible", and it is declared as such.**
**Above chance** is declared only when phase 1 **and** phase 2 both cross the
threshold. A phase-1 pass alone is not a result and is not reported as one.
**Every other outcome is inconclusive**: phase 1 not passed by 230; phase 2 not
passed by its cap; replication not possible; or no pack left to open and no
session pending before the next checkpoint (recorded without evaluating
BF₁₀ — no look is taken off the schedule).
**There is no stop for absence of evidence.** With this prior, data exactly at
chance give BF₁₀ ≈ 0.55 at 230; reaching 1/10 would take on the order of
10,000 sessions. A stop that cannot be reached is not declared.
**After the outcome**, the viewer's sessions continue and stay visible to
them. Every later datum is **EXPLORATORY** and plays no part in the outcome.
### Sample size
**There is no site-level sample size.** The study is per viewer, and each
viewer's sample size is decided by the rule above: it ends at the first
outcome, and at most at **230 sessions**, one per pack. This replaces the
"Sample size" entry that stood under "Not yet declared".
The 623 ranked sessions that block 4 computes for 80% power at ES 0.1 is a
planning and display figure for a fixed-sample design that does not exist
here — pack-level exclusion caps any viewer at 230. It plays no part in the
rule.
### What an inconclusive outcome excludes
Every inconclusive outcome is reported with this sentence, whose figures are
fixed here and not recomputed per viewer:
> *Inconclusive. Under this rule, an effect of ES 0.3 would have been declared
> above chance in 75% of cases, ES 0.2 in 19%, and ES 0.1 in under 1%.*
In words, ES is a mean shift in rank. A viewer who sees the target clearly
some of the time and is at chance otherwise has mean rank 3 − 2*q*, where *q*
is how often they see it, so ES = √2·*q*:
- **ES 0.3 ≈ sees it clearly about 1 time in 5** (*q* = 0.21), at chance the rest;
- **ES 0.2 ≈ about 1 time in 7** (*q* = 0.14);
- ES 0.1 ≈ about 1 time in 14 (*q* = 0.07).
The detection figures in the sentence are computed under H1's own model, the
linear tilt. A viewer of the "sees it clearly sometimes" kind, with the same
ES, is confirmed less often — **65% at ES 0.3 and 17% at ES 0.2** — because the
same mean shift is concentrated in direct hits, which the tilt spreads across
the ranking. Both are stated so neither reads as the whole truth.
### Operating characteristics
From `scripts/stopping-rule/simulate.mts` (seeds fixed in the script; output in
`scripts/stopping-rule/results.json` and, for the "sees it clearly" model,
`results-mixture.json`). 100,000 simulated viewers at ES = 0, 10,000 at each
other ES, each with the full 230 packs and no abandoned sessions.
| ES | Pass phase 1 | **Above chance (confirmed)** | Reach phase 2, not confirmed | …of which, replication not possible | Median total sessions, confirmed |
|---:|---:|---:|---:|---:|---:|
| −0.1 | 0.04% | 0.00% | 0.04% | 0.00% | — |
| 0 | 1.68% | **0.0075%** | 1.68% | 0.26% | 220 |
| 0.1 | 22.93% | **0.75%** | 22.18% | 4.13% | 200 |
| 0.2 | 76.99% | **19.29%** | 57.70% | 8.50% | 200 |
| 0.3 | 98.89% | **75.12%** | 23.77% | 1.72% | 170 |
At ES = 0 the rate is the semi-analytic estimate (phase 2 is the same rule on
fresh data with a shorter cap, so P(confirmed) = Σ P(phase 1 stops at *n*₁) ·
P(phase 1 would stop by 230 − *n*₁)). The direct count was 8 of 100,000
(0.008%, 95% interval 0.004%–0.016%).
Probability that at least one viewer at chance is declared above chance:
| Viewers | 10 | 100 | 1,000 |
|---|---:|---:|---:|
| Two phases (this rule) | 0.08% | 0.75% | 7.2% |
### Declared limitations
1. **An inconclusive outcome is not evidence of no effect.** What it excludes
is only what the sentence above says.
2. **Power at small effects is low.** Under 1% at ES 0.1 and 19% at ES 0.2:
the rule is built so that a confirmed viewer is very unlikely to be chance,
and pays for it in the effects it can see.
3. **The pool is the cap.** 230 packs bound each viewer's whole study,
replication included. Abandoned sessions use packs and shorten phase 2.
4. **Static targets** (§7) and **solitary sessions** (§17): a null cannot
distinguish "no effect" from "wrong target type".
5. **A confirmed viewer is not a site-level result.** It is one study; the
standard in §13 ("replicates in an independent preregistered sample")
still applies to any claim beyond that viewer.
### The record
Each checkpoint is evaluated by a scheduled job, hourly, and written to
`stopping_checks`: viewer, phase, *n*, cap, BF₁₀, decision, and a SHA-256 of
the data it rests on. The table is append-only, accepts checks only in order,
and accepts nothing after a final decision — all three enforced by database
triggers. No person needs to look at results for the rule to be applied. The
record is read only from behind the research door, where every reading is
logged with a reason; a viewer's own results page does not show it.
## Stopping rule v2 — in force from 2026-10-04T14:00:00+02:00
**In force from `2026-10-04T14:00:00+02:00`.** The commit that added this section, and set
`RULE_V2.declaredAt` to the same instant, was on main before it: its author
and committer dates are earlier, and git keeps them. Before that instant v1
applied to every viewer. `src/stopping/rule.test.mts` asserts that this
document states the instant in `src/stopping/rule.ts`. This section amends "Stopping rule — declared
2026-10-02T02:41:38+02:00" above, which is not edited and stays in force for
the viewers named in "Who is on v2" below. The draft, with the thresholds that
were not chosen, is `docs/stopping-rule-v2-draft.md`.
### Why
v1 bounded each study by the pool: 230 packs, so 230 sessions per viewer at
most. On 2026-10-04 the pool was expanded to 762 packs. Two things must not
follow from that silently:
1. **The cap must not move with the pool.** A cap that grows when packs are
added is a stopping rule the operator can lengthen after seeing how a study
is going. v2 states the cap as a number.
2. **A threshold chosen for 230 is wrong at 600.** With more looks, the same
BF₁₀ ≥ 10 confirms a viewer at chance more often. The threshold is raised so
that the chance of at least one of 100 viewers at chance being confirmed
stays near 1%, where v1 had it (0.75%).
### What changes
1. **Cap: 600.** Phase 1: BF₁₀ evaluated at 30, 40, …, 600 revealed data.
Phase 2 cap: 600 minus the distinct packs the viewer had burned when the
*n*₁-th datum was revealed. "Replication not possible" below 30, as in v1.
The 600 is a number in this text, not "the packs in the pool": packs added
later do not raise it, packs deactivated later do not lower it.
2. **Threshold: BF₁₀ ≥ 20, in both phases.**
Everything else in v1 stands unchanged: one study per viewer, two phases,
what counts as a datum, ordering by reveal, the accounting of abandoned
sessions, the start of the data (sessions opened after
2026-10-02T02:41:38+02:00), the directional BF₁₀ with the half-normal prior of
scale 0.1 truncated at 0.7, checkpoints every 10, no stop for absence of
evidence, frequentist figures reported and not used to stop, an
"inconclusive, pool exhausted" outcome for a viewer with no pack left to open,
and the record in `stopping_checks`.
### Who is on v2
**A viewer with at least one row in `stopping_checks` at the instant v2
comes into force stays on v1** — cap 230, BF₁₀ ≥ 10 — until a final outcome; their
phase-2 cap is still computed against 230, and a v1 study that has ended stays
ended. **Every other viewer is on v2**, including one who already has counted
data.
Why: changing a stopping rule mid-study is unsafe when the change can depend
on what the data showed. Until the first checkpoint, no check has looked at a
viewer's data — no BF₁₀ has been computed, nothing recorded, nothing seen by
anyone — so moving that viewer to v2 cannot depend on how their study was
going. Once a check exists, it has been computed and could have been seen, and
the viewer stays on the rule it was computed under.
In the code, `ruleFor()` gives a viewer the version that wrote their first
`stopping_checks` row, or the rule in force if they have none, and every row
records the version that produced it in `rule_version`. `ruleInForce()` is v1
before the instant above and v2 from it on.
**Viewers with at least one row in `stopping_checks` when this section was
committed: 0.**
### The pool when this section was committed
Packs up to `pack-0230` are epoch 1, the pool v1 was declared against; packs
from `pack-0231` are epoch 2, added on 2026-10-04. Labels are assigned in
order by `compose` and never reused.
| | Packs | Active | Inactive |
|---|---:|---:|---:|
| Epoch 1 (`pack-0001`–`pack-0230`) | 230 | 216 | 14 |
| Epoch 2 (`pack-0231`–`pack-0762`) | 532 | 528 | 4 |
| **Total** | **762** | **744** | **18** |
SHA-256 of the 744 active pack ids — lowercase canonical UUIDs, sorted
ascending, each followed by a newline, UTF-8, as computed by
`scripts/stopping-rule/pool-fingerprint.mjs`:
`924f9792881c38592cf59f038a90d15f7888a14c8c18d06488799f3b9931452b`
**Pool requirement.** v2 comes into force with at least 620 active packs: the cap
plus a margin, so that deactivations after it (viewer reports,
§7) do not leave a viewer short of packs before the cap. New packs are added
before the active count falls under 600. A viewer who nonetheless finds no
pack left to open ends inconclusive, pool exhausted, as in v1.
### How the pool reached this state
All times Europe/Rome, 2026-10-04.
| When | What |
|---|---|
| 02:14:25–02:15:16 | 532 packs (`pack-0231`–`pack-0762`, 2,660 targets) copied from development to production, every object checked present on R2 at 1,048,576 bytes before writing. The 4 packs already taken out on development for incomplete attribution arrived inactive; the other 528 were drawable from this moment. |
| 12:40:53 | 4 rows in `pack_deactivations` (migration `0032`, applied in production at 12:40) for those 4 packs: origin `operator`, reason `attribution_incomplete`, `was_active = false`. |
| 12:41:19 | Of 3 epoch-1 targets without an author, 2 completed from Commons and 1 not; its pack, `pack-0171`, taken out: `attribution_incomplete`, `was_active = true`. |
| 12:41:29–12:41:30 | 13 epoch-1 packs taken out by target id, from the automatic content filters: 7 `identifiable_person` (faces), 6 `poor_quality` (5 blurred, 1 flat). `was_active = true`. |
Every inactive pack has exactly one row in `pack_deactivations`. Nothing was
deleted, and no session row was touched.
**The new packs were served under v1.** From the promotion until the instant
v2 comes into force, the 528 active epoch-2 packs were drawable in production while v1
was the rule in force, and until 12:41 the 14 epoch-1 packs above were
drawable as they had been since the pool was built. v1 writes its cap as the
number 230 at every checkpoint, so the larger pool changed no v1 check; it did
end v1's declared limitation "The pool is the cap", which is why this
amendment exists. **Sessions opened on epoch-2 packs between the promotion and
the commit of this section: 0.**
### What an inconclusive outcome excludes
For v2 studies, every inconclusive outcome is reported with this sentence,
whose figures are fixed here and not recomputed per viewer:
> *Inconclusive. Under this rule, an effect of ES 0.3 would have been declared
> above chance in over 99% of cases, ES 0.2 in 85%, and ES 0.1 in 8%.*
As in v1, the figures are computed under H1's own model, the linear tilt. A
viewer of the "sees it clearly sometimes" kind — with probability *q* = ES/√2
ranks the target first, otherwise at chance — with the same ES, is confirmed
**99.8% of the time at ES 0.3, 78% at ES 0.2 and 7% at ES 0.1**. The gap
between the two models is much narrower than in v1 (65% and 17% there at ES
0.3 and 0.2): over 600 sessions both kinds of viewer accumulate enough direct
hits for BF₁₀ to separate them from chance. Both are stated so neither reads
as the whole truth.
### Operating characteristics
From `scripts/stopping-rule/simulate-v2.mts`, with the generator, checkpoints
and BF₁₀ of the v1 simulation; seeds fixed in the script; output in
`scripts/stopping-rule/results-v2.json` (linear tilt) and
`results-v2-mixture.json` ("sees it clearly sometimes"). 100,000 simulated
viewers at ES = 0, 10,000 at each other ES, each with a full pool and no
abandoned sessions. The threshold was chosen from the columns below; the
chosen one is in bold.
**Confirmed above chance (both phases), % of viewers — linear tilt**
| ES | T 10 | T 15 | **T 20** | T 30 |
|---:|---:|---:|---:|---:|
| −0.1 | 0 | 0 | **0** | 0 |
| 0 | 0.074 | 0.023 | **0.0098** | 0.0032 |
| 0.1 | 18.4 | 11.3 | **8.0** | 4.8 |
| 0.2 | 94.0 | 89.2 | **84.9** | 77.3 |
| 0.3 | 100.0 | 99.99 | **99.97** | 99.94 |
At ES 0 the figure is the semi-analytic estimate, as in v1; direct counts out
of 100,000: 77, 27, 11, 4. Phase 1 passed at ES 0: 3.46%, 2.03%, 1.39%, 0.85%.
**Confirmed above chance (both phases), % of viewers — "sees it clearly
sometimes"**
| ES | *q* | T 10 | T 15 | **T 20** | T 30 |
|---:|---:|---:|---:|---:|---:|
| 0.1 | 0.07 | 17.0 | 10.3 | **7.2** | 4.1 |
| 0.2 | 0.14 | 90.3 | 83.6 | **78.0** | 69.2 |
| 0.3 | 0.21 | 99.96 | 99.87 | **99.78** | 99.45 |
At ES 0 the two models are the same distribution (uniform ranks), so the
false-positive figures hold for both.
**At least one viewer at chance declared above chance**
| Viewers | T 10 | T 15 | **T 20** | T 30 | v1 (230, T 10) |
|---:|---:|---:|---:|---:|---:|
| 10 | 0.74% | 0.23% | **0.10%** | 0.03% | 0.08% |
| 100 | 7.2% | 2.3% | **0.98%** | 0.32% | 0.75% |
| 1,000 | 52.5% | 20.7% | **9.4%** | 3.2% | 7.2% |
**Median sessions to a confirmed outcome (phase 1 + phase 2), BF₁₀ ≥ 20**,
among the simulated viewers who were confirmed:
| ES | Linear tilt | "Sees it clearly sometimes" |
|---:|---:|---:|
| 0.1 | 480 | 490 |
| 0.2 | 390 | 400 |
| 0.3 | 230 | 250 |
At ES 0.1 the medians rest on fewer viewers (796 and 721 of 10,000
confirmed) than at 0.2 and 0.3.
Against v1, BF₁₀ ≥ 20 at 600 trades a slightly higher chance of a false
confirmation among 100 viewers (0.98% against 0.75%) for far more power: ES
0.2 confirmed in 85% of cases against 19.29%, ES 0.1 in 8% against 0.75%.
### Declared limitations, changed
1. **The pool is no longer the cap.** The cap is the number 600; the pool must
be larger, with a margin, and its state when this section was committed
is recorded above.
2. **Longer studies.** A viewer at chance runs up to 600 sessions before an
inconclusive outcome. Abandoned sessions still burn packs and shorten
phase 2.
3. **Power at small effects is still low.** 8% at ES 0.1 under the tilt, 7%
under "sees it clearly sometimes".
v1's other declared limitations — an inconclusive outcome is not evidence of
no effect; static targets and solitary sessions; a confirmed viewer is not a
site-level result — apply to v2 unchanged.
## Stopping rule — erased viewers, in force from 2026-10-06T21:00:00+02:00
**In force from `2026-10-06T21:00:00+02:00`.** This section was committed
before that instant; the migrations that apply it (`0038`, `0043`, `0045`) are
applied in production after it. It amends both versions of the rule above,
which are not edited.
**What changes.** An erased account (§9) can produce no more data. Until this
section its study had no way to end: it stayed a candidate of the rule, and it
could never count as exhausted, since packs remained and a ranking locked and
never revealed counted as pending for ever.
1. **At the erasure, the viewer's study ends "inconclusive, withdrawn"**
(`inconclusive_withdrawn`). The row is shaped like "inconclusive, pool
exhausted": the phase and the number of data where the study stands, and no
Bayes factor.
2. **The erasure adds no look off the checkpoints.** If the data had reached a
checkpoint not yet recorded, that checkpoint is recorded as the scheduled run
would have recorded it; then the withdrawal. No Bayes factor is computed for
the withdrawal itself.
3. **The data already revealed stay in the site's data**, with no name on them.
4. **A study that had already ended keeps its ending.** Nothing is added after a
final decision.
5. **Who started the erasure is recorded**: the account holder, from their own
account page, or the operator. The operator must give a reason from a closed
list: the holder asked by another channel, a legal obligation, or abuse of
the site.
6. **Every study an erasure started by the operator closes with data counted is
published, automatically**: the day, the reason, the phase, the number of
data, and the last Bayes factor the rule recorded for that study if it had
reached a checkpoint, with no nick and no identifier, at
`/api/v1/stopping/operator-closures`. The list is written by the database
when the withdrawal is recorded, so no path can close such a study without
publishing it, and each withdrawal is itself an anchored stopping check.
**Why the operator's closures are published.** Erasure ends a study as
inconclusive whatever its data showed. When the account holder asks, that is
their choice. When the operator erases an account, it is the operator's: and an
operator who could see a study heading for a confirmation could end it this way,
and the record would show only an inconclusive study. Publishing every such
closure, with what the study had reached and the last Bayes factor recorded for
it, makes that act visible to anyone rather than leaving it to the operator's
restraint.
**Accounts erased before this section.** When this section was committed, 0
accounts erased before `2026-10-06T21:00:00+02:00` had a study with data counted.
**Data collection: unchanged.** No session row is altered.
## Automatic reveal — in force from 2026-10-08T16:30:00+02:00
**In force from `2026-10-08T16:30:00+02:00`.** This section was committed before
that instant; the migrations that apply it (`0046`, `0047`) are applied in
production after it.
**What changes.** A ranking locked and not revealed by its viewer is revealed by
the site at the first scheduled run seven days after the lock, and counts as a
datum like any other reveal, in the order of the instant it was revealed. Who
revealed each session, the viewer or the site, is recorded, append-only.
**Why.** Until this section a locked ranking entered the data only when its
viewer revealed it. A viewer who felt a session had gone badly could leave it
locked for ever, and a complete datum, blind and immutable, would never be
counted. That is a file drawer one session wide, and it selects on the viewer's
feeling about the outcome.
At chance that feeling cannot follow the outcome, because the target is drawn at
random among the five, so the drawer could not produce a false confirmation.
When there is an effect it could bias every estimate, and a record with a hole
in it is weaker evidence either way. This section closes it: every locked
ranking becomes a datum within seven days.
1. **The same procedure and the same moment for every session.** The selection
reads whether a ranking is locked, when it was locked, and whether the session
is a guest's; never the target, the ranking or the rank given. Every session
due at a run is revealed by one statement, at one instant.
2. **The instant is the run's,** at most the scheduler's delay after the seventh
day, and is not backdated: the data are ordered by reveal instant, and a
backdated reveal could reorder data a stopping check has already recorded.
3. **The viewer may still reveal at any time before.** Both reveals count the
same.
4. **Guests are excluded:** they have no ranking and are not data.
5. **Erased accounts are not.** A ranking locked before the erasure is revealed
like any other and stays in the site's data with no name on it. A study the
erasure already ended keeps its ending: nothing is added to the stopping
rule's record after a final decision.
6. **An alarm.** The site's monitoring goes red if any ranking locked more than
seven days and three hours ago is still not revealed, guests aside.
**The stopping rule.** A ranking locked and not revealed counts as pending, as
before, but now for at most seven days and the scheduler's delay. A study with
no packs left can therefore end as exhausted at most that long after its last
lock, once its locked rankings have been revealed and counted; before this
section one ranking locked and never revealed kept it open for ever. The
checkpoints are unchanged.
**Rankings locked before this section.** The rule applies to them too. When this
section was committed, 0 rankings in production were locked and not revealed.
**Limits.**
- *The schedule.* The reveal happens at the first run after the deadline.
Runs are scheduled hourly, not guaranteed.
- *The anchor.* The ranking and the time it was locked are anchored before any
reveal, so an automatic reveal cannot change a datum. When a session was
revealed, and by whom, is not anchored, and a reveal the server withheld would
leave no trace in the anchor; the alarm above is the operator's own check, not
a proof. The trust model is unchanged.
- *The viewer's timing.* Within the seven days the viewer still chooses when to
reveal, blind to the outcome, and so where the datum falls among their data.
That was already so; it is now bounded.
**Data collection: unchanged.** No session row is altered beyond what any reveal
writes.
## Reports and limits — in force from 2026-10-09T11:30:00+02:00
**In force from `2026-10-09T11:30:00+02:00`.** This section was committed before that instant. The database
applies it from the moment migration `0048` runs in production, after that instant; the
limits on use below likewise. A report made in between is handled by the old rule — it
takes its pack out of the draw at once — and stays recorded exactly as it was made, in
`pack_deactivations`; it is not carried into `pack_reports` and does not count towards
the new rule. It replaces the rule for viewer reports of migration `0032` (`CLAUDE.md`
§7, "Reports after the reveal"), the rule "Stopping rule v2" relies on under "Pool
requirement".
### Why
Until this instant a viewer's report took the pack out of the draw at once: one report
per revealed session, and no other limit. One account could therefore empty the pool —
open, seal, rank, reveal and report, once per pack. Nobody could then open a session,
guests included, and every study under the stopping rule would end "inconclusive, pool
exhausted": an append-only, anchored outcome produced by one account rather than by the
data.
### What changes
1. **A report is recorded, and on its own takes nothing out of the draw.** Reports are
kept in `pack_reports`, append-only and protected from TRUNCATE, like the rest of the
record.
2. **A pack leaves the draw when two distinct accounts have reported it,** each
*qualifying* at the moment of its report: created at least **7 days** earlier and
with at least **10 revealed sessions**. Whether a report qualifies is computed and
stored when it is made; a later change to the account does not move it.
3. **The floor.** While **640 or fewer** packs are active, no report takes a pack out.
The reports stay recorded, the pack stays in the draw, and the monitoring job goes
red until the operator decides. 640 is stopping rule v2's requirement (620) plus a
margin.
4. **At most 3 reports per account in any 24 hours.**
5. **The operator** can take a pack out from the operator's panel, with a reason from
the closed list; the act is recorded with the deactivation and in the audit log. No
floor applies to it: it is a decision someone takes and signs, not an automatic one.
6. **Unchanged:** a report changes only which packs future sessions can draw; nothing
is deleted; every session on a pack that leaves the draw stays in the dataset; there
is no path that puts a pack back.
### Why this cannot depend on outcomes
Who may report, and when a pack leaves, depend on the age of the account, its number of
revealed sessions and the number of active packs — never on a rank, a target or a
result. A report is still possible only after the reveal, and every session's target is
still drawn uniformly from its pack, so chance stays exactly 1 in 5 whatever the pool
holds.
### Reports made before this instant
They stay as they were recorded: rows of `pack_deactivations` with origin
`viewer_report`, each of which took its pack out at once under the old rule. None of
them is carried into `pack_reports`, and none counts towards the two accounts of the new
rule. On 2026-10-09 at 10:25 (Europe/Rome), read-only: **0** viewer reports in
production, **0** packs out of the draw because of one, **744** packs active of 762.
### Also from this instant: limits on use
Stated here because they change how data are collected (migration `0048`):
- an account may have **one session in progress** at a time — open, sealed, or shown
its candidates and not yet ranked — and may open at most **10 sessions in any 24
hours**; guest sessions are unchanged;
- typed text, the body and the structured fields together, is at most **20,000
characters** per session;
- each photograph is at most **15 MB**, and a session takes at most **12**.
None of these depends on outcomes. A refused open creates no session, no row and no
commitment, so nothing is dropped from the record; the stopping rule counts data, not
attempts, and is unchanged. A seal refused for its length or its size leaves the session
open until its deadline, as any unfinished session.
**Data collection: otherwise unchanged.**
## Incidents
Dated notes on the conditions the data were collected under. They are not
declarations: they change no rule and exclude nothing. They are here so that an
analysis cannot miss them.
### 2026-10-04 — uploads from the canonical domain refused by CORS
**A note, not a declaration.** Written 2026-10-04.
From the moment the domain was connected until about 01:50 (Europe/Rome) on
2026-10-03, the CORS rule of the R2 bucket did not admit
`https://www.bearemoteviewer.com`, the site's canonical origin. In that window,
uploading a sheet on the real site failed: the bucket refused the browser's
preflight, so the `PUT` was never sent.
- **Start: not later than 2026-09-30 21:53 (Europe/Rome).** The repository does
not record when the domain was connected. The earliest evidence in it is
commit `8718d56` (author date 2026-09-30T21:53:36+02:00), which moved the
scheduled workflow from the bare domain to `www` because Vercel was already
answering `bearemoteviewer.com` with a 308 to `www`, so the domain was
serving by then. The commit before it, `8e02004` (21:52:54), is the first to
call the domain at all. The window may have started earlier.
- **End: about 01:50 on 2026-10-03**, when the origin was added to the rule.
The bucket's configuration is not in the repository; this time is the
owner's record, not git's.
Sessions abandoned in that window remain counted as abandoned (§2.6, §8).
Every analysis of the abandonment rate must cite this window.
### 2026-10-05T01:17:20+02:00 — the scheduled jobs did not run every hour
**A note, not a declaration.** Drafted 2026-10-04, completed 2026-10-06.
Until `2026-10-05T01:17:20+02:00`, the first successful run of the Cloudflare
Worker that took over the schedule (`workers/scheduler`), the scheduled jobs —
expiry sweep, stopping rule, anchoring and proof upgrade — were started by a
GitHub Actions schedule set for every hour. They did not run every hour. The
scheduled runs #66 to #75, between 2026-10-03 12:35 and 2026-10-05 00:31
(Europe/Rome), leaving out the manual runs #71 and #76, started at intervals
from 0h48 to 8h21, with a median of 4h34. Runs before #66 were not examined.
The last GitHub run that started the jobs was the manual run #76, at 00:52 on
2026-10-05.
- **Stopping rule:** a delay only. Its decisions depend on the checkpoints —
the data counted at 30, 40, … revealed sessions — not on when the job ran,
so a late run computes the same decision later.
- **Anchoring:** the wait before a record reached a batch was longer than the
"up to an hour" written in the amendment of 2026-10-03. The verifier prints
the actual gap between a record being written and its Bitcoin block, so the
length of the window can be read from each proof.
### 2026-10-09 — reports and limits: when they took effect
"Reports and limits" is in force from 2026-10-09T11:30:00+02:00 (commit 59eceb1, committed at 10:27:47). Migration `0048` ran in production from 14:26:01 to 14:26:11, so the report rule and the database's cap on typed text applied from then. The limits enforced by the site's code — one session in progress, ten a day, the size and number of photographs, the length check at the seal — were pushed at 14:26:30 and served about a minute later, after a 35-second build: from about 14:27, not from the migration as the section says. Read-only counts at 14:48:47: no viewer report between 11:30 and the migration, no row in `pack_reports`, 744 packs active, and no session opened by an account between the migration and that read. Nothing was affected.
## Not yet declared
Listed so their absence is deliberate rather than an oversight. Each is required
by §13 before publication and none is settled:
- **The confirmatory hypothesis.** §13 names exactly one candidate: that a
composite degraded physical state (ill, hungry, unrested) predicts worse
performance, which was written down in 1980, before the data. Declared as
*available*, not yet as the hypothesis of a lodged study.
- **Everything else is EXPLORATORY**, in those words — `protocol`,
`input_method`, `entropy_gradient`, time of day, and all six pre-session state
items.
## Considered and not adopted
Measures weighed and rejected, each with its date and reason, so that the choice
is on record rather than inferred from an absence.
- **An exclusion for accounts driven by a script.** Considered on 2026-10-09 and not adopted. No criterion on data written before the reveal separates a fast person from a script with certainty: a seal needs a photograph, and the shortest path for a person — load the page, one button, choose a file, upload, Seal — is close to what a script needs; the ranking list can be locked untouched as soon as the five images arrive; and a person who types their impressions still uploads a photograph, and may upload the same one every time. Under the stopping rule each account is its own study ("Design: one study per viewer"), so such an account's sessions count only in its own study; Panel B's exploratory statistics include them like any other account's. The defence is the limits of "Reports and limits": one session in progress per account, at most 10 opened in 24 hours, and a report takes a pack out of the draw only when two accounts, each at least 7 days old with at least 10 revealed sessions, have reported it, and only above 640 active packs.
## Settled already, elsewhere
These are fixed in `CLAUDE.md` and repeated here only so the list reads as one:
- **Primary statistic:** sum-of-ranks, not hit rate (§13; Solfvin, Kelly &
Burdick 1978). Direct-hit rate is secondary.
- **Chance baseline:** 1/5. Five targets per pack, fixed before any pack was
built (§7).
- **Target type:** static images only, with the declared limitation that a null
result cannot distinguish "no effect" from "wrong target type" (§7).
- **Judging:** blind self-ranking of all five, submitted before reveal and
immutable once submitted (§8).
- **Abandoned sessions:** counted, as explicit nulls (§2.6, §8).
- **Guest sessions:** never enter the dataset at all (§8).
- **Interim looks:** every intermediate consultation of the results is recorded with its date and reason (`admin.research.open` in `admin_audit`, append-only). Before the research door (2026-10-02), every visit to `/admin/research` was also a look, recorded as `admin.research.view`, so the count can only overstate.