Skip to content

fix(cmc): readOffer must omit streams filter — :_cmc:_internal:offer is not a real parent#68

Closed
perki wants to merge 1 commit into
masterfrom
hds/fix-cmc-readOffer-streams-filter
Closed

fix(cmc): readOffer must omit streams filter — :_cmc:_internal:offer is not a real parent#68
perki wants to merge 1 commit into
masterfrom
hds/fix-cmc-readOffer-streams-filter

Conversation

@perki
Copy link
Copy Markdown
Member

@perki perki commented May 19, 2026

Follow-up to #67. After fixing streamIds → streams, cmc.readOffer still throws against any real api-server:

PryvError: events.get >> {"id":"unknown-referenced-resource",
"data":{"streams":[":_cmc:_internal:offer"]}}

Root cause

The api-server validates that streams referenced in events.get actually exist on the user's account. :_cmc:_internal:offer is not auto-provisioned — only the per-capability children :_cmc:_internal:offer:<capId> are minted by capabilityMintHook. The accepter doesn't know <capId> from the capabilityUrl alone, so a specific-stream filter isn't possible either.

The plugin's own implementation of the same read — readOfferViaCapability in components/cmc/src/acceptOrchestration.ts:71-78 of open-pryv.io — handles this correctly: omit the streams filter entirely and rely on the capability access's permissions to narrow the response to the single offer event this token can read.

// Capability access has `read` on a single per-capability stream
// (:_cmc:_internal:offer:<capId>) but the accepter doesn't know
// <capId> from the capabilityUrl alone. Query events.get without a
// streams filter — the access's permissions limit the response to
// the one event that lives on the only stream this token can read.
// types filter narrows to consent/request-cmc in case the offer stream
// ever holds more than one event in future revisions.

Fix

readOffer mirrors that pattern: drop the streams filter, keep types: [ET_REQUEST] as defense.

Test

No new tests in this PR — the existing cmc.test.js suite mocks Connection.apiOne, so neither the field-name bug (PR #67) nor this parent-stream-doesn't-exist bug is caught at unit-test time. Both surface only against a real api-server. The downstream behaviour is exercised end-to-end via HDS plan-59 Phase-3 scenario tests against a deployed open-pryv.io — cmc.readOffer returns the offer correctly post-fix.

Happy to add a wire-shape contract test (asserting events.get is called with no streams field and with types: ['consent/request-cmc']) if you'd like it landed alongside the fix.

…r is not a real parent

After the previous `streamIds → streams` fix, readOffer still fails
against any real api-server with `unknown-referenced-resource`: the
api-server validates that streams referenced in events.get actually
exist, and `:_cmc:_internal:offer` is NOT auto-provisioned on every
user account — only the per-capability children
`:_cmc:_internal:offer:<capId>` are minted by capabilityMintHook.

The accepter doesn't know <capId> from the capabilityUrl alone, so a
specific-stream filter isn't possible either. The plugin's own
implementation of the same read (`readOfferViaCapability` in
acceptOrchestration.ts:71-78) handles this correctly: omit the streams
filter entirely and rely on the capability access's permissions to
narrow the response to the single offer event this token can read.

Mirror that approach. Keep the `types: [ET_REQUEST]` filter as
defense in case future plugin revisions place additional event types
on the offer stream. Verified end-to-end on demo: readOffer now
returns the offer with `requestedPermissions`, `consent`, etc.

No new tests in this PR — the existing cmc.test.js mocks
Connection.apiOne, so neither the field-name bug nor this parent-
stream-doesn't-exist bug is caught at unit-test time. Both surface
only against a real api-server.
perki added a commit that referenced this pull request May 19, 2026
The contract test was asserting the buggy intermediate shape from PR
#67 (`streams: [':_cmc:_internal:offer']`). PR #68 corrected that to
omit the streams filter entirely (parent stream isn't auto-provisioned).
Update the assertion to match the final correct wire-shape: no `streams`,
no `streamIds`, with `types: ['consent/request-cmc']` as the only filter.
@perki
Copy link
Copy Markdown
Member Author

perki commented May 19, 2026

Merged into master at e11d080 (3 commits: 7b7fe2a fix + f84b663 test + e11d080 release).

Also updated the existing [CMCL1OA] contract test (added in 1.0.1 — which asserted the buggy intermediate shape from PR #67) to match the final correct shape: no streams, no streamIds, only types: ['consent/request-cmc']. Verified the test catches the bug by reverting the production fix locally.

End-to-end validation against deployed pryv.me: cmc.readOffer(capabilityUrl) now returns the full offer (requester / mode / requestedPermissions / consent).

Patch release just published: pryv@3.3.2 + @pryv/cmc@1.0.2 + @pryv/monitor@3.3.2 + @pryv/socket.io@3.3.2 lockstep.

Thanks for the rapid follow-up + the clean repro.

@perki perki closed this May 19, 2026
@perki
Copy link
Copy Markdown
Member Author

perki commented May 19, 2026

Correction to my previous comment: the patch release commit is on master (e11d080) but npm publish hasn't happened yet — handing back to the user for that step. Sorry for the confusion.

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