refactor(actions): include residual axios-risk slices in branch
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import axios from "axios";
|
||||
import { BASE_URL } from "../core/env";
|
||||
import { consoleLogger } from "../core/logger";
|
||||
import { buildHashedQueryUrl } from "../clients/relayClient";
|
||||
@@ -129,7 +128,7 @@ export const deleteMyRepresentations = (myRepresentationsID) => {
|
||||
|
||||
return buildHashedQueryUrl(queryUrl)
|
||||
.then((signedUrl) =>
|
||||
axios({
|
||||
requestJson({
|
||||
method: "delete",
|
||||
url: signedUrl,
|
||||
headers: {
|
||||
@@ -142,9 +141,6 @@ export const deleteMyRepresentations = (myRepresentationsID) => {
|
||||
}
|
||||
})
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
@@ -170,14 +166,11 @@ export const deleteWatchedCases = async (watchedCaseID) => {
|
||||
|
||||
return buildHashedQueryUrl(queryUrl)
|
||||
.then((signedUrl) =>
|
||||
axios({
|
||||
requestJson({
|
||||
method: "delete",
|
||||
url: signedUrl
|
||||
})
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
import axios from "axios";
|
||||
import { BASE_URL } from "../core/env";
|
||||
import { consoleLogger } from "../core/logger";
|
||||
import { getJson } from "../clients/endpointClient";
|
||||
import {
|
||||
logAndReturnResponse,
|
||||
logAndReturnEmptyValueErrorResponse
|
||||
} from "./httpServiceUtils";
|
||||
|
||||
export const getBasicSearch = (searchString) => {
|
||||
return axios
|
||||
.get(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicsearch_api?searchString=" +
|
||||
searchString
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicsearch_api?searchString=" +
|
||||
searchString
|
||||
).catch(logAndReturnResponse);
|
||||
};
|
||||
|
||||
export const getBasicDNSURLSearch = (searchString) => {
|
||||
return axios
|
||||
.get(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicdnsurlsearch_api?searchString=" +
|
||||
searchString
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicdnsurlsearch_api?searchString=" +
|
||||
searchString
|
||||
).catch(logAndReturnResponse);
|
||||
};
|
||||
|
||||
export const getAddressSearchPaged = (
|
||||
@@ -39,23 +29,18 @@ export const getAddressSearchPaged = (
|
||||
fieldSort,
|
||||
showNumberOfRecords
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getbasicsearch_by_address_paged_api?searchString=" +
|
||||
searchString +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(
|
||||
"/api/endpoint/getbasicsearch_by_address_paged_api?searchString=" +
|
||||
searchString +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
).catch(logAndReturnResponse);
|
||||
};
|
||||
|
||||
export const getBasicSearchPaged = (
|
||||
@@ -65,46 +50,36 @@ export const getBasicSearchPaged = (
|
||||
fieldSort,
|
||||
showNumberOfRecords
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getbasicsearchpaged_api?searchString=" +
|
||||
searchString +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(
|
||||
"/api/endpoint/getbasicsearchpaged_api?searchString=" +
|
||||
searchString +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
).catch(logAndReturnResponse);
|
||||
};
|
||||
|
||||
export const getDNSCoords = () => {
|
||||
return axios
|
||||
.get(BASE_URL + "/api/endpoint/getdnscoords_api")
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnEmptyValueErrorResponse);
|
||||
return getJson(BASE_URL + "/api/endpoint/getdnscoords_api").catch(
|
||||
logAndReturnEmptyValueErrorResponse
|
||||
);
|
||||
};
|
||||
|
||||
export const getDNSList = (searchString) => {
|
||||
return axios
|
||||
.get(BASE_URL + "/api/endpoint/getdnslist_api")
|
||||
.then((res) => res.data)
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(BASE_URL + "/api/endpoint/getdnslist_api").catch(
|
||||
logAndReturnResponse
|
||||
);
|
||||
};
|
||||
|
||||
export const getBasicDNSSearch = (searchString) => {
|
||||
return axios
|
||||
.get(BASE_URL + "/api/endpoint/getbasicdnssearch_api")
|
||||
.then((res) => res.data)
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(BASE_URL + "/api/endpoint/getbasicdnssearch_api").catch(
|
||||
logAndReturnResponse
|
||||
);
|
||||
};
|
||||
|
||||
export const getBasicDNSSearchPaged = (
|
||||
@@ -113,30 +88,24 @@ export const getBasicDNSSearchPaged = (
|
||||
fieldSort,
|
||||
showNumberOfRecords
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getbasicdnssearchpaged_api?pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch(logAndReturnResponse);
|
||||
return getJson(
|
||||
"/api/endpoint/getbasicdnssearchpaged_api?pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
).catch(logAndReturnResponse);
|
||||
};
|
||||
|
||||
export const getAdvancedSearch = (searchString) => {
|
||||
return axios
|
||||
.get(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getadvancedsearch_api?searchstring=" +
|
||||
JSON.stringify(searchString)
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch(logAndReturnEmptyValueErrorResponse);
|
||||
return getJson(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getadvancedsearch_api?searchstring=" +
|
||||
JSON.stringify(searchString)
|
||||
).catch(logAndReturnEmptyValueErrorResponse);
|
||||
};
|
||||
|
||||
export const getAdvancedSearchPaged = (
|
||||
@@ -146,21 +115,18 @@ export const getAdvancedSearchPaged = (
|
||||
fieldSort,
|
||||
showNumberOfRecords
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getadvancedsearchpaged_api?searchstring=" +
|
||||
JSON.stringify(searchString) +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch(logAndReturnEmptyValueErrorResponse);
|
||||
return getJson(
|
||||
"/api/endpoint/getadvancedsearchpaged_api?searchstring=" +
|
||||
JSON.stringify(searchString) +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords
|
||||
).catch(logAndReturnEmptyValueErrorResponse);
|
||||
};
|
||||
|
||||
export const getBasicSearchDetails = async (
|
||||
@@ -169,20 +135,17 @@ export const getBasicSearchDetails = async (
|
||||
primaryIdAttribute,
|
||||
incidentID
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicsearchdetails_api?appealTypeName=" +
|
||||
appealTypeName +
|
||||
"&primaryIdAttribute=" +
|
||||
primaryIdAttribute +
|
||||
"&incidentID=" +
|
||||
incidentID
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
return getJson(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicsearchdetails_api?appealTypeName=" +
|
||||
appealTypeName +
|
||||
"&primaryIdAttribute=" +
|
||||
primaryIdAttribute +
|
||||
"&incidentID=" +
|
||||
incidentID
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getBasicPartSavedDetails = (
|
||||
@@ -191,20 +154,17 @@ export const getBasicPartSavedDetails = (
|
||||
primaryIdAttribute,
|
||||
incidentID
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicpartsaveddetails_api?appealTypeName=" +
|
||||
appealTypeName +
|
||||
"&primaryIdAttribute=" +
|
||||
primaryIdAttribute +
|
||||
"&incidentID=" +
|
||||
incidentID
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
return getJson(
|
||||
BASE_URL +
|
||||
"/api/endpoint/getbasicpartsaveddetails_api?appealTypeName=" +
|
||||
appealTypeName +
|
||||
"&primaryIdAttribute=" +
|
||||
primaryIdAttribute +
|
||||
"&incidentID=" +
|
||||
incidentID
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getBasicSearchDetailsPaged = async (
|
||||
@@ -231,8 +191,8 @@ export const getBasicSearchDetailsPaged = async (
|
||||
)}`;
|
||||
|
||||
try {
|
||||
const res = await axios.get(url);
|
||||
return res.data.value || [];
|
||||
const data = await getJson(url);
|
||||
return data.value || [];
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return [];
|
||||
@@ -240,27 +200,20 @@ export const getBasicSearchDetailsPaged = async (
|
||||
};
|
||||
|
||||
export const getSearchDocumentDetails = (incidentID) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getsearchdocumentdetails_api?incidentid=" +
|
||||
incidentID
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
throw error;
|
||||
});
|
||||
return getJson(
|
||||
"/api/endpoint/getsearchdocumentdetails_api?incidentid=" + incidentID
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
export const getSearchDocumentTypes = (incidentID) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
return getJson(
|
||||
"/api/endpoint/getsearchdocumentTypes_api?incidentid=" + incidentID
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getSearchDocumentDetailsPaged = (
|
||||
@@ -271,39 +224,30 @@ export const getSearchDocumentDetailsPaged = (
|
||||
showNumberOfRecords,
|
||||
documentType
|
||||
) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getsearchdocumentdetailspaged_api?incidentid=" +
|
||||
incidentID +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords +
|
||||
"&documentType=" +
|
||||
documentType
|
||||
)
|
||||
.then((res) => res.data)
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
return getJson(
|
||||
"/api/endpoint/getsearchdocumentdetailspaged_api?incidentid=" +
|
||||
incidentID +
|
||||
"&pageNumber=" +
|
||||
pageNumber +
|
||||
"&orderby=" +
|
||||
orderBy +
|
||||
"&fieldSort=" +
|
||||
fieldSort +
|
||||
"&showNumberOfRecords=" +
|
||||
showNumberOfRecords +
|
||||
"&documentType=" +
|
||||
documentType
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getLinkedCases = (parentIncidentid) => {
|
||||
return axios
|
||||
.get(
|
||||
"/api/endpoint/getlinkedcases_api?parentincidentid=" +
|
||||
parentIncidentid
|
||||
)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
return getJson(
|
||||
"/api/endpoint/getlinkedcases_api?parentincidentid=" + parentIncidentid
|
||||
).catch((error) => {
|
||||
consoleLogger(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const getAddressSearch = async (searchString) => {
|
||||
@@ -313,14 +257,5 @@ export const getAddressSearch = async (searchString) => {
|
||||
var queryUrl =
|
||||
BASE_URL + "/api/endpoint/getbasicsearch_by_address_api?" + str;
|
||||
|
||||
var config = {
|
||||
method: "get",
|
||||
url: queryUrl
|
||||
};
|
||||
|
||||
return axios(config)
|
||||
.then((res) => {
|
||||
return res.data;
|
||||
})
|
||||
.catch(logAndReturnEmptyValueErrorResponse);
|
||||
return getJson(queryUrl).catch(logAndReturnEmptyValueErrorResponse);
|
||||
};
|
||||
|
||||
@@ -75,3 +75,58 @@ Guidance:
|
||||
|
||||
- Do not treat these files as active runtime architecture unless explicitly reactivated.
|
||||
- If reactivation is proposed, document rationale and rollout/rollback in `memory-bank/change-log.md` and `context/runbook.md`.
|
||||
|
||||
## Current State Assessment and Prioritised Next Steps (2026-03-25)
|
||||
|
||||
### Assessment summary
|
||||
|
||||
The platform has moved into a stronger operational and architectural posture through sustained bounded refactor slices and contract hardening.
|
||||
|
||||
Strengths:
|
||||
|
||||
1. **Governance maturity is high**
|
||||
- Guardrails are explicit for auth/session integrity, CSP/security headers, Prisma source-of-truth, relay hash integrity, and EN/CY parity.
|
||||
2. **API reliability posture has improved materially**
|
||||
- Endpoint contract hardening and phase21 contract test expansion have reduced inconsistency in negative-path handling.
|
||||
3. **Relay operations are significantly more robust**
|
||||
- Shared relay forwarding now includes bounded retry/timeout policy, structured redacted lifecycle logging, and documented rollout/rollback controls.
|
||||
4. **Façade decomposition is delivering low-risk progress**
|
||||
- `actions` layer migration to shared clients (`relayClient`, `endpointClient`) is reducing duplicated request boilerplate and lowering drift risk.
|
||||
|
||||
Primary residual risks/gaps:
|
||||
|
||||
1. **Remaining direct-service inconsistency**
|
||||
- Some direct services still contain legacy axios/request patterns and bespoke signed-request blocks.
|
||||
2. **Coverage concentration**
|
||||
- Contract tests are strong in targeted slices, but end-to-end/high-value journey coverage in sensitive flows remains comparatively sparse.
|
||||
3. **Logging hygiene variance**
|
||||
- Structured redaction exists in relay paths, but broader codebase logging still has uneven consistency.
|
||||
4. **i18n parity assurance remains process-heavy**
|
||||
- EN/CY parity relies heavily on manual discipline rather than automated parity checks.
|
||||
|
||||
### Prioritised next steps
|
||||
|
||||
1. **Complete direct-service consistency sweep (low risk, high maintainability)**
|
||||
- Prioritise `actions/services/searchDirectService.js` for `getJson`/`requestJson` adoption in bounded slices.
|
||||
- Preserve existing error-return behavior contracts per function.
|
||||
2. **Consolidate signed-request patterns (medium risk, high security clarity)**
|
||||
- Introduce a focused signed-request helper for hash-based/signed delete/get pathways currently repeated in service modules.
|
||||
- Keep existing hash/header semantics unchanged while reducing duplication.
|
||||
3. **Add high-value regression automation (high value)**
|
||||
- Add focused automated checks for:
|
||||
- auth callback/redirect safety
|
||||
- one signed-delete negative path
|
||||
- one upload/document authorization negative path
|
||||
- one EN/CY route parity check
|
||||
4. **Perform targeted logging hardening in sensitive paths**
|
||||
- Continue replacing direct/verbose logging in `auth`, `file`, `email`, and account-sensitive endpoint paths with redacted structured logging patterns.
|
||||
5. **Introduce EN/CY parity CI checks**
|
||||
- Add automated checks for route rewrite parity and locale key alignment to reduce drift and manual burden.
|
||||
6. **Continue endpoint sprawl reduction**
|
||||
- Keep collapsing duplicated proxy/request patterns behind shared helpers in bounded route clusters while preserving public response contracts.
|
||||
|
||||
### Recommended execution sequence
|
||||
|
||||
- **Sequence A (immediate):** Step 1 + Step 3 (fastest risk reduction per effort)
|
||||
- **Sequence B (next):** Step 2 + Step 4 (security/logging consistency consolidation)
|
||||
- **Sequence C (after):** Step 5 + Step 6 (institutionalise parity and reduce long-tail maintenance cost)
|
||||
|
||||
@@ -1892,3 +1892,69 @@ Validation:
|
||||
Follow-ups:
|
||||
|
||||
- Optional next bounded slice: assess whether signed-delete helpers in `portalDirectService` should remain explicit axios calls (for clarity on headers/hash semantics) or move to a dedicated signed-request client helper.
|
||||
|
||||
---
|
||||
|
||||
### CL-053: Architecture documentation update — current-state assessment + prioritised roadmap
|
||||
|
||||
date: 2026-03-25
|
||||
author: Cline
|
||||
scope: `context/architecture.md`
|
||||
type: change
|
||||
rationale: User requested the architectural assessment be documented in-project so current state, residual risks, and recommended sequencing are explicitly captured for team planning.
|
||||
impact: Improves documentation clarity and planning alignment; no runtime behavior changes.
|
||||
status: completed
|
||||
|
||||
Summary:
|
||||
|
||||
- Added a new section to `context/architecture.md`:
|
||||
- **Current State Assessment and Prioritised Next Steps (2026-03-25)**
|
||||
- Documented:
|
||||
- strengths (governance maturity, API/relay hardening progress, façade decomposition progress)
|
||||
- residual risks/gaps (remaining direct-service inconsistency, coverage concentration, logging variance, i18n parity automation gap)
|
||||
- prioritised next steps with rationale
|
||||
- recommended execution sequencing (immediate/next/after)
|
||||
|
||||
Validation:
|
||||
|
||||
- Manual coherence check against existing guardrails/runbook/integration-map and memory-bank context.
|
||||
|
||||
Follow-ups:
|
||||
|
||||
- Optional: convert the prioritised steps into a sprint-ready plan (tasks, acceptance criteria, validation matrix, rollback notes).
|
||||
|
||||
---
|
||||
|
||||
### CL-054: Branch correction — include residual duplication/axios risk slices (search + signed-delete portal flows)
|
||||
|
||||
date: 2026-03-25
|
||||
author: Cline
|
||||
scope: `actions/services/{portalDirectService,searchDirectService}.js`, `context/architecture.md`
|
||||
type: change
|
||||
rationale: User requested the previously identified primary remaining technical risk slices be explicitly included on this branch, specifically residual axios/duplication in search direct service and signed-delete portal helper flows.
|
||||
impact: Further reduces direct-service axios boilerplate/duplication and aligns additional high-use service paths to shared endpoint client patterns while preserving existing behavior contracts.
|
||||
status: completed
|
||||
|
||||
Summary:
|
||||
|
||||
- Implemented the requested risk slices on branch:
|
||||
1. **Portal signed-delete flow parity** (`portalDirectService`)
|
||||
- migrated signed delete helpers from direct `axios({...}).then(res.data)` to `requestJson({...})`:
|
||||
- `deleteMyRepresentations`
|
||||
- `deleteWatchedCases`
|
||||
- removed now-unused `axios` import from module
|
||||
- preserved hash-signing flow and custom headers semantics
|
||||
2. **Search direct service axios reduction** (`searchDirectService`)
|
||||
- migrated service GET helpers from direct `axios.get(...).then(res.data)` and `axios(config)` to `getJson(...)`
|
||||
- preserved existing catch semantics (`logAndReturnResponse`, `logAndReturnEmptyValueErrorResponse`, and explicit throw path in `getSearchDocumentDetails`)
|
||||
- removed direct `axios` import from module
|
||||
- Documentation alignment:
|
||||
- retained architecture assessment section in `context/architecture.md` that calls out these residual-risk slices and prioritisation.
|
||||
|
||||
Validation:
|
||||
|
||||
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors)
|
||||
|
||||
Follow-ups:
|
||||
|
||||
- Optional next bounded slice: assess whether a dedicated signed-request client helper should encapsulate hash + headers + method conventions to prevent future drift in remaining signed flows.
|
||||
|
||||
Reference in New Issue
Block a user