Repository navigation
feat: CLI contract v2 names a refused credential or role - #583
Merged
Merged
Conversation
The CLI contract moves to nokv.cli.contract.v2 with two classes for an owner that authenticates its clients over RPC TLS: unauthenticated (exit 10) when the connection has no TLS the owner requires or its certificate is not a current credential of the owner's tenant, and permission_denied (exit 11) when the principal holds no role that allows the request or continues an operation another principal began. Both hand the caller to an operator. operator_required narrows to what only an operator can change: durable or control state, or TLS settings that do not fit the owner. An owner that cannot refresh its policy copy is transient, since it asks for a retry. The classes flow from the wire codes and the handshake answers through the client, the Agent adapter, and the CLI, and the Python SDK reports the same names on its exceptions. The contract document gains the two rows, replaces its one-Agent-per-root scope with the authenticated principal, and the TLS gate expects the new classes. Relates to #566. Signed-off-by: wchwawa <wch19961116@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
The CLI contract moves to
nokv.cli.contract.v2: a refused credential isunauthenticated(exit 10) and a refused role or continuation ispermission_denied(exit 11), each withnext_action: operator, instead ofboth hiding in
operator_required(exit 9). The Python SDK reports the samenames, the contract document says what an authenticating owner means for
identities, and the TLS gate expects the new classes. This is R8 of #566,
the last piece of the R1 to R4 plus R8 MVP.
Problem
Relates to #566.
Since RPC TLS (#575) and role authorization (#578), an owner refuses a
client for three different reasons: its certificate is not a current
credential, it holds no role for the request, or (since #582) it tries to
continue another principal's operation. The CLI reported all of them as
operator_required, the class for quarantined state and exhaustedresources, so a harness could not tell "register or renew my credential"
from "grant me a role" from "the owner needs a human". The contract
document still said that one root is bound to one Agent and that the
principal is not authenticated, which has been untrue since #578.
Consequences
An Agent harness branching on
classhad one branch for four situationswith different remedies, and
operator_requiredlost its meaning as thesignal for state that only an operator can repair. A reader of the contract
was told the opposite of how a TLS owner behaves.
Example
A client whose certificate was revoked, before and after:
A reader that writes, or a principal renewing another principal's snapshot:
nokv version --jsonreports"cli_contract": "nokv.cli.contract.v2".Solution
ErrorClass::UnauthenticatedandErrorClass::PermissionDeniedjoin the failure vocabulary with exit codes10 and 11 and
next_action: operator;operator_requiredkeeps 9 andnarrows to durable or control state and TLS settings that do not fit the
owner. A read reports them unchanged.
Unauthenticatedisunauthenticatedunless theowner marked it retryable (it cannot refresh its policy copy and asks for
a retry), which is
transient; the wirePermissionDeniedispermission_denied. The handshake answersTlsRequiredandUnauthenticatedbecome a newTransportError::unauthenticatedrefusal;a TLS configuration or server-certificate failure stays
operator_required. The Agent adapter's own code table carries both.error_classandnext_actionfrom the same vocabulary, so the new names flow through; itsmapping table and surface test cover them.
names what changed from v1, and the Scope section describes the
authenticated principal: many Agents share a root, ids belong to the
principal that claimed them, and a plaintext owner authenticates nobody.
unauthenticatedfor an unregistered,plaintext, or revoked client,
permission_deniedfor a reader's write, arevoked role, and a snapshot renewed by another principal, and keeps
operator_requiredfor an owner-name mismatch.Gain
A harness can branch on the remedy: renew the credential, ask for a role,
or call an operator. The contract document matches the owner's behavior
since #578, and the gates pin the new classes end to end.
Tradeoffs
classagainstoperator_requiredor exit code 9 for a refused credential or role mustupdate; the version string says so and the document names the change.
report the two classes only once it does; the mapping is in place.
Unauthenticatedistransient. The owner's own answerdecides: a stale policy copy asks for a retry, a revoked credential does
not.
RuntimeErrorwith the classattributes, as for every other class.
Validation
All runs used Rust 1.99.1 on macOS:
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-targets -- -D warningsand the fourfeature combinations CI lints: clean.
cargo test --locked --no-fail-fast --workspace: 1,348 passed, 0 failed,16 ignored. New tests: the wire codes and handshake refusals by class,
the Agent adapter's table, the two exit codes and their next action; the
published-table test and the Python surface test cover the document.
rpc_tls_gate.py15 of 15with the new expectations,
cli_contract_gate.py6 of 6, and the directPython SDK contract gate 6 of 6 on a maturin debug wheel.
test_api_surface.pyand the gate unit-test suites pass.Not run:
yet, so the two classes were observed only through the CLI.
Size: 223 changed lines, under the 5,000-line review threshold.