<ResultConventions keywords="result, error-handling, isErr, TessellError, must, success, fail" type="coding-rule" ver="1.2">
  <Mission>
    Invariants for the Result pattern from @tessell/core/result. Seven conventions (R1–R7) are enforced via ESLint custom rules (eslint-plugin-tessell-result); each codifies an idiom from AGENTS.md that survived code review. The architectural convention AX_RESULT_PUBLIC_BOUNDARY is largely code-review-only — R7 enforces its mechanically-detectable subset. Canonical consumption idiom: `must` + try/catch.
  </Mission>

  <Belief_State>
    <Axiom id="AX_NO_RESULT_SECOND_GENERIC">
      `Result<T, ConcreteError>` is forbidden. No method can guarantee it only throws one error class — library and runtime code can throw anything. Use `Result<T>` only.
      Exception: when the second arg is a type parameter of the enclosing generic declaration (function / type alias / class) — that is a constraint, not a pinned class. Detection: AST walk up to nearest TSTypeParameterDeclaration; skip if name appears there.
      Autofix: remove second type argument.
    </Axiom>

    <Axiom id="AX_NO_RESULT_OBJECT_LITERAL">
      Manual `{ ok: true, data: x, error: null }` and `{ ok: false, data: null, error: e }` are forbidden. Always use `success(x)` / `fail(e)`.
      Rationale: bypasses the contract, breaks silently if Result shape evolves.
      Autofix: ok===true → success(data_value); ok===false → fail(error_value).
    </Axiom>

    <Axiom id="AX_NO_RESULT_ISERR_SHORT_CIRCUIT">
      `if (isErr(r)) return r` and `if (!isOk(r)) return r` are forbidden. Use `must` + try/catch — that is the whole point of the convention.
      No autofix: requires restructuring the surrounding function, not a one-line substitution.
    </Axiom>

    <Axiom id="AX_NO_RESULT_OK_SHORT_CIRCUIT">
      `if (!r.ok) return r` and `if (r.ok === false) return r` (property-access short-circuit form, the twin of AX_NO_RESULT_ISERR_SHORT_CIRCUIT) are forbidden. Use `must` + try/catch.
      Detection: IfStatement whose test is `!r.ok` or `r.ok === false`, whose consequent is a bare `return r` (or a block with exactly one such return), and whose returned identifier matches the object of the `.ok` access.
      No autofix: requires restructuring the surrounding function, not a one-line substitution.
    </Axiom>

    <Axiom id="AX_MUST_EXTRACTOR">
      Extract a Result's value with `must`: `must(result)` returns the data or throws the contained error. It is synchronous and takes only a resolved Result — for a `Promise<Result>` write `must(await fn())`, so a forgotten await is a compile error. Combine with try/catch at the boundary.
    </Axiom>

    <Axiom id="AX_NO_BARE_THROW_UNKNOWN">
      `throw catchBinding` without wrapping in `TessellError.cast` is forbidden when the binding is a CatchClause parameter (type unknown). Applies in wrapper/guard functions that rethrow, not only in catch blocks that return fail().
      Detection: scope analysis — ThrowStatement whose argument resolves to a CatchClause parameter, regardless of variable name (cause, err, e, error).
      No autofix: the fallback message is context-dependent.
    </Axiom>

    <Axiom id="AX_RESULT_PUBLIC_BOUNDARY" enforcement="convention">
      Result is the transport form of error across the public / port boundary — not an internal control-flow primitive. Public surface (port interface methods, exported factory return types, closures returned from factories) MUST return `Result<T>`. Inside the same class:
      - protected/private async helpers with I/O → throw; the public caller wraps in try/catch and returns `fail(error)` (per AX_CATCH_LOG_RECOVER in typescript-rules).
      - pure helpers (predicates, classifiers) → honest return type (`Error | null`, scalar, enum literal); not Result.
      - Result in protected is permitted only when the helper carries a typed `kind` the public call-site immediately extracts (via `must`) — and even then, throw is usually cleaner because it avoids the `Result → must → throw → catch → re-emit` double-packaging pattern.

      Rationale: mixing Result and null-pattern in helpers of one class produces structural noise (two error-channel styles in one surface) and double-packaging. The convention keeps Result as a boundary artifact, not an internal one.

      Not ESLint-enforced. The public/private boundary is not mechanically detectable: closures returned from factories (e.g. `defineDataStorageQuery → (storage, params) => Promise<Result<R>>`) are public-by-intent but private-by-syntax, and methods on factory-returned object literals (e.g. `defineDataOperation`) are not visible as class members. Enforced via code review and this directive.
    </Axiom>

    <Axiom id="AX_GUARDED_RESULT_BOUNDARY">
      A public Result-returning surface guards thrown errors. If a public class method or exported function returns `Result<T>` / `Promise<Result<T>>` and calls `must(...)`, that call must sit inside a try/catch whose `catch` returns `fail(...)` — otherwise the thrown error escapes the boundary instead of becoming `Err`.
      Detection: explicit return-type annotation `Result`/`Promise<Result>` + a `must` call outside try (no autofix; optional suggestion). ESLint-enforceable subset of AX_RESULT_PUBLIC_BOUNDARY — factory-returned closures stay code-review (private-by-syntax).
    </Axiom>

    <Axiom id="AX_CONFIGURABLE_NAMES">
      Every rule that matches function or type names accepts an options object with configurable identifiers: resultTypeName, isErrFn, isOkFn, successFn, failFn, castFn.
      Defaults match @tessell/core/result exports. On package rename — update config, not the rule.
    </Axiom>
  </Belief_State>

  <Reward_Criteria>
    - R1 flags `Result<T, DataStorageError>`, autofixes to `Result<T>`. Does NOT flag `Result<T, E>` where E is a type parameter of the enclosing generic.
    - R2 flags `{ ok: true, data: x, error: null }`, autofixes to `success(x)`.
    - R3 flags `if (isErr(r)) return r` with no autofix; error message cites `must` + try/catch.
    - R4 flags `throw e` inside any catch where e is the catch binding, regardless of variable name.
    - R5 flags `if (!r.ok) return r` and `if (r.ok === false) return r` with no autofix; does NOT flag `if (!r.ok) doSomethingElse()`.
    - R6 enforces `must` as the only Result extractor: autofixes to `must` where unambiguous, report-only otherwise; acts only on result-module identifiers, never foreign packages. Detection/autofix matrix: rule + TSK-58.
    - R7 flags an unguarded `must(...)` (or a catch not returning `fail`) in a public class method / exported function annotated `Result`/`Promise<Result>`; no autofix (optional suggestion). Does NOT flag private/protected, inferred-return-type, or factory-returned closures.
    - R3, R4, R5, R7 and the non-autofix path of R6 carry explanatory comment in eslint.config.ts (per eslint-setup AX_CUSTOM_PLUGINS_OBEY_META_POLICY).
    - Each rule has Vitest tests: positive case, negative case, edge case (R1: type param exception; R4: non-catch-binding throw not flagged; R6: autofixable vs report-only forms + foreign-import not flagged; R7: factory-closure/private/inferred-type not flagged).
  </Reward_Criteria>
</ResultConventions>
