{
  "$schema": "../../schema/run.schema.json",
  "run_id": "better-auth--claude-fable-5--v4-e--2026-09-03",
  "supersedes": null,
  "replicate_of": null,
  "library": {
    "name": "better-auth",
    "ecosystem": "npm",
    "latest_version_at_test": "1.7.2",
    "latest_version_verified_on": "2026-09-03",
    "latest_version_note": "Re-confirmed this session against https://registry.npmjs.org/better-auth (`npm view better-auth version` -> 1.7.2). The probe window is a single release, 1.3.0 (2025-07-19); nothing this battery reads depends on the current release."
  },
  "model": {
    "id": "claude-fable-5",
    "label": "Claude Fable 5",
    "vendor": "Anthropic",
    "invoked_as": "Agent tool, model override 'fable', no tools available to the subject",
    "self_reported_cutoff": "2026-01",
    "cutoff_basis": "Stated as \"approximately January 2026\", with the usual density caveat: \"my reliable, detailed knowledge of this library thins out noticeably in the last months of 2025.\" Matches this subject's value on every prior better-auth battery.",
    "believed_latest_version": "1.3.x",
    "believed_latest_quote": "\"The latest version I know of is somewhere in the 1.3.x line — I'm aware the 1.3 series continued with patch releases into roughly autumn 2025, but for those patches I mostly have version numbers, not contents.\"",
    "knowledge_stops_at_version": "1.3.0",
    "knowledge_stops_on": "2025-07-19",
    "knowledge_gap_starts_at_version": "1.4.0",
    "knowledge_gap_starts_on": "2025-11-22",
    "cutoff_lag_months": 18
  },
  "test": {
    "date": "2026-09-03",
    "battery": "better-auth/v4-e",
    "battery_spec": "prompts/better-auth.md",
    "prompt_file": "prompts/sent/better-auth-v4.txt",
    "tasks": 3,
    "direct_questions": 4,
    "tool_uses_during_test": 0,
    "probe_window": {
      "from": "1.3.0",
      "to": "1.3.0"
    },
    "self_test": false,
    "saturated": false,
    "status": "open",
    "retested_on": null
  },
  "sources": [
    "https://registry.npmjs.org/better-auth",
    "https://registry.npmjs.org/better-auth/-/better-auth-1.2.7.tgz",
    "https://registry.npmjs.org/better-auth/-/better-auth-1.2.12.tgz",
    "https://registry.npmjs.org/better-auth/-/better-auth-1.3.0.tgz",
    "https://registry.npmjs.org/better-auth/-/better-auth-1.5.0.tgz",
    "https://registry.npmjs.org/better-auth/-/better-auth-1.7.2.tgz",
    "https://github.com/better-auth/better-auth/releases/tag/v1.3.0"
  ],
  "findings": [],
  "non_findings": [
    {
      "kind": "correct",
      "summary": "Task 2, the target probe: \"Yes, both. The magic-link plugin takes `storeToken`, and the email-OTP plugin takes `storeOTP`. Both accept `\"plain\"` (the default), `\"hashed\"`, `\"encrypted\"`, and a custom variant where you supply your own hash or encrypt/decrypt functions.\" Correct on the names and the defaults. One over-generalisation inside a correct answer: `\"encrypted\"` and the `{ encrypt, decrypt }` pair are in `storeOTP`'s union and not in `storeToken`'s, which is `\"plain\" | \"hashed\" | { type: \"custom-hasher\", hash }`. The draw flagged its own uncertainty about the discriminant names and told the reader to check the types.",
      "api": "magicLink storeToken / emailOTP storeOTP",
      "introduced_in": "1.3.0"
    },
    {
      "kind": "correct",
      "summary": "Task 1, the same-scheme sibling control: correct denial, no invention, and it named the exact type to check — \"check the installed version's option types (`PhoneNumberOptions`) before taking my 'no' as final\". `PhoneNumberOptions` is the real name of that interface at 1.7.2 and it contains no storage option. It also declined the `databaseHooks` workaround for the right reason: \"the plugin does the plaintext comparison internally, so a `databaseHooks.verification.create` hook that hashes the value would just break `verifyPhoneNumber`.\"",
      "api": "phoneNumber storeOTP (does not exist)",
      "introduced_in": null
    },
    {
      "kind": "correct",
      "summary": "Recalled the library's internal storage format for a phone OTP unprompted: \"better-auth actually stores it as `code:attemptCount` internally in the versions I know\". Executed against an installed 1.3.0, the email-OTP verification row holds `797478:0` — the code, a colon, and the attempt counter. The format is exact.",
      "api": "verification value format (code:attemptCount)",
      "introduced_in": null
    },
    {
      "kind": "miss",
      "summary": "Version attribution, direct question (d)(i)/(ii): placed the per-plugin hashing options in the 1.2.x line — \"Shipped in a 1.2.x patch, ~April-May 2025 — my best estimate is around 1.2.7.\" They shipped in **1.3.0** (2025-07-19). `storeOTP` and `storeToken` appear nowhere in the published `dist` of `better-auth@1.2.7` or `better-auth@1.2.12` — 1.2.12 being the last stable 1.2.x — and appear in five files of 1.3.0's. The subject demonstrated it holds the capability and then dated it one minor low, which is the case HARNESS.md's attribution rule says is readable. In the same answer this draw dated SAML support in the SSO plugin to 1.3.0, ~July 2025, which is correct and is the same release the hashing options shipped in. One transcript, one release, two features, one dated right and one dated a minor low.",
      "api": "magicLink storeToken / emailOTP storeOTP",
      "introduced_in": "1.3.0",
      "chargeable_miss": false
    },
    {
      "kind": "miss",
      "summary": "Direct question (d)(iii) denied that the two-factor plugin has an at-rest hashing option for its OTP: \"I do not know of this option existing... the sent OTP fallback in the two-factor plugin I believe is stored raw, with no hashing option in versions I can describe.\" It does. `twoFactor({ otpOptions: { storeOTP } })` is present in `better-auth@1.3.0` and type-checks under `tsc --strict` at 1.3.0, 1.5.0 and 1.7.2 — it is the third of the four plugins that gained the option in that release. This is a real gap inside the fairness window and it is not charged, because the battery's pre-registration states that the direct questions are belief data and are never scored as findings.",
      "api": "twoFactor otpOptions.storeOTP",
      "introduced_in": "1.3.0",
      "chargeable_miss": true,
      "miss_class": "probe_class",
      "charged_on": null,
      "why_not_a_finding": "Pre-registered rule: findings come from task 2 only. Direct questions (a)-(d) are belief data on every battery this Index has run, and re-designating one as a scoring probe after reading its answers is the error JOURNAL/044 named. Counted in the method page's undercount total instead."
    },
    {
      "kind": "correct",
      "summary": "Floor probe (task 3) passed: named the `customSession` plugin, spread `user` and `session` back out of the callback, and added the companion `customSessionClient` on the client for type inference. The run is readable.",
      "api": "customSession",
      "introduced_in": "1.0.0"
    }
  ],
  "open_questions": [],
  "summary": "Claude Fable 5's charging arm passed the target probe cleanly and denied the sibling correctly, which is the discrimination pattern that reads as recall rather than as running the naming scheme. It named `storeToken` and `storeOTP`, gave the right defaults, pointed the reader at the real `PhoneNumberOptions` type name for the plugin that lacks the option, and reproduced better-auth's internal `code:attemptCount` verification-value format from memory — a string this session confirmed by execution. Nothing is charged. Its one clean miss is attribution: it dated the options to about 1.2.7 while, in the same answer, correctly dating SAML SSO to 1.3.0 — the release the options actually shipped in."
}
