You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Decide whether stella takes one coordinated, versioned protocol break — and if so, which of the 33 rows below ride it. Every item here was deferred by the same sentence in a different crate: "that is a public API change" or "that changes the JSON round-trip shape invariant 4 protects." They cannot be triaged one at a time, because each one alone pays the full compile-and-fix cost across stella-model's six adapters, stella-store's schema, the replay fixtures and the TUI. The opposite answer is equally valid and much cheaper: declare these types frozen, close the rows that are only taste, and keep the documented-trap workarounds that already landed.
Spot-checked against origin/main @ 092b2dda: items 2, 5, 11, 12, 21, 25, 31, 32 and 33 confirmed still open. Line numbers below are updated where they have drifted since the audit.
Items
Add an id/identity field to stella_protocol::ToolResult so tool results carry a stable identity through compaction, instead of the driver-local side map that shipped. stella-protocol (the ToolResult type), with constructors in stella-model (anthropic/openai/gemini/bedrock/zai), stella-tui, stella-pipeline/src/replay.rs, stella-core. Deferred: a new field changes the JSON round-trip shape AGENTS.md invariant 4 protects and ripples into every provider adapter and replay fixture; the issue offered a fully in-stella-core alternative (the side map), which is what landed. (audit stella-core: compaction destroys the evidence loop detection requires, disabling the documented stuck-turn defense #554, deferred by fix(stella-core): keep loop-detection evidence across compaction #588)
Put Arc<[CompletionMessage]> on CompletionRequest and cache the Arc<[ToolSchema]> — stella-core/src/driver.rs:999. Deferred: changes a public stella-protocol type across crate boundaries, and the schema cache needs a correct invalidation contract for late_tools; not verifiable as behavior-preserving without benchmarks. Same change as the row two below it — reported independently by two audits. (audit Batched audit cleanup: area:core (1/2) — 31 items #559, deferred by chore(audit): batched area:core cleanup (7 of 31 items) #590)
Add a MediaError::Journal(String) variant — stella-media/src/operation_journal.rs:517, stella-media/src/error.rs. journal_error funnels every journal failure (mutex poisoning, SQL errors, capacity exhaustion, insecure file modes, path traversal) into MediaError::Artifact, whose doc says "the artifact store could not read/write under .stella/artifacts/" — pointing hosts at the wrong subsystem, since the journal is explicitly not in the workspace. Remediation keeps is_retryable() == false and updates the SidecarRace neighbours' docs. Deferred: public-enum change other crates match on — though the cross-crate uses in stella-tools/stella-cli are variant constructions, not exhaustive matches, so it would likely still compile; the doc-update half is unbounded. Verified: no Journal variant on main. (audit Batched audit cleanup: area:media — 10 items #565, deferred by chore(stella-media): fix the live-smoke gate, preview labels, branding #591)
Make http_client() fallible instead of .unwrap_or_default() — stella-mcp/src/oauth.rs:962, which today silently discards the 30s HTTP_TIMEOUT bound if the builder fails. Deferred:OAuthManager::new is public and infallible, and the issue's "at minimum" fallback is not implementable as written — if Client::builder().timeout(HTTP_TIMEOUT).build() fails, rebuilding it identically fails identically, and Client::default() cannot carry a timeout. There is no half-measure that preserves the 30s bound. (audit Batched audit cleanup: area:mcp — 14 items #564, deferred by fix(stella-mcp): bound the stdio reader's per-line read #592)
Rename RunnableSession::warmth_secs → expires_in_secs (and optionally warmest_first → soonest_to_expire_first) — stella-fleet/src/cache_schedule.rs:71 (field now at :29). The field measures seconds until expiry, so warmest_first sorts ascending — the opposite of what an unaided reader assumes. Deferred: a cross-crate API rename on a public type re-exported at the crate root and crossing the Fleet::with_cache_warmth/CacheWarmthLookup boundary. (audit Batched audit cleanup: area:fleet — 14 items #562, deferred by chore(stella-fleet): apply the area:fleet audit batch #593)
Report PipelineOutcome::candidates_run as what actually ran — stella-pipeline/src/pipeline.rs:713. It is set to the configured n on both terminal paths even when every candidate failed isolation setup and the run degraded to a single bare execution, so a --candidates 4 run that generated zero isolated candidates still reports 4 — contradicting its own doc ("How many candidates were generated (1 for single-shot)") and overstating what the user paid for. Deferred: changes a public outcome field's value, several tests assert on the outcome shape, and PipelineOutcome is re-exported from lib.rs. (audit Batched audit cleanup: area:pipeline — 15 items #567, deferred by test(pipeline): name the work-item test modules for what they pin #594)
Delete witness_watchlist and tampered_paths and their lib.rs re-exports — stella-pipeline/src/witness.rs:631 (now :634 and :656), stella-pipeline/src/lib.rs:87-88 (now :106). The pipeline enforces tamper exclusion via validate_witness_artifact + witness_identity_matches instead, so these read as the live mechanism when they are dead public API; their test cases would move onto the live path. Deferred: removing public items is a public API change, explicitly out of the audit's scope. Verified on main: still only test callers. (audit Batched audit cleanup: area:pipeline — 15 items #567, deferred by test(pipeline): name the work-item test modules for what they pin #594)
Add #[non_exhaustive] to EngineConfig and its sibling config structs (LoopDetectionConfig, GoalConfig, SelectionConfig, MineConfig) — stella-core/src/driver.rs:101. Deferred: a deliberate public-API/semver policy decision. It forbids struct-literal construction from other crates, so it is a knowing cross-crate break for stella-cli, stella-pipeline and the bench harness. Verified: no attribute on main. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)
Wrap the message snapshot and tools_schema in Arc to avoid cloning the snapshot on the first attempt — stella-core/src/driver.rs:999, stella-protocol/src/lib.rs. The perf claim is confirmed: the snapshot really is cloned on the first attempt, not only on retry. Deferred: it reshapes CompletionRequest::messages (a stella-protocol type consumed by every provider adapter in stella-model) and changes RetryAttemptFn's captured types — a protocol change, not a cleanup. Duplicate of the Arc<[CompletionMessage]> row above; land them as one. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)
The two speculation select! follow-ons — stella-core/src/driver.rs (~:1046): the alternative restructure _ = &mut pump => (&mut complete).await, and a new ProviderError::Terminal variant. Only biased; landed, which removes the poll-order dependence but does not delete the unreachable!("the gate keeps the speculation channel open"). Deferred: the restructure changes the failure mode on a currently-unreachable path, and the new variant is a fresh error-semantics decision. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)
Add a turn_instance field to StepUsage so usage records can be attributed to a turn — stella-protocol/src/event.rs:414, populated from stella-core/src/driver.rs. Deferred: it touches the emitter and every exhaustive StepUsage { .. } destructuring. Note the trap: adding the field without populating it ships a permanently-0 money record, which is worse than the current state. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)
Change ManifestEntry.message_index to Option<usize> — stella-protocol/src/event.rs:851, stella-core/src/receipts.rs, plus pipeline replay. Deferred, and worse than the issue states:stella-store/src/migrations.rs persists message_index INTEGER NOT NULL DEFAULT 0 (confirmed on main at :475, :534 and :729), so this also needs a SQLite migration and a reconstruction-refusal policy that is the owner's call. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)
Migrate AgentEvent::Text { delta } to a text field via serde alias — stella-protocol/src/event.rs:196, destructured by name across 10 files including stella-core/src/driver.rs and stella-cli/src/tui.rs. Deferred: a cross-crate change requiring a coordinated PR. The in-crate half already landed — event.rs:193-197 warns the field name is wire-frozen legacy — so nothing in stella-protocol is left to do; this row is purely the coordinated rename. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)
Wire ContextUsage::as_of_is_wellformed() into the metering path — stella-protocol/src/event.rs:948. It has no caller, so nothing actually enforces the check; verified on main, its only references are its own tests at :1530 and :1544. Deferred: wiring it in is a separate, cross-crate change. It ships as an available check, not an enforced one, and the doc comment says so rather than implying a guarantee. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)
Rename the vendor-named shared OpenAI-compatible adapter — stella-model/src/zai.rs:34: ZaiProvider, ZaiRequest, ZaiMessage, ZaiContent, ZAI_CAPS, to_zai_messages, classify_zai_stream_error → chat_completions/ChatCompletionsProvider, and replace the scattered self.id == "openrouter"/"zai"/"xai" string-equality identity gates (zai.rs:897-923) with a DialectQuirks struct resolved once per identity. Deferred: "a public-API rename touching stella-cli — squarely out of an audit's remit," and it would have collided with that PR's own zai.rs edits. Verified: ZaiProvider still at :34 on main. (audit Batched audit cleanup: area:model — 18 items #566, deferred by fix(stella-model): land the area:model audit batch (#566) #598)
Delete or demote four uncalled public items — storage::field_address (stella-graph/src/storage/mod.rs:313), RelationKind::from_tag (storage/mod.rs:58), StorageManifest::layer_for (stella-graph/src/manifest.rs:122), StorageManifest::namespace_meaning (manifest.rs:145). None has a caller anywhere in the workspace and each is exercised only by its own unit tests, yet all four read as supported API. Deferred: "removing or narrowing public items is a public API change, which the audit rules place out of bounds" — the dead-code claim is confirmed, but acting on it is the owner's call. Verified: all four still present at those exact lines on main. (audit Batched audit cleanup: area:graph — 15 items #563, deferred by perf(stella-graph): bound the audit's unbounded loops, scans, and walks #600)
Why these are together
One shared blocker, one shared cost. Each row was stopped by the same sentence in a different crate — that is a public API change, or that changes the JSON round-trip shape invariant 4 protects — and each one alone would pay the full compile-and-fix bill across stella-model's six adapters, stella-store's schema, the replay fixtures and the TUI. Triaging them one at a time means paying that bill up to 33 times, or (more likely) never paying it and letting each row rot separately. The useful unit of decision is the break itself: one versioned bump, one migration note, and an explicit list of which rows ride it — with everything left over closed as "frozen, by design."
Deferred during the #546 audit remediation. Put Closes #618 in the PR description and as a commit trailer when it lands.
What decision this asks for
Decide whether stella takes one coordinated, versioned protocol break — and if so, which of the 33 rows below ride it. Every item here was deferred by the same sentence in a different crate: "that is a public API change" or "that changes the JSON round-trip shape invariant 4 protects." They cannot be triaged one at a time, because each one alone pays the full compile-and-fix cost across stella-model's six adapters, stella-store's schema, the replay fixtures and the TUI. The opposite answer is equally valid and much cheaper: declare these types frozen, close the rows that are only taste, and keep the documented-trap workarounds that already landed.
Spot-checked against
origin/main@092b2dda: items 2, 5, 11, 12, 21, 25, 31, 32 and 33 confirmed still open. Line numbers below are updated where they have drifted since the audit.Items
stella_protocol::ToolResultso tool results carry a stable identity through compaction, instead of the driver-local side map that shipped.stella-protocol(theToolResulttype), with constructors instella-model(anthropic/openai/gemini/bedrock/zai),stella-tui,stella-pipeline/src/replay.rs,stella-core. Deferred: a new field changes the JSON round-trip shape AGENTS.md invariant 4 protects and ripples into every provider adapter and replay fixture; the issue offered a fully in-stella-corealternative (the side map), which is what landed. (audit stella-core: compaction destroys the evidence loop detection requires, disabling the documented stuck-turn defense #554, deferred by fix(stella-core): keep loop-detection evidence across compaction #588)as_str(&self)→as_str(self)onNodeKind,EpisodeOutcome,MemoryKind—stella-context/src/store.rs:195(audit cited :181). Half of the audit's item 13: only the#[must_use]attributes were applied. Deferred: it breaks the explicitNodeKind::as_str(&k)call form and belongs in a version bump. Verified still&selfon main. (audit Batched audit cleanup: area:context — 15 items #558, deferred by chore(stella-context): document the public surface and cache store_kinds #589)open_read_onlyreturnsResult<Option<Connection>>and surfacesmeta().warnings—stella-observatory/src/db.rs:682. Deferred: changes a public method's return shape and the/api/metapayload the embedded page consumes. (audit Batched audit cleanup: area:core (1/2) — 31 items #559, deferred by chore(audit): batched area:core cleanup (7 of 31 items) #590)Arc<[CompletionMessage]>onCompletionRequestand cache theArc<[ToolSchema]>—stella-core/src/driver.rs:999. Deferred: changes a publicstella-protocoltype across crate boundaries, and the schema cache needs a correct invalidation contract forlate_tools; not verifiable as behavior-preserving without benchmarks. Same change as the row two below it — reported independently by two audits. (audit Batched audit cleanup: area:core (1/2) — 31 items #559, deferred by chore(audit): batched area:core cleanup (7 of 31 items) #590)MediaError::Journal(String)variant —stella-media/src/operation_journal.rs:517,stella-media/src/error.rs.journal_errorfunnels every journal failure (mutex poisoning, SQL errors, capacity exhaustion, insecure file modes, path traversal) intoMediaError::Artifact, whose doc says "the artifact store could not read/write under.stella/artifacts/" — pointing hosts at the wrong subsystem, since the journal is explicitly not in the workspace. Remediation keepsis_retryable() == falseand updates theSidecarRaceneighbours' docs. Deferred: public-enum change other crates match on — though the cross-crate uses instella-tools/stella-cliare variant constructions, not exhaustive matches, so it would likely still compile; the doc-update half is unbounded. Verified: noJournalvariant on main. (audit Batched audit cleanup: area:media — 10 items #565, deferred by chore(stella-media): fix the live-smoke gate, preview labels, branding #591)McpError::Authvariant —stella-mcp/src/http.rs:152. Deferred: cross-crate public API change; stella-cli and the TUI would have to compose the remediation sentence, and the right error shape is an owner API decision. (audit Batched audit cleanup: area:mcp — 14 items #564, deferred by fix(stella-mcp): bound the stdio reader's per-line read #592)server_nameparameter fromlogin—stella-mcp/src/oauth.rs:520. Deferred: public signature change with callers in stella-cli, and the issue is itself undecided between two designs (drop the param vs. pass&TokenStore). (audit Batched audit cleanup: area:mcp — 14 items #564, deferred by fix(stella-mcp): bound the stdio reader's per-line read #592)http_client()fallible instead of.unwrap_or_default()—stella-mcp/src/oauth.rs:962, which today silently discards the 30sHTTP_TIMEOUTbound if the builder fails. Deferred:OAuthManager::newis public and infallible, and the issue's "at minimum" fallback is not implementable as written — ifClient::builder().timeout(HTTP_TIMEOUT).build()fails, rebuilding it identically fails identically, andClient::default()cannot carry a timeout. There is no half-measure that preserves the 30s bound. (audit Batched audit cleanup: area:mcp — 14 items #564, deferred by fix(stella-mcp): bound the stdio reader's per-line read #592)RunnableSession::warmth_secs→expires_in_secs(and optionallywarmest_first→soonest_to_expire_first) —stella-fleet/src/cache_schedule.rs:71(field now at :29). The field measures seconds until expiry, sowarmest_firstsorts ascending — the opposite of what an unaided reader assumes. Deferred: a cross-crate API rename on a public type re-exported at the crate root and crossing theFleet::with_cache_warmth/CacheWarmthLookupboundary. (audit Batched audit cleanup: area:fleet — 14 items #562, deferred by chore(stella-fleet): apply the area:fleet audit batch #593)PipelineOutcome::candidates_runas what actually ran —stella-pipeline/src/pipeline.rs:713. It is set to the configurednon both terminal paths even when every candidate failed isolation setup and the run degraded to a single bare execution, so a--candidates 4run that generated zero isolated candidates still reports 4 — contradicting its own doc ("How many candidates were generated (1 for single-shot)") and overstating what the user paid for. Deferred: changes a public outcome field's value, several tests assert on the outcome shape, andPipelineOutcomeis re-exported fromlib.rs. (audit Batched audit cleanup: area:pipeline — 15 items #567, deferred by test(pipeline): name the work-item test modules for what they pin #594)witness_watchlistandtampered_pathsand theirlib.rsre-exports —stella-pipeline/src/witness.rs:631(now :634 and :656),stella-pipeline/src/lib.rs:87-88(now :106). The pipeline enforces tamper exclusion viavalidate_witness_artifact+witness_identity_matchesinstead, so these read as the live mechanism when they are dead public API; their test cases would move onto the live path. Deferred: removing public items is a public API change, explicitly out of the audit's scope. Verified on main: still only test callers. (audit Batched audit cleanup: area:pipeline — 15 items #567, deferred by test(pipeline): name the work-item test modules for what they pin #594)#[non_exhaustive]toEngineConfigand its sibling config structs (LoopDetectionConfig,GoalConfig,SelectionConfig,MineConfig) —stella-core/src/driver.rs:101. Deferred: a deliberate public-API/semver policy decision. It forbids struct-literal construction from other crates, so it is a knowing cross-crate break for stella-cli, stella-pipeline and the bench harness. Verified: no attribute on main. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)tools_schemainArcto avoid cloning the snapshot on the first attempt —stella-core/src/driver.rs:999,stella-protocol/src/lib.rs. The perf claim is confirmed: the snapshot really is cloned on the first attempt, not only on retry. Deferred: it reshapesCompletionRequest::messages(a stella-protocol type consumed by every provider adapter in stella-model) and changesRetryAttemptFn's captured types — a protocol change, not a cleanup. Duplicate of theArc<[CompletionMessage]>row above; land them as one. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)pub usere-export block —stella-core/src/lib.rs:37. Deferred: a public-API curation call rather than a defect, and it risks root-level name collisions (a barecompact) — the crate root already documents a deliberate exception for exactly this ambiguity (thebus::HookEventvshooks::HookEventcomment at lib.rs:39-41). (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)Arc<[ToolSchema]>and reshapeToolExecutor::schemasto avoid the confirmed clone —stella-serve/src/remote.rs:129, port defined instella-core/src/lib.rs. Deferred: a cross-crate trait reshape; the clone is confirmed, but theToolExecutorport is implemented across several crates. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595){}serialization fallback with a typed error frame —stella-serve/src/server.rs:271. Deferred: choosing the wire fallback is a protocol-contract decision requiring the host side; inventing it in an audit risks a worse failure than the one it replaces. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)select!follow-ons —stella-core/src/driver.rs(~:1046): the alternative restructure_ = &mut pump => (&mut complete).await, and a newProviderError::Terminalvariant. Onlybiased;landed, which removes the poll-order dependence but does not delete theunreachable!("the gate keeps the speculation channel open"). Deferred: the restructure changes the failure mode on a currently-unreachable path, and the new variant is a fresh error-semantics decision. (audit Batched audit cleanup: area:core (2/2) — 31 items #560, deferred by chore(audit): apply the area:core (2/2) batch — 13 of 31 findings #595)ProviderError's#[source]error so the provider source chain is preserved —stella-protocol/src/error.rs:10. Deferred: both of the issue's options are public API changes with call sites across stella-model's six adapters, and one pulls inanyhow, which AGENTS.md gates behind a justification andcargo deny.cargo test -p stella-protocolcannot prove it safe. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)turn_instancefield toStepUsageso usage records can be attributed to a turn —stella-protocol/src/event.rs:414, populated fromstella-core/src/driver.rs. Deferred: it touches the emitter and every exhaustiveStepUsage { .. }destructuring. Note the trap: adding the field without populating it ships a permanently-0money record, which is worse than the current state. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)stella-protocol/src/event.rs:394. Deferred: a breaking wire change affecting every emitter and the TUI HUD; it needs a versioned migration, not an audit edit, and it is directly against the additive-only contract stated in this module's own doc. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)ManifestEntry.message_indextoOption<usize>—stella-protocol/src/event.rs:851,stella-core/src/receipts.rs, plus pipeline replay. Deferred, and worse than the issue states:stella-store/src/migrations.rspersistsmessage_index INTEGER NOT NULL DEFAULT 0(confirmed on main at :475, :534 and :729), so this also needs a SQLite migration and a reconstruction-refusal policy that is the owner's call. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)context_eventmodule tolifecycle_event—stella-protocol/src/context_event.rs:1. Deferred: a module rename changes thestella_protocol::context_event::*paths used elsewhere in the workspace. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)into_decoded(self)variant to avoid a clone on decode —stella-protocol/src/context_event.rs:77. Deferred: the win only lands once the replay reader switches to it, and that reader is in another crate; an uncalled method is half the change and zero measured benefit. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)AgentEvent::Text { delta }to atextfield via serde alias —stella-protocol/src/event.rs:196, destructured by name across 10 files includingstella-core/src/driver.rsandstella-cli/src/tui.rs. Deferred: a cross-crate change requiring a coordinated PR. The in-crate half already landed —event.rs:193-197warns the field name is wire-frozen legacy — so nothing in stella-protocol is left to do; this row is purely the coordinated rename. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)Rfc3339(String)newtype for timestamp fields —stella-protocol/src/event.rs:895; the first of the issue's two options foras_ofvalidation. Deferred: the newtype changes the public field type. The second option shipped instead —ContextUsage::as_of_is_wellformed(), confirmed on main atevent.rs:948. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)u64—stella-protocol/src/event.rs:791,stella-core/src/receipts.rs,stella-context. Deferred: widening breaks struct literals and integer inference outside this crate, so it needs a compile-and-fix pass the auditor could not run. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)AgentEventvariants to shrink the enum —stella-protocol/src/event.rs:186. Deferred: "measure first" — no benchmark exists here to justify a breaking wire and API change, and the tag-buffering cost cannot be removed without abandoning the self-describing wire format, which is not worth it. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)MediaJobStateserde tag —stella-protocol/src/event.rs:968. Deferred: both options change the wire format; given the additive-only contract, this is only worth doing behind the same versioned migration as every other breaking change here. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)context_eventtypes from the crate root or document why they are namespaced —stella-protocol/src/lib.rs:31. Deferred: an explicit owner-judgment disclaim — the auditor "could not determine which of the two the author intended," and picking either branch guesses at intent. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)LifecycleEventEnvelope.observed_at—stella-protocol/src/context_event.rs:61. It is stringly typed with no check: the same problem the shippedContextUsage::as_of_is_wellformed()predicate solves. Deferred: a matching predicate there is a second public API addition the issue does not sanction. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)ContextUsage::as_of_is_wellformed()into the metering path —stella-protocol/src/event.rs:948. It has no caller, so nothing actually enforces the check; verified on main, its only references are its own tests at :1530 and :1544. Deferred: wiring it in is a separate, cross-crate change. It ships as an available check, not an enforced one, and the doc comment says so rather than implying a guarantee. (audit Batched audit cleanup: area:protocol — 16 items #568, deferred by chore(stella-protocol): document the core vocabulary, make as_of checkable #596)stella-model/src/zai.rs:34:ZaiProvider,ZaiRequest,ZaiMessage,ZaiContent,ZAI_CAPS,to_zai_messages,classify_zai_stream_error→chat_completions/ChatCompletionsProvider, and replace the scatteredself.id == "openrouter"/"zai"/"xai"string-equality identity gates (zai.rs:897-923) with aDialectQuirksstruct resolved once per identity. Deferred: "a public-API rename touching stella-cli — squarely out of an audit's remit," and it would have collided with that PR's ownzai.rsedits. Verified:ZaiProviderstill at :34 on main. (audit Batched audit cleanup: area:model — 18 items #566, deferred by fix(stella-model): land the area:model audit batch (#566) #598)storage::field_address(stella-graph/src/storage/mod.rs:313),RelationKind::from_tag(storage/mod.rs:58),StorageManifest::layer_for(stella-graph/src/manifest.rs:122),StorageManifest::namespace_meaning(manifest.rs:145). None has a caller anywhere in the workspace and each is exercised only by its own unit tests, yet all four read as supported API. Deferred: "removing or narrowing public items is a public API change, which the audit rules place out of bounds" — the dead-code claim is confirmed, but acting on it is the owner's call. Verified: all four still present at those exact lines on main. (audit Batched audit cleanup: area:graph — 15 items #563, deferred by perf(stella-graph): bound the audit's unbounded loops, scans, and walks #600)Why these are together
One shared blocker, one shared cost. Each row was stopped by the same sentence in a different crate — that is a public API change, or that changes the JSON round-trip shape invariant 4 protects — and each one alone would pay the full compile-and-fix bill across stella-model's six adapters, stella-store's schema, the replay fixtures and the TUI. Triaging them one at a time means paying that bill up to 33 times, or (more likely) never paying it and letting each row rot separately. The useful unit of decision is the break itself: one versioned bump, one migration note, and an explicit list of which rows ride it — with everything left over closed as "frozen, by design."
Deferred during the #546 audit remediation. Put
Closes #618in the PR description and as a commit trailer when it lands.