Skip to content

R.4: remove config_snapshot and api_key_source; recordings keep identity only (A6 item 1, D3; stacked on #21) - #22

Open
cunninghamcard-bit wants to merge 21 commits into
exp/restartfrom
exp/u1R.4-remove-config-snapshot
Open

cunninghamcard-bit wants to merge 21 commits into
exp/restartfrom
exp/u1R.4-remove-config-snapshot

Conversation

@cunninghamcard-bit

@cunninghamcard-bit cunninghamcard-bit commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Phase 1R unit R.4 (A6 item 1, D3). Stacked on #21 (R.3), #20 and #19, so this diff includes them until they merge. The R.4-only changes are commits 2c57b5f and f5e72b0.

Removed (line numbers at 8455db9; re-located on this stack)

  • aimux-core/src/decision_model.rs:556: trait default DecisionModel::config_snapshot. At :668, decide() now records ProviderRecord::from_model(model.provider(), model.model_id()), which is provider name + model name only.
  • aimux-core/src/recording.rs:221,240: the ProviderRecord.api_key_source field and its initializer.
  • Overrides: jev.rs (config_snapshot with the :102 api_key_source logic), openai/decision.rs, runtime_decision.rs, and decision_support.rs:61 (DecisionEndpoint::snapshot plus the now-unused headers()).
  • provider_handle.rs: the api_key_source local and field, and HandleDecisionModel. ProviderHandle is now a plain forwarder over the resolved provider. The placeholder key itself stays for R.5.
  • bindings/node/src/types/ProviderRecord.ts: apiKeySource? was removed by regenerating ts-rs (gen_ts_types.py). --check passes. The generator also strips trailing whitespace in the regenerated file.

git grep -n "config_snapshot\|api_key_source\|apiKeySource" -- ':!*.md' finds only scripts/check_provider_boundaries.sh (rules, R.5) and the three new apiKeySource-absent assertions below.

Test changes (assertions changed, no test deleted; cargo count unchanged)

  • decision_recording_test.rs:91 (changed): ("apiKeySource","explicit") is now provider.get("apiKeySource").is_none(). The baseUrl check next to it also changed, from "ends with /v1/systemone" to "absent", because the record no longer carries it.
  • decision_handle_test.rs:146 (changed): apiKeySource == "explicit" is now absent. The providerOptions.headers redaction checks in the same test became "baseUrl / providerOptions absent". The raw-secret-not-in-file checks are kept.
  • decision_handle_test.rs:83 (config_snapshot_matches_model_identity, rewritten, now recorded_identity_matches_model_identity): it builds the record decide now writes (ProviderRecord::from_model). It asserts provider / providerId / modelId match the model, and that apiKeySource and baseUrl are absent.
  • decision_handle_test.rs:184 (keyless placeholder test, removed assertion): the config_snapshot().api_key_source == "none" check is gone. The wire assertion (Bearer not-needed) stays until R.5.
  • decision_handle_test.rs recorded_explicit_auth_headers_are_redacted (changed, not on the original list): it asserted providerOptions.headers = [REDACTED]. It now asserts the provider record has no providerOptions. The raw-secret-not-in-file checks are kept.
  • runtime_decision_test.rs:608 (rewritten): the record used for rebuild_decision_provider is now ProviderRecord::from_model(...), the same identity decide records, and the api_key_source == "none" assert is dropped. Rebuilding through the registry still passes for all 6 local runtimes.
  • runtime_decision_test.rs:616 (removed assertion): api_key_source == "explicit". The following :622 check, "explicit key is not in the recording, so rebuilding without a registration fails", now uses from_model.

Update (f5e72b0): base_url, profile and provider_options removed too

  • Checks before deleting: no committed recording carries these fields. The only committed ProviderRecords are in aimux-providers/tests/fixtures/jev_systemone_live.jsonl, and they hold identity only. The fixtures/aisdk providerOptions and the cassettes are not ProviderRecords. ProviderRecord is not deny_unknown_fields.
  • Removed the three fields, ProviderRecord::redacted and the feat: support official decision providers, candidate scoring, and native media arcships/aimux#226-only redact_provider_options helper. ProviderRecord.ts is now {providerId, provider, modelId}, and gen_ts_types.py --check passes.
  • New recording::tests::provider_record_ignores_removed_snapshot_fields: JSON with baseUrl / apiKeySource / profile / providerOptions still deserializes into a ProviderRecord and re-serializes as identity only, so old recordings still load and replay.
  • Grep (excluding rfc/, docs and *.md) leaves only the absence-asserting tests and that test's input JSON. The "profile" keys in provider_registry.json are registry rows, unrelated.
  • Other bindings had no api_key_source references; the decision_capabilities hits there are the capabilities API and are untouched.

Results vs R.0 (local linux x64; new and renamed test names confirmed in the log)

R.0 (8455db9) R.3 (ff8fae7) R.4 (f5e72b0)
cargo test --workspace 2871 / 0 / 7 2882 / 0 / 7 2883 / 0 / 7
fmt --check / clippy -D warnings — ok / ok ok / ok
check_provider_boundaries.sh 0 0 0
gen_ts_types.py --check — up to date up to date
Python pytest 93 pass, 5 fail 95 pass, same 5 95 pass, same 5 (same KeyErrors)
Node ava (node 22) 87 pass, 1 fail 93 pass, same 1 93 pass, same 1

Go/Java/Kotlin/Swift/Flutter were not run locally.

Review follow-up

Drop ProviderOptions.body_overrides and both arcships#226 rejections in
provider_handle.rs; ProviderOptions now denies unknown fields. Node
ProviderConfig loses bodyOverrides and reports any unknown key as
InvalidArgumentError 'unknown field `x`'. Python docs updated; Python
already rejects unknown keys via serde. Tests in Rust/Python/Node.
Adds docs/amendments/A6-remove-226-per-rfc.md verbatim.
…keys)

Replace the hand-written key allowlist: the JS object is read as a raw
JSON map (undefined values skipped), maxRetries is rejected first with
the call-level message, then the map deserializes into ProviderConfig
with #[serde(deny_unknown_fields, rename_all = "camelCase")]. The
max_retries struct field is gone (index.d.ts loses maxRetries?). Tests:
maxRetries gets the call-level message, undefined keys are absent.
…ies only (A6 item 2)

Drop RetryConfig and prepare_retries_with_config from aimux-core,
DecisionModel::retry_config, the retry fields/overrides in jev,
openai/decision (with_retry_config), runtime_decision and
decision_support (snapshot no longer records max_retries), and the
handle-level ProviderOptions.max_retries default in provider_handle.
decide() uses retry::prepare_retries(options.max_retries, ...).
New mock test: max_retries = 0/1/3/None makes exactly 1/2/4/3 requests.
…n jev_test

Retry counting moves into aimux-core decision_model::retry_tests, using a
crate-internal #[cfg(test)] PreparedRetries::with_initial_delay(ZERO);
asserts attempts = max_retries + 1 and RetryErrorReason::MaxRetriesExceeded
with the last 503/429/529 error. jev_test keeps status classification at
max_retries = 0 plus one max_retries = 1 run on 503 asserting 2 requests
and retry exhaustion. No public API, feature or binding change.
…st time (A6 item 4, D23)

provider_handle no longer reads the generic format!("{}_API_KEY") for local
runtimes (arcships#226). openai with api_key None keeps the package default
OPENAI_API_KEY, now read at request time (arcships#200:538); Some("") is used as
given (RFC-0040:67); a changed base URL without a key sends no credential and
reads no env. Tests inject env as a plain-data map (crate-internal
EnvSource::Vars), never std::env::set_var: generic key not read, env set after
construction is used, Some("") no fallback, replay/auth=none/changed base URL
read nothing. api_key_source field stays for R.4; placeholder key for R.5.
…eep identity only (A6 item 1, D3)

decide() records ProviderRecord::from_model(provider, model_id). Removed
DecisionModel::config_snapshot and its jev/openai/runtime/handle overrides,
DecisionEndpoint::snapshot, ProviderHandle's api_key_source and
HandleDecisionModel, and ProviderRecord.api_key_source (ts-rs regenerated).
Recording tests now assert no apiKeySource / baseUrl / providerOptions in the
provider record; config_snapshot() callers rewritten to the recorded identity.
…only)

Nothing writes them since config_snapshot is gone. Also removes the arcships#226-only
ProviderRecord::redacted and redact_provider_options. ProviderRecord is not
deny_unknown_fields; a new test shows an old record carrying baseUrl /
apiKeySource / profile / providerOptions still deserializes (and
re-serializes as identity only). ts-rs regenerated.
… the struct

Drop #[napi(skip)] invalid: Option<String>; from_napi_value now returns the
InvalidArgumentError directly. The constructors' 3rd arg becomes ConfigArg
(dispatch on JS type) because napi's Either swallowed the member error.
… from env_tests

Prod compiles no Handles / fetch / env plumbing. The env is injected, so the
tests need no serialization; the replay test keeps only its own recording
(unique state) since the recorder is process-global.
Constructors accept the config argument as serde_json::Value (d.ts unchanged),
so conversion never throws synchronously; maxRetries, unknown keys and wrong
types are refused inside the async body as InvalidArgumentError. ConfigArg and
the custom ProviderConfig FromNapiValue are gone.
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