Skip to content

test(daemon): prove the protected preview stays redacted - #1815

Merged
ScriptedAlchemy merged 1 commit into
masterfrom
pr1615/protected-preview-journey
Sep 19, 2026
Merged

ScriptedAlchemy merged 1 commit into
masterfrom
pr1615/protected-preview-journey

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Supersedes #1615 (the journey-test half of it). #1615 is left open for the owner to close.

What this is

tracedecay_configuration_protected_preview is the dry run a host calls before it commits a protected configuration change, and nothing covered its host-facing contract end to end. This adds one journey test that drives the tool through MCP tools/call on the production project composition and asserts what a caller actually receives:

  • the plan is bound to the revision the caller supplied (base_revision_id);
  • redacted_changes carries only the setting key, the operation, and the before/after digests — never the rule identity or the denied capability;
  • the operation digest is echoed as the preview digest, and preview_id is the plan_id the host later applies;
  • the plan stays valid for five minutes (expires_at - created_at);
  • two structurally different changes (UpsertAccessRule, UnbindSource) produce two different digests, so the digest is evidence rather than a constant;
  • a stale expected_revision is a typed configuration.conflict with retry: after_revalidate, not a committed setting;
  • an empty capability set is a typed configuration.invalid_request with retry: never;
  • after all four previews the configuration revision is unchanged.

What this is not

#1615 also carried an unexplained production change in code_index_scheduler/serving.rs, test-authority grafts in two scheduler tests, two assertion changes weaker than master's, and byte-identical fixture churn. None of that is here.

The serving.rs change was evaluated separately and rejected: it re-collapses a text-build admission refusal into RetrievalPortError::BudgetExceeded, which is exactly what commit 0ae9fad ("shrink text builds to available memory headroom", #1782) deliberately undid — "Reservation failures now retain their typed detail instead of collapsing into BudgetExceeded." Its stated rationale (that AuthorityUnavailable makes the successor never retry) does not hold on master: advance_text_serving_with_control marks text serving failed only for CapabilityManifestRejected | GenerationMismatch | IncompatibleProjection | Contract, so both variants are retried identically, while at the executor BudgetExceeded maps to a terminal TimedOut/Internal and AuthorityUnavailable maps to the reason the code documents as the retryable one. The reclassification would therefore make transient reservation pressure look less retryable, not more, and it drops the byte-level diagnostic #1782 added.

Verification

Run in a clean worktree on origin/master with its own CARGO_TARGET_DIR:

Gate Result
cargo test -p tracedecay --features tracedecay/test-helpers --lib -- configuration_protected_preview 1 passed (run twice)
cargo clippy --workspace --all-targets --locked -- -D warnings clean
cargo fmt --all -- --check clean
node scripts/lint-commit-range.mjs origin/master HEAD clean

🤖 Generated with Claude Code

`tracedecay_configuration_protected_preview` is the dry run a host calls
before it commits a protected setting, and nothing covered its host-facing
contract end to end. Drive the tool through MCP `tools/call` on the
production composition and assert what a caller actually receives: a plan
bound to the supplied revision, the redacted per-setting digests, the
operation digest echoed as the preview digest, the five-minute validity
window, and a preview id the host can apply. The rule identity and the
denied capability must never appear in the rendered answer, a stale
revision must be a typed `configuration.conflict`, an empty capability set
a typed `configuration.invalid_request`, and no preview may advance the
configuration revision.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a0d26c7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-19T06:33:46.227271Z a0d26c7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0d26c7050

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +204 to +205
let unbind = ProtectedChange::UnbindSource {
binding_id: SourceBindingId::new(ABSENT_BINDING_ID).expect("binding identity"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preview an existing source binding instead

This constructs an UnbindSource for an explicitly absent binding and then requires the preview to succeed. Such a plan can never be applied: ConfigurationSnapshotV1::apply_protected_change returns ProtectedChangeSnapshotError::Stale when the binding is absent, while introducing the binding first would change the pinned base revision. The test therefore blesses a non-actionable preview rather than exercising a real second protected-change journey; seed a binding before previewing its removal, or assert that this request is refused.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

"access_rule_upsert",
before_digest.as_str(),
&access_digest,
&[ACCESS_RULE_ID, DENIED_CAPABILITY],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Assert that the target project identity is redacted

The submitted access rule embeds project_id in its AuthorityRef, but the leak check only searches for the rule ID and denied capability. A response that accidentally exposed the target project identity anywhere outside the exactly compared redacted_changes object would still pass, despite the ProtectedChangePlan contract explicitly forbidding target identities. Include project_id.as_str() among the hidden values so this journey actually covers the full sensitive input it sends.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

@ScriptedAlchemy
ScriptedAlchemy merged commit 4201dc6 into master Sep 19, 2026
1 check passed
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