Skip to content

feat: add the Grievance plugin for PMFBY support and status [#57] - #70

Open
nisargabd wants to merge 38 commits into
developmentfrom
feat/57-pmfby-grievance-plugin
Open

nisargabd wants to merge 38 commits into
developmentfrom
feat/57-pmfby-grievance-plugin

Conversation

@nisargabd

@nisargabd nisargabd commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

What

Adds a Grievance plugin so a farmer can file a PMFBY crop-insurance grievance and check its status over Beckn, following the Grievance/v0.1 schema pack.

  • support files the grievance with PMFBY and returns the ticket number.
  • status reads the grievance back by ticket number and phone. It returns the status, category, crop year, season, crop and PMFBY's reply, and drops the farmer's personal details.
  • Status codes: PMFBY's Open is returned as Registered, Resolved as Resolved, and anything else as UnderReview.
  • Required fields and formats are checked by the schema pack before PMFBY is called.
  • Errors are answered in our own words. A wrong phone, an unknown application or no grievance found is a 400. Any other PMFBY failure is a 502. PMFBY's own message is logged, never returned.
  • Shared code gains a login that sends the token in a header and keeps it until the expiry PMFBY states. It also lets a mapping refuse a request or a reply.

Why

Farmers need to raise and track PMFBY grievances through the network. PMFBY answers every call with HTTP 200, even failures, so the adapter has to read each reply to tell the caller what went wrong.

Testing

Unit tests run the real mappings against a PMFBY stub. End to end, the flow was run on the quick-start stack against PMFBY's demo portal, and the other capabilities behave as before.

Known gaps

  • A missing field is refused (400, PMFBY not called), but the message does not name the field. The Grievance pack writes its two PMFBY required-field rules as anyOf, and a failed anyOf cannot say which field caused it. The fix is in the pack (the same rules written as oneOf).
  • Status codes other than Open and Resolved are provisional until PMFBY publishes its list.

Related: OpenAgriNet/helmcharts#78 (quick-start, charts, mock).

Checklist

  • Self review done

Closes #57

@nisargabd
nisargabd requested a review from ameersohel45 October 5, 2026 07:23
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🛡️ Trivy security scan (CRITICAL,HIGH,MEDIUM,LOW)

View full run

Go dependencies

Package Severity Installed Fixed in Advisory
github.com/getkin/kin-openapi HIGH v0.144.0 0.146.0 CVE-2026-100349
github.com/getkin/kin-openapi HIGH v0.144.0 0.147.0 CVE-2026-100351
github.com/getkin/kin-openapi HIGH v0.144.0 0.148.0 CVE-2026-100352
github.com/getkin/kin-openapi HIGH v0.144.0 0.149.0 CVE-2026-100353
github.com/getkin/kin-openapi HIGH v0.144.0 0.149.0 CVE-2026-100354
github.com/getkin/kin-openapi MEDIUM v0.144.0 0.145.0 CVE-2026-100347
github.com/getkin/kin-openapi MEDIUM v0.144.0 0.145.0 CVE-2026-100348

Container image

Package Severity Installed Fixed in Advisory
stdlib HIGH v1.26.9 1.26.10, 1.27.3 CVE-2026-111943

@nisargabd
nisargabd requested a review from manjudr October 5, 2026 07:23
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

📊 Test Coverage: ✅ Passed — 92% of changed lines covered, min 80%

Comment thread config/mappings/pmfby/grievance.status.yaml Outdated
Comment thread config/mappings/pmfby/grievance.support.yaml Outdated
Comment thread pkg/plugin/implementation/internal/common/serve.go
Comment thread pkg/plugin/implementation/internal/common/serve.go Outdated
Comment thread pkg/plugin/implementation/internal/common/common.go Outdated
nisargabd added a commit that referenced this pull request Oct 10, 2026
- An empty or all-null PMFBY record is no record (202), not a grievance
  reported as under review.
- cropYear must be a four-digit year: PMFBY reads requestYear like the ids.
- A mapping writing "_error": null means no error, not a 500.
- BindingPaths refuses a path list, so a caller reading one pair fails at
  startup instead of matching nothing.
@nisargabd nisargabd changed the title feat: add the Grievance plugin for PMFBY init, confirm and status [#57] feat: add the Grievance plugin for PMFBY support and status [#57] Oct 13, 2026
…ecks [#57]

Some upstreams, PMFBY among them, answer every call with a 200 and signal
failure in the body. Without a way to recognise that, the response half
mapped a refusal as if it were an answer.

A mapping file can now declare a reject section, checked after the call and
before the response half runs, and any check can carry its own error code
and HTTP status. The step uses Reject only when the mapper offers it, so
existing mappers and plugins need no change, and a check without a code
keeps returning the generic bad-request code it did before.
…token [#57]

PMFBY issues a token from its own login endpoint and expects it back,
unprefixed, in the Authorization header. tokenQuery already does that login
but puts the token in the query string, and oauth2 sends it as a Bearer
header, so neither fits.

tokenHeader reuses tokenQuery's exchange, caching and redaction and places
the token in a configured header, which keeps PMFBY's auth config-only.
…ppings [#57]

Farmers need to file a PMFBY grievance and check on it. The plugin is a thin
package over the shared step, like MandiPrice, and holds no PMFBY logic:
init sends an OTP, confirm files the grievance with it, and status reads the
ticket back, all described by the mapping files.

The mappings follow the PMFBY Postman collection for the ticket and status
calls (AddKRPHNCIPGrievenceSupportTicket, receiptSourceID) and the design doc
for the OTP step, which the collection does not cover yet. The OTP is never
echoed, and the status answer is an allow-list that drops the farmer's
personal details.
…he OTP is sent [#57]

A refusal carrying "responseMessage": null passed the $exists guard and then
failed inside $lowercase, so the caller got a 400 adaptation error instead of
the 502 a portal refusal should be. Only a string message is now read for
"otp".

A phone sent as a number passed the presence check, PMFBY sent the OTP, and
only then did the reply fail to mask it, so a retry would send a second OTP.
init now refuses a non-string phone before calling PMFBY.
…er [#57]

The reject path logged the provider's raw answer at WARN with only
credentials redacted, and PMFBY refusals can echo the farmer's phone number
or other personal details. The log now carries the mapping's own message,
which says what was refused without quoting the provider.
The exchange now serves tokenHeader as well as tokenQuery, so a name tied to
the query placement misled readers about what calls it.
…ping "null" [#57]

A status record with "TicketStatus": null failed the whole call in $replace,
so the farmer could not read their ticket at all. A null category name or
sub-category id came back as "null / ..." or "3.null", and a null description,
date or remark was returned as JSON null.

Each field is now mapped only when it holds a real value and dropped
otherwise, so one missing field no longer costs the rest of the answer.
…57]

The updated grievance design lodges with support, and a SupportAction
composes no contract: there is no offer to read the provider from and no
commitment to read the type from, so the step passed every support request
through unserved.

A step can now name a second pair of paths, tried only when the first finds
nothing. Grievance sets it to message.support.channels[].providerId and
.@type, where the design places them. Both or neither, as with the first
pair, and plugins that set neither behave as before.
…igned [#57]

The grievance design moved lodging from confirm to support and fixed the
replies and errors each action returns. The mappings follow it: support reads
the application number from orderId, the complaint from descriptor and the
farmer from channels[0]; status puts the complaint on contract.descriptor and
the case record in the attributes.

The error table needs answers the mapper cannot give, and the mapper stays
unchanged: the reject section and Reject hook are removed from jsonmapper.
Instead a mapping half may return a reserved _error object, which the step
turns into the reply:

- a missing field is refused by the request half with
  SCH_REQUIRED_FIELD_MISSING, before PMFBY is called
- a status reply with no case record is a 202 ACK, BIZ_NO_RESULTS_FOUND
- a 4xx from the provider keeps its status and is shown to the response half
  under _status; support reads PMFBY's 400 as a wrong OTP and answers
  400 BIZ_GENERIC_ERROR, and anything a mapping does not explain stays 502

Nothing is read from a failure body: PMFBY's failure format is not
documented. SendOTP's reply is not either, so on_init carries no expiresAt.
…#57]

A support request names its provider and type on its channel rather than on
a contract. Saying where they sit per action, in config, replaces the fallback
pair: a later action, or a schema that moves the fields, is a config edit.
New already validates every action's paths and refuses an empty one, so the
parser checking both halves again was a second copy of the same rule.
providerIdAt and capabilityCodeAt may each list several paths, paired by
position and tried in order. A support request is found on its channel by the
second pair, so no action-keyed block, no parser for it and no change to the
plugin's config parsing are needed. A single path behaves as before.
tokenHeader reuses the tokenQuery exchange unchanged, so its name and the
redaction comment need no rewrite; only the scheme check and the token path
differ.
- An empty or all-null PMFBY record is no record (202), not a grievance
  reported as under review.
- cropYear must be a four-digit year: PMFBY reads requestYear like the ids.
- A mapping writing "_error": null means no error, not a 500.
- BindingPaths refuses a path list, so a caller reading one pair fails at
  startup instead of matching nothing.
…ounds [#57]

The pack now refuses case.status.name: the portal's TicketStatus picks the
code and is not forwarded. And the implementation doc restates enrolmentId's
10-30 character bound on support, where it rides in orderId outside the
channel the pack validates.
…nel [#57]

The use case doc makes the category codes optional, defaulting to PMFBY's published 3 and 10, and the pack keeps enrolmentId in the channel instead of message.support.orderId.
@nisargabd
nisargabd force-pushed the feat/57-pmfby-grievance-plugin branch from 9185726 to 9dec9bb Compare October 13, 2026 06:09
…#57]

PMFBY answers every call with HTTP 200, so a refusal was reported as a 502
with our own text, hiding the reason the caller can act on. A refusal with
a reason is now a 400 carrying PMFBY's message; a reply with no reason is a
502, since that is PMFBY failing rather than the caller. Format checks move
out of the mappings to the pack, and filedOn becomes a UTC instant.
PMFBY's login states when its token expires, so a configured tokenTtl was a
guess at a number the issuer already gives. tokenExpiryField reads it from
the reply instead; tokenTtl stays for issuers that state none.
PMFBY's expirationTime is the moment a token was issued, not when it
expires, so every token read as already expired and every call failed.
validTo is the expiry, as an RFC 3339 time, so tokenExpiryField now reads
that form as well as Unix seconds.
The demo portal sends InsertDateTime as "2026-10-12T11:02:49.000Z", not the
bare IST time the mapping expected, so filedOn was always dropped on a read.
PMFBY answers every failure with HTTP 200 and responseCode "0", so the
mapping passed its message straight to the caller: an internal
"AxiosError: Request failed with status code 403" reached the farmer as a
400, as if the request were wrong. The contract says the portal's own
message is never forwarded.

A refusal PMFBY is known to give is now a 400 in our words; any other
failure is a 502 asking the caller to try again later. PMFBY's text rides
on _error.cause into the warning log and is never returned.
… status fields [#57]

The separate PMFBYGrievance pack is gone: PMFBY is now the PMFBY branch of
Grievance/v0.1, picked by scheme.code, under @type openagrinet:Grievance.
The binding key, the request's applicantPhone (now applicantMobile) and
the fixtures follow it.

The pack's status read now lists what PMFBY's record really carries, so
the mapping returns cropYear, season (1/2/3 as Kharif/Rabi/Zaid), the
category and sub-category codes beside their names, and case.reply from
latestRemark. PMFBY's 0 for "none", out-of-range values and blank remarks
are dropped rather than sent as values the pack refuses.
PMFBY's "Open" is a ticket nobody has acted on yet, which is Registered,
not UnderReview; it is still not final, so an unknown phrase keeps
falling to UnderReview.

The lodge reply took filedOn from the caller's complaintDate while the
status read takes it from PMFBY's InsertDateTime, so a back-dated
complaint showed two filing times for one ticket. filedOn is now the
lodge time on both; complaintDate still goes to PMFBY as its IST date.
@nisargabd
nisargabd force-pushed the feat/57-pmfby-grievance-plugin branch from 83f9f40 to c604cb0 Compare October 19, 2026 05:37
The Grievance pack now enforces its mustSend lists, so the schema
validator refuses a support or status call missing a required field
before the mapping runs. The mappings' own $missing checks could no
longer fire, so they and their tests are removed.
@manjudr
manjudr added this pull request to stack #87 October 22, 2026 06:40
Comment thread config/mappings/pmfby/grievance.status.yaml Outdated
Comment thread config/mappings/pmfby/grievance.status.yaml Outdated

@manjudr manjudr left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We need to structure the mapper file in the proper format for easy reading or maintenance

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.

PM-FBY application status and grievance provider plugin

3 participants