Skip to content

docs: ADRs for the PM-KISAN grievance and application status [#60] - #92

Open
KrutikaPhirangi wants to merge 39 commits into
feat/57-pmfby-grievance-pluginfrom
docs/60-pmkisan-adrs
Open

KrutikaPhirangi wants to merge 39 commits into
feat/57-pmfby-grievance-pluginfrom
docs/60-pmkisan-adrs

Conversation

@KrutikaPhirangi

Copy link
Copy Markdown

What

Architecture Decision Records for the PM-KISAN work in #71 (grievance) and #91 (application status), kept out of the code PRs so their comments stay short.

ADR Decision Code PR
0001 Provider envelopes live in common, with capability hooks #71
0002 An action served by another participant is named by its binding key #71
0003 Input validation lives in the capability packs, not the mappings #71
0004 An upstream refusal is a 400 when its reason is known, else a 502 #71
0005 A follow-up read adds a second answer to the first #91

docs/adr/ follows discovery-service's template. /docs/ stays ignored except docs/adr/.

…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.
…#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.
nisargabd and others added 9 commits October 14, 2026 13:08
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.
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.
docs/adr holds one record per decision, on discovery-service's template:
0001 provider envelopes in common, 0002 an action served by another
participant, 0003 input validation in the capability packs, 0004 upstream
refusals as 400 or 502, 0005 the follow-up read. /docs/ stays ignored
except docs/adr.
@github-actions

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

@github-actions

Copy link
Copy Markdown

📊 Test Coverage: ➖ Not applicable — no non-test Go files changed vs origin/feat/57-pmfby-grievance-plugin, so there are no lines to measure and no percentage to report.

@KrutikaPhirangi
KrutikaPhirangi force-pushed the feat/57-pmfby-grievance-plugin branch from 4d44c56 to b5d7048 Compare October 23, 2026 10:00
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.

2 participants