Bring the Rust SDK to documentation parity with Python and TypeScript - #142
Bring the Rust SDK to documentation parity with Python and TypeScript#142ron-42 wants to merge 5 commits into
Conversation
sdk/rust is feature-complete and published on crates.io, but was not surfaced anywhere else in the repo: the README install table listed only Python, TypeScript, and the CLI; skills/ shipped inkbox-python and inkbox-ts with no Rust equivalent; and examples/ had no Rust agent (the two files under sdk/rust/examples/ are low-level smoke checks). A Rust user landing on the repo README had no signal the SDK existed. Docs, skills, and examples only — no change to sdk/rust's implementation. README.md Rust row in the install table (cargo add inkbox, linking ./sdk/rust/ and crates.io), a crates.io badge, and sdk/rust/ plus the two new directories in the "What's in this repo" table. skills/inkbox-rust/ New SKILL.md mirroring skills/inkbox-ts/SKILL.md section for section, adapted to Rust idioms: Arc<Inkbox>, inkbox::Result<T> / InkboxError, positional arguments, the Unset<T> and Option<Option<T>> tri-state sentinels, typed enum variants, and the tunnels-runtime feature gate. It also names where Rust genuinely differs — iter_emails drains every page eagerly into a Vec, A2A receiver config and the outbound protocol client are Python/TS only, tunnels forward to a URL rather than an in-process handler, and UnlockedVault mutators take &mut self — so the skill does not promise what the crate cannot do. skills/README.md, skills/inkbox-all/SKILL.md Register the new skill in the skills table and the skills index, with a cargo add prerequisite and a How To Choose entry. examples/use-inkbox-rust/ Four runnable binaries in one crate, numbered to line up with the scripts in use-inkbox-cli: 01-identity-and-email, 02-vault-totp, 04-inbox-monitor, and 07-signup. use-inkbox-signup and use-inkbox-vault are polyglot directories that had no Rust counterpart; those flows are covered here. One dependency, no async runtime. Teardown runs even when a step fails, and billable or outward-facing steps are opt-in. Verified: cargo build, cargo fmt --check, and cargo clippy --all-targets -D warnings all pass against inkbox v0.5.14 from crates.io. Every code sample in the skill was extracted into a scratch crate and type-checked against the published crate, which caught four defects before they shipped (created_ids is a method not a field, source_url is an Option, event_type is a typed enum, and the create response flattens its base under .subscription). Examples 01, 02, and 04 were run end to end against a live organization, plus 07's cleanup path; a follow-up list confirmed no residue. That live run also surfaced the 409 agent_handle_unavailable case and its HandleUnavailableError view, now documented under Error Handling. CI is unaffected: tests.yml only builds sdk/rust, and the example is a standalone crate outside any workspace.
dimavrem22
left a comment
There was a problem hiding this comment.
Two blocking issues:
-
The newly advertised Rust 1.74 minimum does not work with a fresh dependency resolution.
cargo +1.74.1 buildinexamples/use-inkbox-rustfails before compilation because the current transitive graph includes a Rust-2024 manifest and dependencies requiring newer compilers. The successful build on current stable does not verify the documented MSRV. Please either raise the documented minimum consistently or constrain the dependency graph to 1.74-compatible releases, and add a minimum-toolchain CI check. -
02-vault-totpdoes not fulfill the promise that teardown runs when a step fails. Oncecreate_secretsucceeds, any later error returns throughrun;cleanupdeletes only the identity, while the vault secret remains unless execution reaches the success-pathdelete_secret. Please retain the secret ID outside the fallible workflow and best-effort delete it on every exit before deleting the identity.
I verified the current-toolchain build, formatting, and clippy checks pass; these failures are in the advertised compatibility and error-cleanup paths.
Two review findings, both real. 1. The advertised Rust 1.74 minimum was never achievable. rust-version was copied from sdk/rust/Cargo.toml and only ever verified on current stable, which proves nothing about the floor. With a fresh resolution, cargo 1.74 cannot even parse the manifest of rand_pcg 0.10.2 (edition 2024), and 1.85 is rejected by icu_* 2.2 and idna_adapter 1.2, which require 1.86. Measured the real floor rather than guessing: 1.74.1 and 1.85.0 both fail, 1.86.0 resolves and builds. The example now declares rust-version = "1.86", and Cargo.lock is committed so that number is reproducible instead of drifting with upstream releases — .gitignore updated accordingly, with the reasoning recorded in both files. Documented minimums are now consistent across the root README install table, skills/README.md, the skill, and the example README. The skill and example README also explain why the crate's own declared 1.74 is not attainable, so the next reader does not "fix" it back. Added CI: rust-example builds, clippies, and format-checks with --locked on both 1.86.0 and stable. A second advisory job, rust-example-fresh-resolution, deletes the lockfile and rebuilds on 1.86.0, so dependency drift past the documented minimum surfaces as a warning rather than a surprise for the next contributor. This is the one place this branch touches something outside docs, skills, and examples. 2. 02-vault-totp orphaned its vault secret on any mid-workflow failure. The secret id lived inside run(), and delete_secret sat on the success path, so any error after create_secret returned straight past it; cleanup then deleted only the identity. Confirmed the leak is real rather than assumed — a vault secret is an organization-level row that does not cascade: creating one, deleting its identity, and listing secrets leaves the row behind. The id is now recorded in an out-parameter the moment the secret exists, and cleanup best-effort deletes it on every exit, before the identity. The success-path delete is gone, so there is exactly one teardown path. Verified by injecting a failure immediately after creation: the secret is deleted, the identity is deleted, and the original error still propagates. The happy path is unchanged. 02 also now honours INKBOX_AGENT_HANDLE like the other three binaries, since deleted handles stay reserved and a fixed constant made re-runs fail with a 409. Verified: build, clippy -D warnings, and fmt --check pass with --locked on 1.86.0 and stable; 1.85.0 and 1.74.1 still fail, so the floor is meaningful. Skill code samples still type-check with zero errors.
dimavrem22
left a comment
There was a problem hiding this comment.
One remaining documentation fix is needed.
| Admin-only free-form notes with per-identity access grants. There is no wildcard for notes — grant identities explicitly. Note and identity ids are `Uuid`, not strings. | ||
|
|
||
| ```rust | ||
| use uuid::Uuid; |
There was a problem hiding this comment.
Please remove this unused import. The skill tells users to depend only on inkbox; uuid is only transitive and is not available to consumer code, so this snippet fails with E0432 unless users separately add uuid.
There was a problem hiding this comment.
Good catch, removed in 82b2ffe.
My snippet check had uuid as its own dependency, so it was not building the way a reader would. Rebuilding it with inkbox alone also caught two spots that genuinely need serde_json (webhook payload, tunnel metadata), now flagged at the call site and in Install.
The skill compiles against inkbox plus serde_json and nothing else.
Follow-up to bd28d72, which corrected the example but left the published crate declaring a minimum it cannot meet. sdk/rust/Cargo.toml declared rust-version = "1.74". That is not achievable, and — unlike the example — a committed Cargo.lock does not rescue it, because a library's lockfile does not apply to its consumers: `cargo add inkbox` always resolves fresh. Measured rather than assumed: 1.74.1 fails even with --locked; base64ct 1.8.3 in the committed lockfile is an edition-2024 manifest that cargo cannot parse 1.82.0 same, fails on base64ct 1.8.3 1.85.0 rejected by icu_* 2.2 1.86.0 builds and passes the full suite (231 and 293 tests across default and tunnels-runtime) So the floor is 1.86 for consumers and for developing the crate alike. Bumped rust-version to 1.86 and documented the reason in the manifest and sdk/rust/README.md — the constraint comes entirely from the transitive graph, not from anything in this crate's source, which is why it drifted unnoticed. This is a metadata and documentation change: no source and no lockfile in sdk/rust is modified. Added a rust-msrv CI job building and testing both feature sets on the declared minimum, so the floor cannot regress silently the next time a dependency raises its own. rust-tests continues to cover stable. Also narrowed the rust-example job: clippy and rustfmt now run on stable only. Lint sets drift between compiler releases, so pinning them to the MSRV toolchain would fail on lints the minimum-supported build has no say over. The MSRV entry still builds. Every Rust MSRV claim in the repo now reads 1.86 and agrees with the manifests: the root README install table, skills/README.md, the skill, sdk/rust/README.md, both Cargo.tomls, and the CI matrices. The passages that previously explained "the crate declares 1.74" were updated rather than left to contradict it. Verified: rust-msrv, rust-tests, and the rust-example matrix all reproduced locally and pass.
07-signup had a fail_str wrapper that only forwarded to fail, left over
from an earlier iteration, and unique_suffix summed subsec nanos with
whole seconds. That sum is meaningless and its {:08x} format implied a
width the value did not have. Use the low 32 bits of the nanosecond
timestamp so the suffix is exactly 8 hex characters.
No behaviour change beyond the suffix format.
Both fixed, thanks. 1. MSRVRaised the minimum instead of pinning deps. Tested it this time:
Note that
New CI jobs:
2. TeardownThe secret id now lives outside Forced an error right after creation to check: I also confirmed vault secrets do survive identity deletion, so this was a real leak. Also Commits: |
The Notes snippet opened with `use uuid::Uuid;`. The import was unused, and worse, unusable: the skill tells readers to run `cargo add inkbox`, and inkbox does not re-export uuid, so the snippet failed with E0432 unless the reader separately added the crate. My verification harness never caught this because it listed uuid and serde_json as its own dependencies, so it was not building the way a reader following the skill would. Rebuilt it with inkbox alone, which surfaced every snippet quietly relying on a crate the skill never mentions. Two are real and legitimate: deserializing a webhook payload and building tunnel metadata both need serde_json. Both are now called out at the call site, and Install & Init states up front which extra crates are needed and why inkbox does not provide them. The harness now depends on exactly what the skill tells a reader to add, with uuid deliberately absent so a snippet needing it fails the check rather than passing silently. Verified: the full skill compiles against inkbox + serde_json only.
Fixes #141
The gap
sdk/rustis feature-complete and published on crates.io asinkboxv0.5.14, but it is invisible from everywhere except its own directory:sdk/rust/missing from "What's in this repo" — even thoughRELEASING.mdalready documents publishing to crates.io.inkbox-pythonandinkbox-ts. A coding agent asked to add Inkbox to a Rust project has no skill to load, so it guesses at the API or falls back to a Python/TS idiom that does not compile.sdk/rust/examples/holds two low-level smoke checks, not agent examples.use-inkbox-signup/anduse-inkbox-vault/are polyglot (.py+.ts) and Rust is conspicuously absent from both.Net effect: a Rust user landing on the repo README has no signal the SDK exists.
Full evidence — the per-SDK example coverage table and the seven ways the Rust API actually diverges from Python/TS — is in #141.
What this PR adds
Docs, skills, and examples only. No change to
sdk/rust's implementation.1.
README.mdcargo add inkbox, linking./sdk/rust/and crates.io.sdk/rust/,skills/inkbox-rust/, andexamples/use-inkbox-rust/rows in "What's in this repo", so the two new directories are reachable.2.
skills/inkbox-rust/A new SKILL.md mirroring
skills/inkbox-ts/SKILL.mdsection for section — same headings, same order, same tone and capability coverage (signup, identities, mail + imports + storage caps, mail clients, phone, text/SMS, iMessage, SMS opt-ins, A2A, vault, TOTP, admin resources, contact rules, contacts, notes, whoami, tunnels, webhooks, error handling, key conventions).Adapted to Rust idioms rather than transliterated:
Arc<Inkbox>,inkbox::Result<T>/InkboxError,?propagation.Unset<T>andOption<Option<T>>tri-state sentinels called out explicitly — the single biggest porting trap.MailRuleAction::Allow,CallOrigin::DedicatedNumber,SecretPayload::Login(..)) rather than string literals.matchon typedInkboxErrorvariants instead of exception subclasses.tunnels-runtimecargo feature gate.It also documents where Rust genuinely differs from the other SDKs, so the skill does not promise things the crate cannot do:
iter_emailsdrains every page eagerly into aVec<Message>— not a lazy generator.spawn_blockingin an async context.UnlockedVaultmutators take&mut self.skills/README.mdgains theinkbox-rustrow, acargo add inkboxprerequisite, the manual-installcpline, and a "six skills" → "seven skills" correction.skills/inkbox-all/SKILL.md— the index-of-skills skill — gainsinkbox-rustunder Core Skills,use-inkbox-rustunder Related Examples, and a "How To Choose" bullet. Its Rust entry names the deltas above so an agent picking a skill knows what Rust does not do before it commits.3.
examples/use-inkbox-rust/Four runnable examples as numbered binaries in one crate, deliberately mirroring the numbered scripts in
use-inkbox-cliso the two sets line up:01-identity-and-email01-identity-and-email.sh+03-phone-call.sh02-vault-totp02-vault-totp.sh04-inbox-monitor04-inbox-monitor.sh07-signupuse-inkbox-signup/agent_signup.pyuse-inkbox-signupanduse-inkbox-vaultare polyglot directories that had no Rust counterpart; those flows are covered here rather than by adding aCargo.tomlto each. Numbering leaves gaps where no Rust counterpart exists yet.inkbox. No clap, no uuid, no async runtime.default-runkeeps a barecargo runworking for 01; the rest usecargo run --bin <name>.INKBOX_DEMO_PHONE, signup registration is its own subcommand.02-vault-totpdeliberately demonstrates the snapshot semantics ofcredentials()— it prints the login count before and after re-unlocking, because a freshly created secret is not in the unlock snapshot.Verification
Every Rust snippet is grounded in
sdk/rust/src— no invented methods.Compile.
cargo build,cargo fmt --check, andcargo clippy --all-targets -- -D warningsall pass inexamples/use-inkbox-rustagainstinkboxv0.5.14 from crates.io.Every code sample in the skill type-checks. All snippets in
skills/inkbox-rust/SKILL.mdwere extracted into a scratch crate and compiled against the published crate — zero errors. The only warnings are the intentional uses ofmail_contact_rules(),phone_contact_rules(), and the org-levelcreate_signing_key(), all three of which the skill labels DEPRECATED. This caught four real defects before they shipped:ContactImportResult::created_idsis a method, not a field (and the per-card list isresults, noterrors)ContactFactCitation::source_urlisOption<String>TextWebhookPayload::event_typeis a typed enum, not a stringWebhookSubscriptionCreateResponseserde-flattens its base under.subscriptionEnd-to-end against the live API. Examples
01,02, and04all ran clean against a real organization with an admin-scoped key, plus07'scleanupand argument-handling paths. A follow-up list confirmed no residue. (07 registerwas not run — it provisions a new organization and emails a real human. The phone leg of01was likewise left off, since it provisions a billable number and dials a real destination.)02-vault-totplive-confirmed the snapshot semantics the skill documents (0 login(s)before re-unlock, 1 after) and produced correctly rotating codes — the same value twice inside one 30-second window, then a new one.04-inbox-monitorfound the unread message, printed the body, marked it read, and came back clean on the next check; its output also incidentally confirmed the Free-plan stored-body footer the skill warns about.A separate read-only pass exercised the rest of the documented surface live — whoami discriminant, identity facade accessors, mailbox storage fields (
storage_limit_bytescame back1073741824, exactly 1 GiB, confirming the binary-units guidance),iter_emails, the serde-flattenedThreadDetail.thread.folder,list_folders, identity-keyed contact rules, the iMessage triage number, vault info/secrets/credentials, contacts, notes, both A2A directories plus tasks/contexts withnext_cursor, signing-key status, webhook subscriptions, phone numbers, tunnels, SMS opt-ins, and domains. Every field name and shape matched what the skill documents.One finding worth surfacing. Re-running
01fails with409 agent_handle_unavailable: handles live in a namespace shared with tunnels and mail and stay reserved after deletion. The SDK ships a typed view for exactly this —HandleUnavailableError::from_error(&e)→blocking_namespace— which the first draft of the skill did not mention, despite this being the most commoncreate_identityfailure. Now documented as a "Handle collisions (409)" subsection under Error Handling, with a note in the example README. Type-checking alone would never have caught it.CI is unaffected:
.github/workflows/tests.ymlonly buildssdk/rust, and the example is a standalone crate outside any workspace.