A build declares both an internal package index and a public index as sources - what does that cost hermeticity?
answer
- your file names which, not where
- the input set is not fully declared
- one resolvable endpoint, everything in it
- a miss must fail, never fall back
- record source and hash per component
basics
~20 sTwo declared sources mean the origin of a component is decided at build time by whichever index answers, not by your declaration. Hermeticity wants exactly one resolvable source, no fallback on a miss, and a build record naming the source and hash for each resolved component.
solid answer
~60 sDeclaring two sources makes the build's inputs a function of the world's state rather than of your configuration. Your declaration names `acme-risk-utils`; which index served it depends on availability, ordering and precedence at that moment, and none of that is written down anywhere you review. The hermetic shape is one resolvable source per build: a single endpoint that carries everything the build may consume, internal components and reviewed copies of external ones alike, with no second lookup when a name is missing. A miss must be a build failure, because a fallback is precisely the moment when an input arrives from somewhere nobody declared. Then make the origin provable rather than assumed: the lock record should carry, per component, the source it was served from and a content hash, and the build's record of resolved artifacts should list those digests so you can confirm each one exists in that single index. A package URL can carry a `repository_url` qualifier for the same purpose. Without that, "it came from our index" is a belief, not evidence.
code
json · 16 lines{
"packages": [
{
"name": "acme-risk-utils",
"version": "2.4.1",
"source": "https://packages.internal.example/simple",
"hashes": ["sha256:9f2c...e41a"]
},
{
"name": "vendor-http",
"version": "5.0.2",
"hashes": ["sha256:1b7d...c003"]
}
...
]
}go deeper
Know that a build should have one place it is allowed to get components from, and that pointing it at two means you cannot say for certain where a given component came from.
Explain why two sources make the input set incompletely declared, and why a missing name must fail the build rather than trigger a second lookup.
Show how you prove origin from the build's own output - source and content hash per component, digests checked against the one index - rather than asserting it from a configuration file.
Own the operational cost: a single source means someone must accept and mirror external components, and you need a story for how that stays fast enough that teams do not reintroduce a second source to get unblocked.
## The property that breaks Hermeticity says the build's inputs are declared and pinned. Declaring two sources for the same namespace quietly moves part of that decision out of your configuration and into the runtime state of two servers. Your file says which *names* you want. It does not say which *place* will answer for a given name on a given day. Which one wins in what circumstances is a property of the resolver's configuration and of how the two indexes are wired together - a subject of its own - but for hermeticity the relevant fact is simpler: the answer is not determined by your declaration alone, so the input set is not fully declared. The consequence is concrete. Two builds of the same commit can consume components with different content. An internal name that is absent from the internal index for a moment - a publish in progress, a brief outage, a typo in a name - can be answered by the other source. And after the fact, nothing in the build's output tells you which source served what, so an investigation ends in speculation. ## What "one declared upstream" means operationally It does not mean giving up external dependencies. It means exactly one endpoint is resolvable from the build, and that endpoint carries everything the build is allowed to use: - internal components published there directly; - external components you have deliberately accepted, held there as copies; - nothing else. The critical part is the behaviour on a miss. If a name is not present, the build fails. It does not consult a second location, and it does not fall back to a wider search. A fallback is not a resilience feature in this context; it is the one path by which an undeclared input can enter, and it fires exactly when something is unusual. The surrounding discipline is ordinary configuration hygiene: no per-developer or per-job override that adds a second source, no environment variable that appends one, no tool-specific configuration file inherited from a base image that reintroduces the default public source. It is common to find a carefully scoped project configuration undone by a user-level file on the runner. ## Proving where a component actually came from A policy nobody can verify is a hope. Three artifacts of the build make the origin checkable: **The lock record.** A per-component entry that carries not only name and version but the source it was served from and a content hash. When the source field is present for some components and absent for others, that is itself the finding: the ones without it were resolved from wherever, and you cannot say where. **A package URL with an explicit repository.** A package URL identifies a component by ecosystem, name and version, and can carry a `repository_url` qualifier naming the index it came from. That turns "which repository" into part of the identifier rather than an assumption. **The build's record of resolved artifacts with digests.** Given a digest per resolved component, you can check that each one exists in your single index with that exact content. That check is mechanical and it is the difference between evidence and belief. With those in place, the question "did anything in this release come from outside our index?" has a yes-or-no answer computed from the build's own output, and the asset you are protecting - the integrity of the artifact you ship - is defended by something you can run rather than by a configuration file you hope nobody edited. ## A useful test to propose Make the build fail closed and prove it: point the build at the single index, remove every other source, and run it with all other network destinations denied. If it succeeds, the single source really does carry everything. If it fails, you have just learned which components were arriving from somewhere you had not declared - which is exactly the list you needed.
- Why must a missing name fail the build rather than fall back to another source?Because the fallback fires precisely in the abnormal case - a publish in flight, a brief outage, a mistyped name - and that is when an input arrives from somewhere nobody declared. A hard failure is loud, instantly diagnosable and safe; a fallback is silent and produces an artifact whose contents you cannot account for afterwards.
- How would you prove that nothing in last month's release came from outside your index?Take the build's record of resolved components with their digests, and check each digest exists in that index with exactly that content. Where the lock record also carries a source per component, the absent ones are your gap list. If neither record exists for that build, the honest answer is that you cannot prove it, and the fix is to start recording it now.
- A project's configuration declares one source but builds still pull externally. Where do you look?At everything layered around the project file: a user-level configuration on the runner, an environment variable that appends a source, a setting baked into the base image, a tool wrapper with its own default, and per-job overrides. Scoped project configuration is routinely undone by an inherited file nobody reads, so test by denying every other destination and seeing whether the build still succeeds.
saying these in an interview costs you the question
- Assumes the internal index always wins over a public one
- Treats a public fallback as a resilience feature
- Claims origin from configuration rather than build evidence
- Records versions in a lockfile but no hashes or source
- Overlooks runner-level configuration that adds a second source