Individual account approval resources (dotnet) - #46
Open
lucasmiranda-stark wants to merge 1 commit into
Open
Conversation
lucasmiranda-stark
force-pushed
the
feature/account-approval
branch
from
May 30, 2026 00:30
5e2bbc3 to
f841482
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
The account-approval feature —
IndividualAccountRequestandIndividualAccountAttachment(each with a read-only.Log) — for the dotnet infra SDK, generated from contractsindividual-account-request.md(v3) andindividual-account-attachment.md(v3). Run-type: generate. Run artifacts:runs/20260528-182857-account-approval/dotnet/.What changed
IndividualAccountRequest(create/get/query/page/update + read-only.Log) with a structuredAddresssub-resource (nested object, never flattened).IndividualAccountAttachment(create/get/query/page/cancel + read-only.Log); the constructor encodescontent+contentTypeinto adata:<contentType>;base64,…URL client-side;contentTypeis input-only;cancel→ statusdeleted(idempotent).Test plan
runs/20260528-182857-account-approval/dotnet/test-run-success.md)Risk & Rollback
Contract review (Phase 7)
Contract Review
Checklist
IndividualAccountRequest — mandatory points (M1–M12)
createaccepts a list ofIndividualAccountRequestand returns the same shape with server-assignedid,status,accountType,created,updatedpopulatedCreate(List<IndividualAccountRequest>))CreateGet@ IndividualAccountRequest.cs:19 — assertsNotNull(request.ID),Equal("individual", request.AccountType),NotNull(request.Status)addressis an object (not a string), with required sub-fieldsstreet,number,neighborhood,city,state,zipCode; serialized as nested JSON, never flattenedAddress Address), ResourceMaker:330 (Address.Parse(json.address))CreateWithStructuredAddress@ IndividualAccountRequest.cs:35 — assertsrequest.Address.Street,.Number,.Neighborhoodnot null/value-equal after round-tripget(id)returns a singleIndividualAccountRequestby idGet(string id))CreateGet@ IndividualAccountRequest.cs:27 — callsGet(id: request.ID), assertsgetRequest.ID == request.IDqueryreturns an iterable ofIndividualAccountRequest, acceptinglimit,after,before,status,tags,idsIEnumerable<IndividualAccountRequest> Query(…))Query@ IndividualAccountRequest.cs:52;QueryIds@ :64;QueryParams@ :89 — all filter params exercisedpagereturns(items, cursor)and accepts same params asquerypluscursor(List<IndividualAccountRequest> page, string pageCursor) Page(…))Page@ IndividualAccountRequest.cs:105;PageParams@ :127 — cursor returned, items/cursor unpacked, all params exercisedupdate(id, …)PATCHes the request; accepts any subset ofname,taxId,address,income,status,tags; replacesaddressas whole objectUpdate(string id, string name, string taxID, Address address, long? income, string status, List<string> tags))Update@ IndividualAccountRequest.cs:147 — patchesnameandaddressas whole object, asserts updated valuesstatusenum is exactlyapproved|created|denied|processing|updated; every fetched status is a member of this setStatusEnum@ IndividualAccountRequest.cs:173 — queries 20 records, asserts every.Statusis in the allowed listIndividualAccountRequest.Logis read-only, exposed under<resource>.Log, providesget,query,page; LogRequestfield is the parent typepublic class Log : Resource, withIndividualAccountRequest Requestfield at :28, staticGet,Query,Pageat :73, :104, :145)QueryAndGet@ IndividualAccountRequestLog.cs:17 — assertslog.Request != nullandlog.Request.ID != null; also callsLog.Get;Page@ :41Log.queryandLog.pageacceptlimit,after,before,types,accountRequestIdsQuery(…, List<string> types, List<string> accountRequestIds)); Log.cs:145–168 (Page(…, List<string> types, List<string> accountRequestIds))QueryParams@ IndividualAccountRequestLog.cs:63 — all five params includingaccountRequestIdsserialized;PageParams@ :78created,updated) are parsed to nativeDateTimeon the way outStarkCore.Utils.Checks.CheckDateTime(createdString); same forupdated); Log.cs:182–183 (same pattern)OutputOnlyFieldsIgnoredOnCreate@ IndividualAccountRequest.cs:190 — assertsrequest.Created != nullandrequest.Updated != nullafter API round-tripaccountType,flags,id,status,created,updatedare output-only — passing to constructor populates field but API ignores on POSTnulldefaults; not included in POST payload serialization)OutputOnlyFieldsIgnoredOnCreate@ IndividualAccountRequest.cs:190 — constructs without output-only fields, verifies API populates them on responseInputErrorsis raised (not a specific code string): name empty, taxId invalid, address incomplete, income < 0, status transition invalid, unknown idStarkCore.Error.InputErrors)CreateWithEmptyNameRaises@ IndividualAccountRequest.cs:211;CreateWithInvalidTaxIdRaises@ :227;CreateWithIncompleteAddressRaises@ :244;CreateWithNegativeIncomeRaises@ :265;UpdateWithInvalidStatusRaises@ :281;GetUnknownIdRaises@ :295 — all useAssert.Throws<StarkCore.Error.InputErrors>IndividualAccountAttachment — mandatory points (M1–M14)
createaccepts a list ofIndividualAccountAttachmentand returns the same shape with server-assignedid,status,createdpopulatedCreate(List<IndividualAccountAttachment>))CreateGet@ IndividualAccountAttachment.cs:22 — assertsNotNull(attachment.ID)after create round-tripcontent+contentTypeintodata:<contentType>;base64,<payload>URL client-side; callers pass raw bytes + MIME typebyte[]constructor:Content = "data:" + contentType + ";base64," + Convert.ToBase64String(content))CreateGet@ IndividualAccountAttachment.cs:22 — usesExample()which calls byte-array constructor withSampleImageBytes()+"image/png"contentTypeis input-only: after constructor builds data URL,contentTypeis not serialized as its own wire field; response object has no populatedContentTypepublic string ContentType { get; }— declared but never assigned inbyte[]constructor orResourceMaker; ResourceMaker at :329–343 does not setContentType)ContentTypeIsInputOnly@ IndividualAccountAttachment.cs:40 — assertsattachment.ContentType == nullon response from createget(id)returns a singleIndividualAccountAttachmentby idGet(string id))CreateGet@ IndividualAccountAttachment.cs:31 — callsGet(id: attachment.ID), asserts IDs equalqueryreturns an iterable ofIndividualAccountAttachment, acceptinglimit,after,before,status,tags,idsIEnumerable<IndividualAccountAttachment> Query(…))Query@ IndividualAccountAttachment.cs:55;QueryIds@ :69;QueryParams@ :94pagereturns(items, cursor)and accepts same params asquerypluscursor(List<IndividualAccountAttachment> page, string pageCursor) Page(…))Page@ IndividualAccountAttachment.cs:109;PageParams@ :133cancel(id)maps toDELETE /individual-account-attachment/{id}, returns resource withstatus = deleted; cancel is idempotentCancel(string id)callsRest.DeleteId(…))GetAndCancel@ IndividualAccountAttachment.cs:153 — assertscanceled.Status == "deleted"; callsCanceltwice, second invocation asserts no exception and returns same IDIndividualAccountAttachment(notaccountRequestAttachment)public partial class IndividualAccountAttachment : Resource); Resource() at :324 returns"IndividualAccountAttachment"CreateGet@ IndividualAccountAttachment.cs:22 — callsIndividualAccountAttachment.Create,IndividualAccountAttachment.Getby class nameIndividualAccountAttachment.Logis read-only, exposed under<resource>.Log, providesget,query,page; Logattachmentfield is the parent typepublic class Log : Resource,IndividualAccountAttachment Attachmentat :28, staticGet/Query/Pageat :73/:104/:145)QueryAndGet@ IndividualAccountAttachmentLog.cs:12 — assertslog.Attachment != nullandlog.Attachment.ID != null; callsLog.Get;Page@ :40Log.queryandLog.pageacceptlimit,after,before,types,attachmentIds(notaccountRequestIds)Query(…, List<string> types, List<string> attachmentIds)); :145–168 (Page(…, List<string> attachmentIds))QueryParams@ IndividualAccountAttachmentLog.cs:65 — usesattachmentIdsparam;PageParams@ :83 — same; param isattachmentIdsnotaccountRequestIdstypeenum is exactlydrivers-license-front|drivers-license-back|identity-front|identity-back;selfienot validselfie)CreateWithInvalidTypeRaises@ IndividualAccountAttachment.cs:177 — uses"not-a-real-type"(comment notesselfieis also invalid); assertsInputErrorsraisedcreatedis parsed to nativeDateTimeDateTime created = StarkCore.Utils.Checks.CheckDateTime(createdString)); Log.cs:182–183 (same)CreateGet@ IndividualAccountAttachment.cs:22 — creates attachment, which exercisesResourceMaker;Createdis typedDateTime?at scaffold line 40InputErrorsis raised: type invalid, content empty, contentType missing, accountRequestId not found, unknown idStarkCore.Error.InputErrors); :191–200 (Get same)CreateWithInvalidTypeRaises@ IndividualAccountAttachment.cs:179;CreateWithEmptyContentRaises@ :194;CreateWithMissingContentTypeRaises@ :213;CreateWithUnknownAccountRequestIdRaises@ :229;GetUnknownIdRaises@ :243 — all useAssert.Throws<StarkCore.Error.InputErrors>AccountRequestAttachmentresource (route/account-request-attachment, SDK idaccountRequestAttachment) is removed in same change setgenerate; no priorAccountRequestAttachmentexists in sdk-infra/dotnet (grep confirms zero hits); M14 applies only tomigraterun-types where the legacy resource pre-existsSummary
If FAIL — routing recommendation
N/A — PASS
Metadata
contracts/individual-account-request.md(v3),contracts/individual-account-attachment.md(v3)runs/20260528-182857-account-approval/dotnet/feature/account-approval7d02cd06e9b2a2865c3cbdc46ce32e2e8e642e25