Skip to content

fix(client): authenticate inline publication receipts; regrade the append gates - #555

Merged
wchwawa merged 3 commits into
mainfrom
fix/inline-receipt-and-append-gates
Oct 21, 2026
Merged

wchwawa merged 3 commits into
mainfrom
fix/inline-receipt-and-append-gates

Conversation

@wchwawa

@wchwawa wchwawa commented Oct 21, 2026

Copy link
Copy Markdown
Member

Related Issue

Relates to #551
Relates to #552

Two follow-ups to the inline publication path that #552 merged: one client
defect it introduced, and the append acceptance gates it left unable to fire
their faults.

Summary

  • Inline receipts are authenticated before they are returned. For a body
    of at most 16 KiB, a publication that reused an operation id with other
    inputs returned the original publication's receipt as a success
    (replayed: false, the first body's digest), and the second body was never
    written. A body above the bound already failed with "publish operation id was
    reused with different immutable inputs". On an inline error the client now
    reads the durable operation row once:

    • it returns a receipt, marked replayed, only when the row records exactly
      this publication: operation id, target, revision, size, body digest, and the
      generation its claim implies;
    • a refused identity or precondition keeps its typed error;
    • an absent operation is not aborted.
  • The append gates fault the requests a publication actually sends. A
    small append now commits in one PublishInlineArtifact request and a
    one-batch body in the fused Prepare/Finish pair. The identity, operations,
    and product gates still armed their faults on stepwise requests, so their
    response-loss cases waited for a request that never came. Each faulted
    fixture now:

    • declares the publication shape it needs, and fails as a shape mismatch
      rather than a missed fault when the command sends another one;
    • uses a body past the inline bound when it needs objects.

    Inline variants cover the one-request commit boundary. The identity gate's
    lost-admission retry now follows the product contract
    (append-product-spec.md, recovery timelines): once cleanup proves the lost
    attempt cannot publish, the same logical id completes once on the current
    head. The gates read the pinned Holt version from Cargo.lock.

Commits:

  • fix(client): authenticate an inline publication before returning its receipt
  • test(workbench): regrade the append gates for inline and fused publication

Scope

  • This PR changes one logical boundary only: what the inline publication
    path returns, and the gates that exercise it.
  • No unrelated refactor, benchmark, metadata model, Holt layout,
    object-store, agent interface, or docs change is mixed in.
  • The linked issue describes the user-visible problem, design decision, or
    maintenance task this PR resolves.
  • Any breaking change is intentional and documented (none).
  • No compatibility shim, deprecated alias, or forwarding wrapper was added
    without a removal condition (none added).

Change Size And Review

  • I checked GitHub's additions plus deletions for this pull request:
    +835 / -117 (952 lines).
  • If the total is more than 5,000 lines, one core maintainer other than the
    current-head pusher approved the exact current head commit. Not
    applicable: under 5,000 lines.
  • This change is still small enough for a focused review.

Code Contract (Code Changes Only)

  • Not applicable; this is a docs/config-only change.
  • Package boundaries follow docs/development/code_contract.md: the receipt
    check is in nokv-client/src/artifact.rs, beside the inline path.
  • Shared helpers reuse the standard library or existing repository helpers.
  • File names and file placement follow the code contract.
  • New types, interfaces, structs, fields, and functions use domain-specific
    names (InlinePublicationRow, observe_inline_publication,
    records_inline_publication).
  • New errors are in the owning package's errors.rs (none added).
  • New metrics/stats are owned by the package that reports or serves them
    (none added).
  • Metadata changes document durability, atomicity, revision-reference
    lifetime, snapshot/commit/event retention, GC epochs, and fail-closed
    recovery boundaries. Not applicable: no metadata change.
  • Placement or routing changes. Not applicable.
  • Agent-interface changes. Not applicable.
  • User-facing integration guidance. Not applicable.

Claims and Evidence

  • User-facing documentation distinguishes current, experimental, and
    planned capabilities. Not applicable: no user-facing documentation change.
  • Security claims do not treat a Workbench root or Agent scope as tenant
    authentication or authorization (no security claims).
  • Performance claims. Not applicable: no performance claim.
  • Benchmark-only behavior does not alter product semantics (none).

Validation

  • Command and result: with Rust 1.99.1, cargo fmt --all -- --check is clean,
    and cargo clippy --workspace --all-targets -- -D warnings passes, as do the
    feature clippy runs from rust.yml. cargo test -p nokv-client passes.
    Four new tests fail before the fix and pass after it:

    • an_inline_identity_reused_for_other_inputs_is_refused_without_a_stale_receipt
    • an_inline_publication_whose_response_was_lost_returns_its_receipt_as_a_replay
    • an_inline_retry_refused_across_owner_epochs_recovers_its_own_receipt
    • an_inline_create_refused_at_admission_does_not_abort_an_absent_operation

    A live probe of the defect on d4ada8d441 returned the first body's
    receipt for the reused id. After the fix it returns
    RequestReplayMismatch, including after an owner restart.

  • Command and result: the three append gates pass on this code against etcd
    3.7.1 and the pinned RustFS image:

    • append_identity_recovery_gate.py --mode qualified --scenario all: 9/9
      PASS in 175 s. --mode baseline still reproduces the legacy duplicate
      append.
    • append_operations_acceptance_gate.py: 4/4 PASS in 238 s.
    • append_product_acceptance_gate.py --append-activity-lease-ms 1000 --scenarios <19 native cases>: 19/19 PASS, in each of 3 consecutive runs.

    On the same binary, the pre-change gates time out or report the cases NOT
    QUALIFIED.

  • Not run (with reason): legacy_wire_rejected and legacy_format_rejected
    need a frozen pre-stack binary (--legacy-nokv-bin), which this host does not
    have. These gates are not part of rust.yml.

Contributor Sign-off

  • Every commit in this PR includes a DCO Signed-off-by trailer.

…receipt

After any failed PublishInlineArtifact request the client read the
operation row and returned a succeeded row as this publication's result.
It checked only the row's kind and operation id and reported the result
as not replayed. Reusing an operation id for other bytes therefore
returned the earlier publication's receipt as a success while the path
kept the earlier body. A create-only claim refused at admission also
tried to abort an operation that was never admitted and reported a
NotFound abort failure.

The client now observes the row once and accepts a succeeded row only
when its receipt records this publication: the same target, revision,
logical size and body digest, and the generation its claim implies. That
receipt is returned as a replay, so an exact retry still converges after
a lost response or after an owner restart, which refuses the epoch-bound
identity. A receipt of other inputs leaves the server's error unchanged.
A missing row means nothing was admitted, so the refusal is returned
without an abort; a running row of this publication is aborted as before.

The scripted transport now refuses a reused identity with other inputs,
a create-only claim on an existing path and an unknown operation as the
engine does, and can lose inline responses or refuse an inline replay
after an epoch change.

Signed-off-by: wchwawa <wch19961116@gmail.com>
…ation

Since the fused and inline publication paths landed, a small append
commits in one PublishInlineArtifact request and a one-batch body in the
Prepare/Finish pair. The append gates still armed their faults on the
stepwise requests, so the identity gate's response-loss cases waited for
a request that never came and the operations gate could not quarantine.

Each faulted fixture now declares the publication shape it needs and
fails as a shape mismatch, not as a missed fault, when the command sends
another one. Fixtures that need objects use a body past the inline
bound; inline variants cover the one-request commit boundary. The
identity gate's lost-admission retry follows the product contract: once
cleanup proves the lost attempt cannot publish, the same logical id
completes once on the current head. The gates read the pinned Holt
version from Cargo.lock.

Signed-off-by: wchwawa <wch19961116@gmail.com>
@wchwawa
wchwawa merged commit d52b31c into main Oct 21, 2026
8 checks passed
@wchwawa
wchwawa deleted the fix/inline-receipt-and-append-gates branch October 21, 2026 11:29
@github-project-automation github-project-automation Bot moved this from In Progress to Done in NoKV Delivery Oct 21, 2026
feichai0017 pushed a commit to feichai0017/NoKV that referenced this pull request Oct 29, 2026
Bump crates/nokv and crates/nokv-python to 0.12.1, together with the
workspace dependencies and the build identity test, for the inline
receipt fix in NoKV-Lab#555. The wire protocol (nokv.workspace.rpc.v14) and the
metadata format (System format 14) are unchanged, so 0.12.1 and 0.12.0
clients, CLIs, and owners interoperate.

The Python SDK install notes use 0.12.1 and describe the 0.12 line. The
LoopX Stage 2A install example still pinned the 0.11.1 wheel, which the
stack script refuses next to a 0.12 owner; it now names 0.12.1.

Signed-off-by: wchwawa <wch19961116@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant