feat(discovery): harden consumer selection boundary - #819
Conversation
There was a problem hiding this comment.
💡 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".
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>
c4bcd3c to
570e2f3
Compare
There was a problem hiding this comment.
💡 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)); |
There was a problem hiding this comment.
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]): |
There was a problem hiding this comment.
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 👍 / 👎.
| const chosen = services.items.find( | ||
| (item) => item.serviceId === expectedEvidence.serviceId, | ||
| ); |
There was a problem hiding this comment.
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 👍 / 👎.
Pull Request
Summary
Closes #816
Closes #817
Closes #818
Security boundary
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 -- --checkcargo check --locked -p registry-discovery-client -p registry-discovery-client-node -p registry-discovery-client-py --all-targetscargo 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 warningsproducts/discovery/scripts/check-contracts.shproducts/discovery/scripts/test-http.shproducts/discovery/scripts/test-adopter-tutorial.shnpm run checkgit diff --checkNotes
Cargo.lockchange.DCO
Signed-off-bytrailer.