Conversation
…inputs Three issues found while fact-checking AGENTS.md against the code. 1. DeviceManager.get_devices did a single unpaginated table.scan, so device lists silently truncated once the auth table exceeded 1 MB scanned. Now follows LastEvaluatedKey, matching StorageManager.list_collections. The loop deliberately does not stop on an empty page: DynamoDB caps a page at 1 MB *scanned*, before FilterExpression is applied, so a page of non-device items returns zero Items while more devices remain. The new test covers exactly that case (page 2 is empty on purpose). The scan itself stays — UserCollectionsIndex is the only GSI in the stack and the auth table has none. 2. smithy/models/storage/bso.smithy documented BSO `modified` as "milliseconds since epoch"; the code uses seconds (get_current_timestamp() is round(time.time(), 2)). The wrong description propagated into the generated pydantic model. Left account.smithy's keyRotationTimestamp alone — that one really is milliseconds (accounts store int(time.time() * 1000)). 3. Fixing (2) surfaced why it had gone unnoticed: `./gradlew smithyBuild` reported UP-TO-DATE after the IDL edit and emitted a stale spec. The models live in `models/` (declared via `sources` in smithy-build.json) but the Gradle plugin defaults to `model/` (singular), so the IDL files were never task inputs. The Smithy CLI still read them at execution time, which is why the build worked while being incrementally wrong. Declaring models/ and smithy-build.json as inputs fixes it. Verified: an IDL edit now rebuilds and the change reaches the spec; a no-op run is still UP-TO-DATE. CI never saw this because it builds from a clean checkout. 959 tests pass at 100% coverage; mypy, black, isort and flake8 clean.
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||
Diff for stage: DefaultStageWarning 4 Destructive Changes No Changes for stack: GitHubOidcStack ✅ Diff for stack: Service-prod - 4 to add, 5 to update, 4 to destroy ❌Details
Resources
[-] AWS::ApiGateway::Deployment AuthApiDeploymentB62B2E46bda7a3ee526845c6f831f98a8023195f destroy
[-] AWS::ApiGateway::Deployment TokenApiDeploymentB896C219ee4433fd7a430b8eb183e22b78e32990 destroy
[-] AWS::ApiGateway::Deployment ProfileApiDeployment84A54415750bd55753532dd2ebecd42716cf4155 destroy
[-] AWS::ApiGateway::Deployment StorageApiDeployment3B4929C55affeb30ee76fabc67e717b56801d14f destroy
[+] AWS::ApiGateway::Deployment AuthApiDeploymentB62B2E46ac99c3a69dd45f19958cb37bb317091a
[+] AWS::ApiGateway::Deployment TokenApiDeploymentB896C21940d30031daf269dad58a2f6f3cd50e46
[+] AWS::ApiGateway::Deployment ProfileApiDeployment84A54415225de1b126ba8d19e1f58d99b83b22e1
[+] AWS::ApiGateway::Deployment StorageApiDeployment3B4929C5d19069ae0722651b83088eb0f3753260
[~] AWS::Lambda::Function AuthApiHandlerED50ACFA
├─ [~] Code
│ └─ [~] .S3Key:
│ ├─ [-] 0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f.zip
│ └─ [+] 689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68.zip
└─ [~] Metadata
└─ [~] .aws:asset:path:
├─ [-] asset.0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f
└─ [+] asset.689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68
[~] AWS::Lambda::Function TokenApiHandler2E66DB25
├─ [~] Code
│ └─ [~] .S3Key:
│ ├─ [-] 0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f.zip
│ └─ [+] 689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68.zip
└─ [~] Metadata
└─ [~] .aws:asset:path:
├─ [-] asset.0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f
└─ [+] asset.689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68
[~] AWS::Lambda::Function ProfileApiHandler9B65A298
├─ [~] Code
│ └─ [~] .S3Key:
│ ├─ [-] 0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f.zip
│ └─ [+] 689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68.zip
└─ [~] Metadata
└─ [~] .aws:asset:path:
├─ [-] asset.0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f
└─ [+] asset.689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68
[~] AWS::Lambda::Function ApiHandler5E7490E8
├─ [~] Code
│ └─ [~] .S3Key:
│ ├─ [-] 0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f.zip
│ └─ [+] 689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68.zip
└─ [~] Metadata
└─ [~] .aws:asset:path:
├─ [-] asset.0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f
└─ [+] asset.689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68
[~] AWS::ApiGateway::Stage AuthApiDeploymentStageprodB0E4172A
└─ [~] DeploymentId
└─ [~] .Ref:
├─ [-] AuthApiDeploymentB62B2E46bda7a3ee526845c6f831f98a8023195f
└─ [+] AuthApiDeploymentB62B2E46ac99c3a69dd45f19958cb37bb317091a
[~] AWS::ApiGateway::Stage TokenApiDeploymentStageprod11035AE4
└─ [~] DeploymentId
└─ [~] .Ref:
├─ [-] TokenApiDeploymentB896C219ee4433fd7a430b8eb183e22b78e32990
└─ [+] TokenApiDeploymentB896C21940d30031daf269dad58a2f6f3cd50e46
[~] AWS::ApiGateway::Stage ProfileApiDeploymentStageprodF609D968
└─ [~] DeploymentId
└─ [~] .Ref:
├─ [-] ProfileApiDeployment84A54415750bd55753532dd2ebecd42716cf4155
└─ [+] ProfileApiDeployment84A54415225de1b126ba8d19e1f58d99b83b22e1
[~] AWS::ApiGateway::RestApi StorageApiAD520CAE
└─ [~] Body
└─ [~] .components:
└─ [~] .schemas:
└─ [~] .BasicStorageObject:
└─ [~] .properties:
└─ [~] .modified:
└─ [~] .description:
├─ [-] Last modified timestamp (milliseconds since epoch)
└─ [+] Last modified timestamp (seconds since epoch, 2 decimal places)
[~] AWS::ApiGateway::Stage StorageApiDeploymentStageprodF84F218F
└─ [~] DeploymentId
└─ [~] .Ref:
├─ [-] StorageApiDeployment3B4929C55affeb30ee76fabc67e717b56801d14f
└─ [+] StorageApiDeployment3B4929C5d19069ae0722651b83088eb0f3753260
[~] AWS::Lambda::Function ChannelApiHandler02759D57
├─ [~] Code
│ └─ [~] .S3Key:
│ ├─ [-] 0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f.zip
│ └─ [+] 689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68.zip
└─ [~] Metadata
└─ [~] .aws:asset:path:
├─ [-] asset.0591ee89c558a8ed5fa8f88e4083c55df9860b74fe3dd7ce90ee792698d3a55f
└─ [+] asset.689208ab57a8688fff4b5ec88020862f2f318731385815b8572b1a3e83679c68
No Changes for stack: Frontend-prod ✅ Generated for commit c134c59 at 2026-09-18T21:43:54.312Z |
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.
Three issues found while fact-checking
AGENTS.mdagainst the code (#574). Each is small and independent; the third was only discovered because it prevented the second from taking effect.1.
DeviceManager.get_devicessilently truncated device listsIt did a single unpaginated
table.scan, so once the auth table exceeded 1 MB scanned, devices past that point were dropped with no error. Now followsLastEvaluatedKey, matching the existingStorageManager.list_collectionspattern.The loop deliberately does not stop on an empty page. DynamoDB caps a page at 1 MB scanned — before
FilterExpressionis applied — so a page full of non-device items returns zeroItemswhile more devices remain. Stopping on an empty page would reintroduce the bug in a subtler form. The new test covers exactly that: page 2 is empty on purpose, with devices on pages 1 and 3.The
scanitself stays:UserCollectionsIndexis the only GSI in the stack, and the auth table has none.2. BSO
modifiedwas documented in the wrong unitssmithy/models/storage/bso.smithysaid"Last modified timestamp (milliseconds since epoch)". The code uses seconds —get_current_timestamp()isround(time.time(), 2)— and the wrong description propagated into the generated pydantic model.Left
account.smithy'skeyRotationTimestampalone: that one really is milliseconds (accounts storeint(time.time() * 1000)), so a blanket fix of every "milliseconds" mention would have introduced a new error.3.
./gradlew smithyBuildwas incrementally blind to the IDLFixing (2) surfaced why it had gone unnoticed for so long: after editing the
.smithyfile,./gradlew smithyBuildreported:smithyBuild UP-TO-DATEand emitted a stale spec. My "fix" appeared to do nothing.Root cause: the models live in
models/(declared via"sources": ["models"]insmithy-build.json), but the Gradle plugin's default source dir ismodel/— singular, and nonexistent here. So the IDL files were never task inputs. The Smithy CLI still reads them at execution time, which is why the build produced correct output while being incrementally wrong: any IDL edit was invisible to the up-to-date check.Consequences were real. A stale spec feeds stale pydantic models into
lambda/scripts/codegen.shand a stale OpenAPI document intoSpecRestApi. CI never noticed because it builds from a clean checkout with no prior outputs — so this could only ever be wrong locally, which is the environment nobody verifies.Declaring
models/andsmithy-build.jsonas inputs fixes it. Verified:.smithyfileUP-TO-DATE, stale specUP-TO-DATEUP-TO-DATEVerification
959 passedat 100% coverage (958 + the new pagination test);mypy,black,isort,flake8all clean. The regenerated models were confirmed to carry the corrected description.