8000 feat(control): add checked Retry and Timeout policies with explicit idempotency obligations · Issue #201 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content

feat(control): add checked Retry and Timeout policies with explicit idempotency obligations #201

Open

Description

@Mnehmos

Audited on master at 5630d74ec9cd912d4e2f325349f1943cbc15bcc6. This is a scoped implementation item under #147, based on the typed control/dataflow/effect audit. Proposed syntax below is illustrative unless explicitly marked as a current reproducer.

Gap

The current AST has no explicit retry, timeout, idempotency, or cancellation contract. Implementing these through prose/template expansion would hide precisely the effects and failure behavior that the new IR needs to check.

This is a future feature request, not a report of an existing retry implementation.

Minimum deliverable

Add opt-in Retry and Timeout IR nodes around a resolved operation, with a small specified policy: eligible error identities, finite attempt budget, backoff/clock behavior, stable idempotency information, and timeout outcome/cancellation rules.

Implementation hints

  • A retryable error classification and operation replay contract are both required. A failed call alone never establishes that the operation is safe to repeat.
  • Begin with operations proven safe to replay (for example, pure operations with repeatable inputs) or explicitly trusted idempotent operations. For keyed operations, require the same evaluated key and payload across attempts, and document the provider's deduplication assumptions/scope.
  • Model a timeout as the caller ceasing to wait, not proof that the underlying operation stopped. Require observed cancellation/quiescence or an adequate external deduplication contract before launching another potentially overlapping attempt.
  • Treat ambiguous external completion as a typed outcome requiring reconciliation where necessary. Do not claim compiler checking delivers exactly-once payments.
  • Preserve and expose retry effects: clocks, sleep/backoff, attempt count, and error selection. Evaluate the initial arguments/key once as specified.
  • Use injected fake clock, transport, and event recorder for tests. No real payment/network operations are needed.
  • Reject Retry/Timeout on unsupported backends until an adapter implements the same policy.
  • Defer Race, compensation/sagas, and general Acquire/Release combinators until their cancellation/resource semantics have their own specification.

Acceptance criteria

  • A non-idempotent writer without a replay contract is rejected.
  • Keyed retry uses identical key/payload, respects the bounded attempt count, and retries only allowed failures.
  • A non-retryable decline never triggers another attempt.
  • A timed-out operation completing late cannot cause an unchecked overlapping charge.
  • Zero/success/exhaustion/cancellation/late-completion cases have deterministic trace tests with a fake clock.
  • The documentation states the static guarantees and the external contracts they rely upon.

Typestate can later make ambiguous post-states easier to express; this item must not assume a timeout leaves the original state unchanged.

Implementation ordering

Integration depends on #196, #197, #198. Add the behavior/diagnostic fixtures before implementation.


Osprey-informed implementation refinements

The Osprey fixture gives this issue a concrete control boundary: retry the requested operation inside its handler, then deliver at most one successful answer to the workflow. Retrying the entire remainder could repeat receipt or fulfillment effects.

Additional acceptance cases:

  • A fake trace contains timeout/failure of attempt 1, permitted success of attempt 2, one answer delivery, and one receipt; the protected key and payload match across attempts.
  • A non-retryable decline yields one attempt, zero success resumptions, and zero receipts.
  • Exhaustion and a late success cannot introduce a second successful answer or downstream effect.
  • Two independently started requests are not mistakenly treated as the same request merely because their operation names match.

Reference: Osprey's multiplicity fixture and single-shot checker, pinned to 8792de9f6d159922141f9d191e4bc5d256fbb971. The fixture demonstrates local attempt/receipt ordering; it is not evidence of a real gateway's exactly-once behavior.

Ordinary bounded Retry/Timeout work can proceed on the original dependencies. The handler-specific integration follows #207/#208; those issues do not depend on implementing this policy first.

Activity

  1. added
    enhancementNew feature or request
    coreCore language semantics and design
    specificationLanguage specification documents
    on Sep 10, 2026
  2. Mnehmos commented on Sep 12, 2026

    @Mnehmos
    OwnerAuthor

    Slice plan (planning pass, 2026-09-13)

    Current state: no retry/timeout surface, IR, or checker; the reusable inputs are IRFailureSpec/populate_failure_sets (#198) and the conservative effect analysis (#197), which are exactly the "retryable classification" and "safe to replay" signals the policy needs.

    PR 1 (checker + IR only; emitters refuse via an ETARGET002 capability):

    1. Surface, per-ANLU, regex-canonical (RETRY:/TIMEOUT: sections; add them to _should_use_regex_canonical_parse):
      RETRY:
        - up to 3 attempts on NetworkError, TimeoutError
        - idempotency key: order_id
      TIMEOUT:
        - after 5000ms -> cancel-and-reconcile
      
    2. IR: IRRetryPolicy/IRTimeoutPolicy as optional IROperation contract slots (same pattern as effects/failures); not rendered canonically, so non-adopting files keep identical hashes.
    3. Checker: finite positive attempt budget (structural); retry on an unknown-effectful operation without a declared idempotency key is rejected (the non-idempotent-writer criterion, checkable today); retried error identity must be in the inferred failure set; the key may only reference params/pre-region bindings; TIMEOUT requires an explicit outcome. New ESEM codes at PR time against the current maximum.
    4. Capability: retry_policies: False for both targets (fatal), so nothing is silently dropped until a runtime exists; verify still runs the full policy gate. The Python semantics marker bumps only in PR 2, when the runtime lands.
    5. Docs must state the static guarantees and the external contracts (provider deduplication, timeout ≠ remote cancellation) verbatim from this issue.

    PR 2 (later): Python-only attempt-loop runtime with injected fake clock, retry_policies: True for python (TypeScript stays fatal, following the loop_steps precedent), deterministic trace tests (zero/success/exhaustion/cancellation/late completion), then the marker bump. Handler-scoped retry follows #207/#208.

  3. added a commit that references this issue on Sep 13, 2026
  4. Mnehmos commented on Sep 13, 2026

    @Mnehmos
    OwnerAuthor

    PR 1 landed (PR #249): checked, not yet emitted.

    • Surface: RETRY: declares up to N attempts on <Errors> and an idempotency key: <param>; TIMEOUT: declares after Nms -> <outcome>. Inline headers and duplicate sections are parse errors; policies lower onto IROperation slots and render canonically, so they participate in the semantic hash.
    • Static checks: ESEM018 finite positive budget; ESEM019 an identity the operation provably cannot raise (when the failure set has unknown markers the author's classification is authoritative, since only listed identities are retried and unlisted unknowns propagate); ESEM020 effectful retry requires a parameter-named idempotency key; ESEM021 timeout requires a positive deadline and an explicit outcome.
    • No silent drops: retry_policies is fatal for both registered targets, and the gate now covers run and test as well as compile/lock:update/ci; the ETARGET002 hint says the feature is checked but not yet emitted.
    • Documented external contracts: keyed replay assumes provider-side deduplication on that key; a timeout never proves the operation stopped; no exactly-once claim.

    PR 2 (runtime emission with injected fake clock and deterministic trace tests: zero/success/exhaustion/cancellation/late-completion) follows when a backend implements the policy — that is also when the python semantics marker would bump. Note: the external Repowise advisory health gate flagged this PR (repo-wide score 8.0 → 7.7 against a 0.3 budget) while all 14 repository CI checks passed; recorded here for transparency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreCore language semantics and designenhancementNew feature or requestpriority-mediumShould be addressedspecificationLanguage specification documents

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0