Engineering reference: the stock count reconciler architecture
This is the reference. The same stock count reconciler, drawn without the business framing — service names, resource identifiers, the region, the Bedrock model id, the Lambda inventory and their Function URLs, IAM scopes, the EventBridge rules and the Scheduler entries, the SQS and dead-letter queue, and the DynamoDB table and key schemas. If you want to build this, or review someone’s build of it, this is the page to read. Everything is prefixed stkr- and lives in one region, eu-west-2.
Key takeaways
- Seven Lambdas (Python 3.14, arm64), one region (eu-west-2), no API Gateway, no VPC, no always-on compute.
stkr-approvecarries two Function URLs — approve and reject — signed and tied to a single variance.- Three DynamoDB tables on-demand:
stkr-inventory,stkr-counts,stkr-adjustments. - One Bedrock model — Claude Haiku 4.5 via Global cross-Region inference — reachable only by
stkr-causeandstkr-summary. - Every IAM role is scoped to its job; only
stkr-approvecan write a stock quantity.
The architecture, drawn for engineers
stkr-adjustments.Lambdas (Python 3.14, arm64)
stkr-capture— triggered by the SQS queue behind the S3 count-submitted event. Normalises the scan/sheet, ties lines to the open session, writes clean counted lines tostkr-counts, parks unmappable lines. No Function URL.stkr-variance— runs per captured session. Joins counted lines to thestkr-inventorysnapshot, computes gap in units/percent/value, applies the materiality tolerance, writes variances, and invokesstkr-causeper material gap. Plain Python; no Bedrock. No Function URL.stkr-cause— builds the evidence bundle and makes one Bedrock call, returning a cause from the fixed list of four plus a rationale. IAM allowsbedrock:InvokeModeland reads; no write tostkr-inventory. No Function URL.stkr-approve— two Lambda Function URLs, one for approve, one for reject, each a signed link scoped to a single variance. Approve does the conditional post tostkr-inventory; both write an audit row tostkr-adjustments. The only Lambda permitted to write a stock quantity.stkr-sync— fired by EventBridge Scheduler every 15 minutes. Reads the system of record using a credential from Secrets Manager and mirrors it intostkr-inventory. No Function URL.stkr-sweep— daily via Scheduler. Re-surfaces aged unactioned variances and issues periodic count reminders for stale locations. No Function URL.stkr-summary— monthly via Scheduler. One larger Bedrock call for the narrative summary, sent by SES. No Function URL.
EventBridge rules and schedules
- Rule — count-submitted. S3
ObjectCreatedundersessions/*/→ SQS (stkr-capture-queue) with a redrive tostkr-capture-dlqafter 2 attempts →stkr-capture. - Schedule — sync.
rate(15 minutes)→stkr-sync. - Schedule — daily sweep + reminders.
cron(0 7 * * ? *)→stkr-sweep(respecting quiet hours from the rules doc). - Schedule — monthly summary.
cron(0 8 1 * ? *)→stkr-summary.
DynamoDB tables (on-demand) + key schema
stkr-inventory— PKLOC#<location>, SKSKU#<sku>. Attributes:name,system_qty,unit_cost,pack_size,aliases,snapshot_version. Written only bystkr-syncandstkr-approve.stkr-counts— PKSESSION#<id>, SKMETAfor the session header (location,counter,opened_at,status) and SKLINE#<sku>per counted line (counted_qty,state,gap_units,gap_pct,gap_value,cause).stkr-adjustments— PKSKU#<sku>, SKTS#<iso8601>#<variance_id>. Attributes:action(queued/approved/rejected/posted),user,cause,qty_before,qty_after,session_id. Append-only audit trail.
S3, SES, Secrets Manager
- S3 —
stkr-counts-bucket. Versioning on. Layoutsessions/<session-id>/{scan,sheet}.csv. Lifecycle: transition raw uploads to Glacier after 90 days. - SES. Outbound only — approver notifications and the monthly summary, through one configuration set for bounce/complaint tracking. No inbound rule set (there is no email lane).
- Secrets Manager. One secret,
stkr/sync-credential, read only bystkr-sync.
IAM scopes, Bedrock, region
- IAM — least privilege per function.
stkr-variance: readstkr-counts+stkr-inventory, write variances, invokestkr-cause.stkr-cause:bedrock:InvokeModel+ read only — no inventory write.stkr-approve: conditionalUpdateItemonstkr-inventory+ writestkr-adjustments.stkr-sync: read the secret, writestkr-inventory. No role has more than its step needs. - Bedrock model id.
global.anthropic.claude-haiku-4-5-20251001-v1:0, invoked via Global cross-Region inference. Reachable only bystkr-causeandstkr-summary. - CloudWatch Logs. 7-day retention on every function’s log group.
- AWS Budgets. One monthly budget at $20 with an alarm action.
- Region. Everything in
eu-west-2. No API Gateway, no VPC, no NAT Gateway, no always-on compute.
The whole system, in one breath
- A count lands in S3, an event drives
stkr-capture, and clean lines go tostkr-counts. stkr-variancereconciles against thestkr-inventorysnapshot in plain Python and flags material gaps.stkr-causelabels each gap with one Bedrock call — and cannot touch stock.stkr-approve, behind two Function URLs, is the only thing that posts an adjustment — after a human taps it.- Every action is appended to
stkr-adjustments, so the books are always auditable.
That is the stock count reconciler end to end: a count in, a reconciled and explained set of variances out, and not a single stock quantity changed without a person’s sign-off and a row in the audit trail. It reconciles what you have — deciding what to buy is a different system entirely.
All posts