Skip to content

feat(discovery): harden consumer selection boundary - #819

Merged
jeremi merged 6 commits into
mainfrom
feat/discovery-consumer-boundary
Aug 25, 2026
Merged

feat(discovery): harden consumer selection boundary#819
jeremi merged 6 commits into
mainfrom
feat/discovery-consumer-boundary

Conversation

@jeremi

@jeremi jeremi commented Aug 25, 2026

Copy link
Copy Markdown
Member

Pull Request

Summary

  • Add explicitly structural selection-validation APIs across Rust, Node.js, and Python while retaining the original names as compatibility aliases.
  • Add ephemeral adopter-owned accepted-service handoffs so credentials and native Evidence or Relay I/O are created only after local policy accepts an exact selection.
  • Add unchanged-selection renewal that permits fresh fetch provenance and catalog revision only when every trust-relevant service semantic remains unchanged.
  • Bind the boundary to cross-SDK negative tests, Discovery contracts and traceability, the adopter tutorial, CI routing, public documentation, and installed-package smokes.

Closes #816
Closes #817
Closes #818

Security boundary

  • Threat: Discovery metadata or structural validation is mistaken for trusted or current native service configuration, including after a persisted selection changes or is withdrawn.
  • Enforcement point: structural validation, synchronous adopter-owned acceptance, the ephemeral accepted-service type, and unchanged-semantic renewal before credential construction or native I/O.
  • Negative case: endpoint, identity, role, jurisdiction, profile, capability, mapping, resolution, or Relay tuple drift is refused before token construction or native traffic.
  • Trust boundary: Discovery remains an inert public index. It gains no trust store, credentials, authorization decision, proxy, or native invocation capability.

This updates Tier-C trust/currentness documentation. Maintainer source-pack sign-off remains required before merge; the page stays draft and its last-reviewed date is unchanged.

Checks

  • cargo fmt --all -- --check
  • cargo check --locked -p registry-discovery-client -p registry-discovery-client-node -p registry-discovery-client-py --all-targets
  • cargo test --locked -p registry-discovery-client (32 unit tests and the native Evidence/Relay journey)
  • cargo clippy --locked -p registry-discovery-client -p registry-discovery-client-node -p registry-discovery-client-py --all-targets -- -D warnings
  • Node.js native build, 7 binding tests, and strict declaration/type check
  • Python binding suite: 15 tests
  • Built Node package smoke and freshly built/installed Python wheel smoke
  • products/discovery/scripts/check-contracts.sh
  • products/discovery/scripts/test-http.sh
  • products/discovery/scripts/test-adopter-tutorial.sh
  • Docs generation, 450 script tests, content/Markdown/Vale checks, tutorial dry runs, build, and full npm run check
  • CI classifier suite: 62 tests
  • git diff --check

Notes

  • No dependency or Cargo.lock change.
  • Offline persisted selections remain inert and require caller-owned age/currentness policy plus fresh local acceptance.
  • Online renewal requires a new lookup, explicit reselection, unchanged-semantics comparison, and local acceptance. It never chooses a replacement service or Evidence alternative.
  • Independent final review found no P0-P2 blocker.

DCO

  • Every commit includes a Signed-off-by trailer.
  • I reviewed the submitted changes and am responsible for the contribution.

@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: c4bcd3c1de

ℹ️ 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 thread .github/scripts/test_ci_changes.py
Comment thread release/scripts/smoke-discovery-client-package.py
Comment thread crates/registry-discovery-client-node/client.d.ts
jeremi added 6 commits August 25, 2026 21:07
Add structural validation, adopter-owned acceptance, and unchanged-selection renewal across the Rust, Node.js, and Python clients. Keep the accepted handoff nominal in TypeScript and runtime-subscriptable in Python.

Refs #816

Refs #817

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Require structural validation, explicit local acceptance, and unchanged-selection renewal in the Discovery completion and security traceability contracts. Execute the cross-SDK adopter journey as contract evidence.

Refs #816

Refs #817

Refs #818

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Document structural validation, adopter-owned acceptance, persisted inert selections, and online renewal across the Rust, Node.js, and Python client journeys.

Refs #816

Refs #818

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Exercise validation, explicit acceptance, and unchanged-selection renewal through the installed Node.js package and Python wheel.

Refs #817

Refs #818

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Select the Discovery client packages, adopter contract journey, documentation, and installed-package smokes together, with the pinned Node.js toolchain required by the contract job.

Refs #818

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Require the installed Python wheel to support runtime subscription of the generic accepted handoff.

Refs #817

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi force-pushed the feat/discovery-consumer-boundary branch from c4bcd3c to 570e2f3 Compare August 25, 2026 14:14
@jeremi
jeremi merged commit 2c7dbb7 into main Aug 25, 2026
45 checks passed
@jeremi
jeremi deleted the feat/discovery-consumer-boundary branch August 25, 2026 14:23

@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: 570e2f3797

ℹ️ 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".

function acceptSelection(selection, accepts) {
const checked = validateSelectionStructure(selection);
if (typeof accepts !== 'function') throw inputError('query');
const accepted = accepts(responseValue(checked));

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 Preserve normal objects at the acceptance boundary

When an adopter callback uses ordinary Object APIs—for example, candidate.hasOwnProperty(...)—or passes the value to code that checks Object.prototype, this call supplies a null-prototype clone because responseValue ultimately creates every object with Object.create(null). That contradicts the declared T and the normal selection objects returned by the SDK, causing the trust callback to throw before returning its policy decision; the same clone path also makes accepted.selection nonstandard. Return a normal plain clone for outward-facing values while keeping null-prototype objects internal to native-input sanitization.

Useful? React with 👍 / 👎.

ServiceSelection = CommonServiceSelection | EvidenceServiceSelection | RelayServiceSelection
_SelectionT = TypeVar("_SelectionT", bound=ServiceSelection)

class AcceptedServiceSelection(Generic[_SelectionT]):

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 Make the Python accepted-selection type non-constructible

The stub gives this class an implicit public zero-argument constructor and permits subclassing, even though the PyO3 runtime rejects both with TypeError and intends instances to come only from accept_selection. As a result, mypy-clean code such as AcceptedServiceSelection() or a test-double subclass fails immediately at runtime, while also weakening the advertised accepted-handoff boundary in type-checked code. Mark the class final and encode a non-public or otherwise uncallable constructor in the stub.

Useful? React with 👍 / 👎.

Comment on lines +107 to +109
const chosen = services.items.find(
(item) => item.serviceId === expectedEvidence.serviceId,
);

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 Reject ambiguous service-ID matches in the Node example

When two origins advertise the same serviceId—which the index deliberately preserves as distinct records—this filtered result can contain multiple items, but .find silently chooses whichever record appears first despite the comment promising an explicit choice. Because the sample acceptance policy also omits publisherId, originId, and originUrl, otherwise identical advertisements from different origins can both pass and the adopter proceeds to credential construction and native I/O without reviewing which record was selected. Require exactly one independently pinned match or make the record/origin choice explicit.

AGENTS.md reference: AGENTS.md:L75-L79

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant