fix(crafter): use deterministic filename for AI config collector#2943
fix(crafter): use deterministic filename for AI config collector#2943waveywaves wants to merge 1 commit intochainloop-dev:mainfrom
Conversation
486591a to
cb22854
Compare
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pkg/attestation/crafter/collector_aiagentconfig_test.go">
<violation number="1" location="pkg/attestation/crafter/collector_aiagentconfig_test.go:59">
P3: Test hardcodes a Unix-style path and full-path string equality, but aiConfigTempFilePath uses filepath.Join, which is OS-dependent. This assertion will fail on Windows even when the implementation is correct.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| path := aiConfigTempFilePath(testTempDir, testAgent, testConfigHash) | ||
|
|
||
| require.Contains(t, path, testConfigHash, "path must contain the full config hash (not truncated)") | ||
| assert.Equal(t, testTempDir+"/ai-agent-config-"+testAgent+"-"+testConfigHash+".json", path) |
There was a problem hiding this comment.
P3: Test hardcodes a Unix-style path and full-path string equality, but aiConfigTempFilePath uses filepath.Join, which is OS-dependent. This assertion will fail on Windows even when the implementation is correct.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/attestation/crafter/collector_aiagentconfig_test.go, line 59:
<comment>Test hardcodes a Unix-style path and full-path string equality, but aiConfigTempFilePath uses filepath.Join, which is OS-dependent. This assertion will fail on Windows even when the implementation is correct.</comment>
<file context>
@@ -0,0 +1,61 @@
+ path := aiConfigTempFilePath(testTempDir, testAgent, testConfigHash)
+
+ require.Contains(t, path, testConfigHash, "path must contain the full config hash (not truncated)")
+ assert.Equal(t, testTempDir+"/ai-agent-config-"+testAgent+"-"+testConfigHash+".json", path)
+ })
+}
</file context>
cb22854 to
449088d
Compare
Replace os.CreateTemp (which generates a random suffix) with a deterministic filename derived from the config's content hash. Root cause: although AddMaterialContractFree uses a deterministic map key (Materials[m.Name]), the Artifact struct embedded in each material gets its Name and Digest from fileStats(), which calls os.Stat(filepath).Name(). A random temp filename therefore produces a different Artifact.Name on every call — even when the JSON payload is byte-for-byte identical — causing the CAS to treat each retry as a new, distinct resource. Changes: - Extract aiConfigTempFilePath() helper for deterministic path generation - Use full ConfigHash (no truncation) to avoid collision risk - Write into a private os.MkdirTemp directory (symlink-safe) instead of the shared os.TempDir() - Add unit tests proving same-input-same-output and different-input- different-output invariants Fixes: chainloop-dev#2907 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
449088d to
c7e366e
Compare
|
@jiparis @migmartri gentle ping — this is ready for review. CI is green except for the known flaky |
Summary
os.CreateTemp(random suffix) with deterministic filenames in bothcollector_aiagentconfig.goandcollector_prmetadata.go, eliminating duplicate CAS uploads on attestation retriesos.TempDir()with deterministic names anddefer os.RemovecreatePRMetadataTempFilehelper — inlined at the call site with explanatory commentRoot Cause Analysis
The reviewer questioned whether the filename is actually the cause of duplicates since
Materials[m.Name]already uses a deterministic key. The answer is yes — the map key is deterministic, but the Artifact struct inside the material is not.Here is the chain of causation:
uploadAgentConfig()callsos.CreateTemp("", "ai-agent-config-claude-*.json"), which produces a random filename like/tmp/ai-agent-config-claude-3847291.jsonAddMaterialContractFree()callsaddMaterial(), which callsuploadAndCraft()uploadAndCraft()callsfileStats(artifactPath), which doesos.Stat(filepath).Name()to get the basename of the temp fileArtifact.Namein the material proto (line 136 ofmaterials.go)fileStats()also computessha256(file_contents)which becomesArtifact.DigestBecause the JSON payload is identical across retries,
Artifact.Digeststays the same. ButArtifact.Namechanges every time (random temp suffix), so the CAS sees a different resource name on each retry. The map keyMaterials["ai-agent-config-claude"]is overwritten (good), but each retry still triggers a new CAS upload because the artifact metadata differs.The fix makes the filename deterministic by deriving it from a content hash, so
Artifact.Nameis stable across retries.Changes
pkg/attestation/crafter/collector_aiagentconfig.go:os.CreateTemp+ manual write/close withos.WriteFileto a deterministic path inos.TempDir()ConfigHash(no truncation)aiConfigTempFilePath()helper andMkdirTempwrapperpkg/attestation/crafter/collector_prmetadata.go:createPRMetadataTempFile()helper (which usedos.CreateTempwith random suffix) with inline deterministic path usingsha256(content)os.WriteFile+defer os.Removeinos.TempDir()Removed test files:
collector_aiagentconfig_test.go— tested the removedaiConfigTempFilePathhelpercollector_prmetadata_test.go— tested the removedcreatePRMetadataTempFilehelperTest Plan
go vet ./pkg/attestation/crafter/...passesgo test ./pkg/attestation/crafter/...— all sub-packages passFixes #2907