Skip to content

feat(lock): add --trust-local to skip remote lock verification#657

Open
yeazelm wants to merge 1 commit into
bottlerocket-os:developfrom
yeazelm:local_validation
Open

feat(lock): add --trust-local to skip remote lock verification#657
yeazelm wants to merge 1 commit into
bottlerocket-os:developfrom
yeazelm:local_validation

Conversation

@yeazelm
Copy link
Copy Markdown
Contributor

@yeazelm yeazelm commented Apr 18, 2026

Description of changes:

When --trust-local is passed (or TWOLITER_TRUST_LOCAL=true is set), twoliter computes a SHA-256 hash of Twoliter.toml and Twoliter.lock before each build. If the hash matches the cached value stored in build/external-kits/.lock-inputs-hash from the last successful verification, remote registry manifest fetches are skipped entirely and the local lockfile is trusted as-is.

When --trust-local is not set (the default), behavior is unchanged: remote verification always runs. The hash is still written after each successful remote verification so the cache is warm for the next --trust-local run.

This is especially useful when developing on a machine outside of EC2 which may have longer round-trip and authentication paths than from within it. For local development, this really speeds up builds since it doesn't do the same costly check over the network when nothing else has changed between builds.

Testing done:
Built the same variant with and without the TWOLITER_TRUST_LOCAL=false set.

  ┌─────────────────────────────────────┬────────┬─────────────────────────────────────────────────────┐                                                    
  │                Test                 │  Time  │                      Behavior                       │                                                    
  ├─────────────────────────────────────┼────────┼─────────────────────────────────────────────────────┤                                                    
  │ Full remote verification (baseline) │ 2m 02s │ TWOLITER_TRUST_LOCAL=false — hits ECR for each kit  │                                                    
  ├─────────────────────────────────────┼────────┼─────────────────────────────────────────────────────┤                                                    
  │ Fast path, unchanged inputs         │ 19.6s  │ Hash matched → skipped ECR entirely                 │                                                    
  ├─────────────────────────────────────┼────────┼─────────────────────────────────────────────────────┤                                                    
  │ touch only (mtime change)           │ 17.9s  │ Content unchanged → hash still matched → still fast │                                                    
  ├─────────────────────────────────────┼────────┼─────────────────────────────────────────────────────┤                                                    
  │ Content change to Twoliter.toml     │ 1m 50s │ Hash mismatch → full remote check ran, hash updated │                                                    
  └─────────────────────────────────────┴────────┴─────────────────────────────────────────────────────┘    

Terms of contribution:

By submitting this pull request, I agree that this contribution is dual-licensed under the terms of both the Apache License, version 2.0, and the MIT license.

@jmt-lab
Copy link
Copy Markdown
Contributor

jmt-lab commented Apr 27, 2026

Hey Matt, long time no see :P. So this is good but i think we can actually do this implicitly, I'm not sure if we even need to gate this behind an argument. Here is what i'm thinking might be a better approach for this:

  • During twoliter fetch which fetches the kits, (what fetches to external kits) in addition to fetching the layers we fetch the oci index/manifest and hash it the same way we do in Twoliter.lock and store it inside external-kits/bottlerocket-core-kit/digest. We do the same with the sdk
  • Then during twoliter lock resolution outside of update flow we check for each requirement sdk, and kits we check for the digest and compare the digest in Twoliter.lock against the digest in the build/ directory. If they match we don't need the remote check.
  • I don't think we need to gate this behind an argument either. We already have a hash digest check betwen Twoliter.toml and Twoliter.lock. The hash in Twoliter.lock root is a hash of only the components in Twoliter.toml that would affect lock invalidation (ie the kit and sdk dependencies). As such i think if we go with the above flow we can forgoe needing a new flag.

Let me know if you'd like to do these updates or I can make the changes myself as well.

During lock resolution (outside of `twoliter update`) each artifact's stored
digest is compared to the digest in Twoliter.lock. If all match the remote
registry check is skipped entirely; if any mismatch or file is missing a full
remote re-resolution runs and the digest files are refreshed.

Signed-off-by: Matthew Yeazel <matthew.yeazel@gmail.com>
@yeazelm
Copy link
Copy Markdown
Contributor Author

yeazelm commented Apr 29, 2026

Sounds good, I can shift towards that pattern!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants