Repository navigation
Conversation
Adds the public-orders Lambda behind publicGetOrderOffer: GSI locate -> strongly consistent profile confirm -> hmac.compare_digest token check -> anchor campaign GetItem -> catalog GetItem -> allowlist intersection, with QR keys pre-signed from the stored key explicitly. New scoped lambda_public_orders_execution role (read-only on profiles/campaigns/catalogs/accounts, no orders access, plus s3:GetObject on payment-qr-codes/* for the pre-signed buyer GETs), wired as a unit resolver through lambda_unit_resolver.js in all three environments.
…Terraform assertions
This branch was successfully deployed
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.
Intent
"we're building this" — the public order placement feature described in
data/KW-PUBLIC-ORDERS/spec.md. This slice builds the first thing a buyer ever touches: the anonymous read that turns a share URL into a real page. Given a bare profile id and the token from the URL, it answers what the seller is offering — who they are, which campaign, the catalog to pick from, and the payment methods they allowed — including a short-lived presigned image for each method that has a QR code. No login, no account, no money.The spec is the design of record: read §5.3 (public read), §7, §9, §10.2 and §11 before you start. Two measured facts are settled and must not be re-derived: an object type reachable from a key-only field must itself carry the key directive or its fields are denied to the buyer caller, and AppSync's auth directives are an exclusive per-field allow-list — a signed-in caller is refused on a key-only field. The six public types and their markers already exist from the schema slice; you are writing the handler behind one of them.
What Changed
src/handlers/public_orders_offer.py, the handler behind thepublicGetOrderOfferpublic field: given a bare profile id and URL token it locates the profile throughprofileId-index(GSI as locator only, strongly consistent re-reads to disambiguate a transfer projection), confirms the row with a consistent base-table GetItem, compares the token withhmac.compare_digest, then GetItems the anchor campaign by canonical id, its catalog, and the owner account'spreferences.paymentMethods— answering an identicalNOT_FOUNDon every negative branch, intersecting the seller's allowlist with stored methods case-insensitively, and pre-signing short-lived QR image URLs (with token/S3-key log redaction enforced at the handler boundary).lambda_unit_resolver.js) on a newPublicOrdersDSLambda datasource, register thepublic-ordersLambda function across dev/ephemeral/prod, and add a scopedlambda_public_orders_executionIAM role (read-only GetItem/Query on profiles/campaigns/catalogs/accounts, deliberately no orders access, pluss3:GetObjectonpayment-qr-codes/*for the buyer's pre-signed GETs) with matching ephemeral recovery-import lines.lambda_unit_resolverfield table and a refactor of the public-settings wiring helpers), and updatedocs/SCHEMA.mdandAGENTS.mdto reflect the landed offer read.Risk Assessment
✅ Low: The offer read is tightly scoped (read-only domain role, uniform NOT_FOUND across all negative branches, constant-time token compare, explicit-key pre-signing with zero S3 calls, token/key scrubbed from every log surface) and is pinned by behavioral moto tests plus static IAM and schema-directive contract tests; only minor informational items were found.
Testing
Built a disposable moto-backed local AWS stack and drove the real resolver/pipeline code, the real lambda_unit_resolver.js, and the real public_orders_offer Lambda handler through 11 live scenarios (share-URL offer with a fetched pre-signed QR image, identical-NOT_FOUND probing, ambiguous-projection and anchor/catalog guards, allowlist intersection, disable/re-enable token stability, owner-only gate, enable validation, first-enable race CONFLICT, log hygiene), plus live runs of the integration-env CLI for both API-key present/absent stacks — all passed — corroborated by 170 targeted unit-test passes; the real AppSync auth layer and the not-yet-emitted cap error could not be driven live (expired AWS session / no emitting path on this branch) and are reported as untested.
publicGetOrderOfferentry with identity: null) and transcripts/qr_fetch.txt (HTTP 200, 70 bytes, PNG magic, Expires ≈ now+900s); scenario_results.json scenari…post-transfer-ambiguous-profile-projection-is-not-found-not-a-guess+ 'Ambiguous profileId-index projection' WARNING in transcripts/offer_lambda_stdout.txtinactive-or-dangling-anchor-and-soft-deleted-catalog-block-the-offer(6 checks) with live_calls.json probe entriesallowlist-intersection-legacy-qr-url-and-empty-lists-still-succeed(6 checks); allowlist changes made through the real settings pipelinedisable-revokes-the-offer-but-keeps-the-share-token-re-enable-restores-it(8 checks)owner-only-settings-gate-refuses-collaborator-stranger-and-unknown-identically(8 checks) + FORBIDDEN entries in live_calls.jsonenabling-rejects-missing-campaign-foreign-campaign-inactive-catalog-no-methods-no-ack(8 checks), ending with profile B still never-enabledconcurrent-first-enable-second-writer-gets-conflict-not-double-mint(3 checks); writer2's conditioned UpdateItem fails on attribute_not_exists(publicOrders.#token) exactly as d…logs-never-contain-the-share-token-or-the-qr-object-key(5 checks) over transcripts/offer_lambda_stdout.txtaws sts get-caller-identityfails with 'Your session has expired. Please reauthenticate using aws login' — re-authentication is interactive and writes user-level cre…Evidence: Live validation report (scenarios, harness description, untested reasons, F2 acceptance)
Evidence: All recorded GraphQL request/response transcripts (46 calls: 20 offer queries, 26 settings pipeline calls)
~/.no-mistakes/evidence/01M46KEY6RQ3MS0XE2K417MGTA/transcripts/scenario_results.json)Evidence: Pre-signed QR URL fetch evidence (HTTP 200, PNG bytes, ~900s expiry)
Evidence: Captured Lambda handler log output across all offer calls (token/key absence evidence)
Evidence: All 11 negative offer probes returning one identical error
Evidence: generate_integration_env.py live CLI runs (API-key present and absent, plus --check)
Evidence: Targeted pytest run 1 (69 passed, 1 skipped)
Evidence: Targeted pytest run 2 (101 passed)
Evidence: AppSync JS settings-resolver node tests (79 passed)
~/.no-mistakes/evidence/01M46KEY6RQ3MS0XE2K417MGTA/harness/driver.mjs)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
tofu/application/modules/iam/main.tf:761- The public-orders role grants dynamodb:Query on the campaigns table plus its GSI (locals at lines 727-729), but the offer handler never issues a campaign Query - the behavioral scope test (tests/unit/test_public_orders_role_scope.py) pins the exact call sequence to a single profiles Query plus four GetItems. The same file's own orders-access comment states 'unused permissions on a domain role are a liability', which this grant contradicts; it is read-only and the role is single-purpose, so exposure is bounded, but the grant exists only for a hypothetical future slice. Sibling axis: the s3:GetObject grant (line 783) IS load-bearing (pre-signed GETs authorize against the signing role), so only the campaigns Query is surplus.src/utils/errors.py:62- PUBLIC_ORDER_LIMIT_EXCEEDED is registered here and mapped in frontend/src/lib/apollo.ts:207, but no code path on this branch emits it - the publicCreateOrder write slice that owns the per-campaign cap is a later slice. Deliberate forward placement (both sides pinned by tests so the code cannot drift out of sync), currently dead until that slice lands.tests/unit/test_public_orders_role_scope.py:340- Newly added source-content-only assertions over Terraform source: test_public_orders_is_mapped_to_the_role_in_all_three_environments raw-substring-matches '"public-orders" = module.iam.lambda_public_orders_execution_role_arn' against each environment main.tf, and tests/unit/test_public_settings_pipeline_wiring.py:46/:58 locate the owner gate's data_source/code with regex over resource blocks. Both files otherwise parse the same artifacts semantically with python-hcl2, and test_monolithic_role_retirement.py:178 already covers the env role-map wiring via an hcl2 parse that extracts function keys dynamically - so the substring test is both fragile and redundant. Refine to the hcl2 parse (or drop the env-mapping test in favor of the existing dynamic coverage).🔧 Fix applied.
3 infos still open:
tofu/application/modules/iam/main.tf:761- The public-orders role grants dynamodb:Query on the campaigns table plus its GSI (locals at lines 727-729), but the offer handler never issues a campaign Query - the behavioral scope test (tests/unit/test_public_orders_role_scope.py) pins the exact call sequence to a single profiles Query plus four GetItems. The same file's own orders-access comment states 'unused permissions on a domain role are a liability', which this grant contradicts; it is read-only and the role is single-purpose, so exposure is bounded, but the grant exists only for a hypothetical future slice. Sibling axis: the s3:GetObject grant (line 783) IS load-bearing (pre-signed GETs authorize against the signing role), so only the campaigns Query is surplus.src/utils/errors.py:62- PUBLIC_ORDER_LIMIT_EXCEEDED is registered here and mapped in frontend/src/lib/apollo.ts:207, but no code path on this branch emits it - the publicCreateOrder write slice that owns the per-campaign cap is a later slice. Deliberate forward placement (both sides pinned by tests so the code cannot drift out of sync), currently dead until that slice lands.tests/unit/test_public_orders_role_scope.py:340- Newly added source-content-only assertions over Terraform source: test_public_orders_is_mapped_to_the_role_in_all_three_environments raw-substring-matches '"public-orders" = module.iam.lambda_public_orders_execution_role_arn' against each environment main.tf, and tests/unit/test_public_settings_pipeline_wiring.py:46/:58 locate the owner gate's data_source/code with regex over resource blocks. Both files otherwise parse the same artifacts semantically with python-hcl2, and test_monolithic_role_retirement.py:178 already covers the env role-map wiring via an hcl2 parse that extracts function keys dynamically - so the substring test is both fragile and redundant. Refine to the hcl2 parse (or drop the env-mapping test in favor of the existing dynamic coverage).✅ **Test** - passed
✅ No issues found.
publicGetOrderOfferentry with identity: null) and transcripts/qr_fetch.txt (HTTP 200, 70 bytes, PNG magic, Expires ≈ now+900s); scenario_results.json scenari…post-transfer-ambiguous-profile-projection-is-not-found-not-a-guess+ 'Ambiguous profileId-index projection' WARNING in transcripts/offer_lambda_stdout.txtinactive-or-dangling-anchor-and-soft-deleted-catalog-block-the-offer(6 checks) with live_calls.json probe entriesallowlist-intersection-legacy-qr-url-and-empty-lists-still-succeed(6 checks); allowlist changes made through the real settings pipelinedisable-revokes-the-offer-but-keeps-the-share-token-re-enable-restores-it(8 checks)owner-only-settings-gate-refuses-collaborator-stranger-and-unknown-identically(8 checks) + FORBIDDEN entries in live_calls.jsonenabling-rejects-missing-campaign-foreign-campaign-inactive-catalog-no-methods-no-ack(8 checks), ending with profile B still never-enabledconcurrent-first-enable-second-writer-gets-conflict-not-double-mint(3 checks); writer2's conditioned UpdateItem fails on attribute_not_exists(publicOrders.#token) exactly as d…logs-never-contain-the-share-token-or-the-qr-object-key(5 checks) over transcripts/offer_lambda_stdout.txtaws sts get-caller-identityfails with 'Your session has expired. Please reauthenticate using aws login' — re-authentication is interactive and writes user-level cre…REPO=$PWD .venv/bin/python <evidence>/harness/stack.py— disposable moto ThreadedMotoServer with real table schemas + S3 QR objects, torn down after the runnode --import <evidence>/harness/loader.mjs <evidence>/harness/driver.mjs <evidence> <repo>— 11 live scenarios, 60 checks, all passing; output intranscripts/scenario_results.jsonandtranscripts/live_calls.json(20 offer queries, 26 settings pipeline calls)Pre-signed QR URL fetched over HTTP from local S3 inside the driver →HTTP 200, 70 bytes, PNG magic: true(transcripts/qr_fetch.txt)Log-hygiene scan over all captured Lambda subprocess output →transcripts/offer_lambda_stdout.txt(9Public order offer servedlines, zero token/key occurrences,Generated GET URLsuppressed)uv run pytest tests/unit/test_public_orders_handlers.py tests/unit/test_public_settings_pipeline_wiring.py tests/unit/test_public_orders_role_scope.py tests/unit/test_public_api_key_surface.py tests/unit/test_errors.py -q --no-cov→ 69 passed, 1 skippeduv run pytest tests/unit/test_lambda_unit_resolver_wiring.py tests/unit/test_generate_integration_env.py tests/unit/test_ephemeral_reliability.py -q --no-cov→ 101 passednode --import ./register-loader.mjs --test verify_public_settings_owner_fn.test.js lookup_public_settings_campaign_fn.test.js validate_public_settings_write_fn.test.js validate_public_settings_catalog_fn.test.js write_public_order_settings_fn.test.js get_profile_public_order_settings_pipeline_resolver.test.js update_profile_public_order_settings_pipeline_resolver.test.js→ 79 passed, 0 faileduv run python scripts/generate_integration_env.py --outputs-json <fixture> --out <file> --frontend-out <file>and the same with--check, once withappsync_api_keyin the outputs and once without →transcripts/generate_integration_env_live.txtaws sts get-caller-identity→ 'Your session has expired. Please reauthenticate using 'aws login'' (documented blocker for the real-AppSync scenario)src/utils/errors.py:62- Recorded acceptance (F2): PUBLIC_ORDER_LIMIT_EXCEEDED is deliberately registered in ErrorCode and mapped in frontend/src/lib/apollo.ts ahead of the publicCreateOrder write slice that will emit it; both sides are pinned by tests so they cannot drift. Dead until that slice lands, by design — no change made.tests/unit/test_appsync_pipeline_functions.py:1- Pre-existingruff format --checkdrift on six test files untouched by this change (test_appsync_pipeline_functions.py, test_appsync_resolver_order.py, test_except_syntax.py, test_exports_bucket_lifecycle.py, test_list_unit_catalogs_env_isolation.py, test_waf_managed_rule_action_rollout.py). CI gates onlyruff check, which passes. Left alone as out of scope; a repo-wideruff format src/ tests/pass would clear them.✅ **Push** - passed
✅ No issues found.