Sync with InsForge-sdk-js v1.5.0 - #18
Conversation
Sync with InsForge-sdk-js v1.5.0 (InsForge/InsForge#1760): - upload(path, data): uploading to an existing key now replaces the object in place (standard PUT create-or-replace). The behavior change is server-side; docs updated accordingly. - uploadWithAutoKey(...): the backend no longer mints keys, so the key is now generated client-side (sanitized base + timestamp + random suffix) and uploaded through the standard upload() path, keeping repeated uploads of the same file collision-free. The POST /objects auto-key request path is removed. Requires an InsForge backend that includes the standard-PUT storage change. API signatures are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review: Sync with InsForge-sdk-js v1.5.0
Summary: A clean, well-scoped port of the JS v1.5.0 storage change — standard PUT create-or-replace semantics plus client-side auto-key generation — with matching MockEngine unit tests; no blocking issues found.
Requirements context
No /docs/superpowers/ (or docs/specs/) directory exists in this repo, and there is no .claude/skills/ folder — no matching spec/plan found; assessed against the PR description, the linked JS release (InsForge-sdk-js v1.4.5…v1.5.0), and the companion backend change (InsForge/InsForge#1760). I fetched the JS v1.5.0 src/modules/storage.ts and compared its generateObjectKey line-by-line against the Kotlin port to validate the "mirrors JS exactly" claim.
Findings
Critical
(none)
Suggestion
- Software engineering / test coverage —
BucketApi.kt:342-346:uploadWithAutoKeynow forwardscontentType/upsert/metadatathrough toupload(). This is new wiring, but no unit test asserts those options actually reach the PUT request (thex-upsert/x-metadataheaders), nor is there coverage of theuploadWithAutoKey(file: java.io.File)overload (BucketApi.kt:372-392). A silent regression in option-forwarding wouldn't be caught. The added tests are otherwise thorough; a small assertion on forwarded headers would close the gap.
Information
- Functionality —
BucketApi.kt:716-718: the extension (ext) is taken verbatim from the filename and is not sanitized before being concatenated into the object key (and thus into the request URL path inuploadDirect,BucketApi.kt:400). A filename likea.b cor one with a separator in the suffix would flow through unescaped. This is an exact parity match with the JS reference (ext = filename.slice(dotIndex), also unsanitized), so it is intentional and low blast radius — noting only for awareness. - Functionality —
BucketApi.kt:722-724: the random suffix is always exactly 6 chars drawn fromRandom.Defaultover[0-9a-z], whereas the JS source isMath.random().toString(36).slice(2, 8), which yields up to 6 chars (can be shorter when the value has trailing zeros). Functionally equivalent for collision-avoidance (both non-cryptographic, both paired with a millisecond timestamp), so "mirrors exactly" is accurate in spirit — just a minor, harmless divergence in the suffix distribution.
Dimension coverage
- Software engineering — New
StorageUploadUnitTestruns in the plaintesttask (not@Tag("integration")), matching the CI split inbuild.gradle.kts:114-138;ktor-client-mock:2.3.7is already atestImplementationdep. Coverage of the new behavior is solid (key format, sanitization, truncation, fallback, collision-freedom, exact-key PUT routing, no-POST /objects, empty-data rejection). The removedrequire(data.isNotEmpty())inuploadWithAutoKeyis correctly preserved via the delegatedupload()(BucketApi.kt:310) and is regression-tested. Scope is tight — two files, no drive-by changes. - Functionality — The implementation matches the JS v1.5.0 contract and backend #1760: server-side minting (
POST /objects/uploadDirectAutoKey) is fully removed, auto-key routes throughupload()which now correctly handles both DIRECT (PUT) and PRESIGNED (S3) backends — a slight improvement over the old auto-key path. Content-type detection still uses the original filename (BucketApi.kt:343) before the sanitized key. Public API signatures unchanged; sample apps and the existing integration test remain valid. Theupsertheader no-op is documented. - Security — No security-relevant changes. No secrets/tokens/PII logged or newly returned. Object keys are not a security boundary here (access is auth-gated), so the non-crypto RNG is fine. No new dependencies.
- Performance — No new N+1 queries, hot-path loops, or blocking I/O. The auto-key path issues the same single
getUploadStrategycall as before;generateObjectKeyis trivial.
Verdict
approved — zero Critical findings. The two Suggestion/Information items are non-blocking. (Informational verdict for the bot report; explicit GitHub approval remains a human action.)
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="src/main/kotlin/dev/insforge/storage/BucketApi.kt">
<violation number="1" location="src/main/kotlin/dev/insforge/storage/BucketApi.kt:342">
P3: uploadWithAutoKey no longer validates that data is non-empty before generating the object key, so a rejected empty-data call still pays the cost of timestamp/random key generation before failing inside upload(). Low impact, but an early require(data.isNotEmpty()) would avoid the wasted work and make the failure path clearer.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // Auto-key generation is a client-side convenience — the storage API | ||
| // has no server-side key minting — so mint a unique, collision-free | ||
| // key here and upload through the standard upload() path. | ||
| return upload(generateObjectKey(filename), data) { |
There was a problem hiding this comment.
P3: uploadWithAutoKey no longer validates that data is non-empty before generating the object key, so a rejected empty-data call still pays the cost of timestamp/random key generation before failing inside upload(). Low impact, but an early require(data.isNotEmpty()) would avoid the wasted work and make the failure path clearer.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/main/kotlin/dev/insforge/storage/BucketApi.kt, line 342:
<comment>uploadWithAutoKey no longer validates that data is non-empty before generating the object key, so a rejected empty-data call still pays the cost of timestamp/random key generation before failing inside upload(). Low impact, but an early require(data.isNotEmpty()) would avoid the wasted work and make the failure path clearer.</comment>
<file context>
@@ -326,25 +334,15 @@ internal class BucketApiImpl(
+ // Auto-key generation is a client-side convenience — the storage API
+ // has no server-side key minting — so mint a unique, collision-free
+ // key here and upload through the standard upload() path.
+ return upload(generateObjectKey(filename), data) {
+ contentType = uploadOptions.contentType ?: detectContentType(filename)
+ upsert = uploadOptions.upsert
</file context>
Ports the storage changes from InsForge-sdk-js v1.5.0 (standard PUT create-or-replace semantics, paired with InsForge/InsForge#1760).
Ported
upload(path, data)/upload(path, file)— standard PUT semantics: uploading to an existing key now replaces the object in place (previously the server silently auto-renamed the key). This is a server-side behavior change; the method signatures are unchanged, KDoc updated to document the new contract.uploadWithAutoKey(filename, data)/uploadWithAutoKey(file)— the backend no longer mints keys server-side, so the unique, collision-free key is now generated client-side (<sanitized-base>-<timestamp>-<random><ext>, mirroring the JSgenerateObjectKeylogic exactly:[^a-zA-Z0-9-_]→-, 32-char base cap,filefallback) and uploaded through the standardupload()path. The deadPOST /objectsserver-minting request path (uploadDirectAutoKey) is removed. Public API signatures unchanged.StorageUploadUnitTest(MockEngine-based, runs in the plaintesttask) mirroring the new JS unit tests: exact-key PUT routing, client-minted key format/uniqueness, no fallback to the removedPOST /objectsendpoint, empty-data rejection, plus direct coverage ofgenerateObjectKey(extension preservation, sanitization, truncation, fallback, collision-freedom).Skipped
vitestmocks, workflow YAML tweaks,package.json/lockfile version bump) — JS-only concerns.SDK-REFERENCE.mdedit — the Kotlin repo has no equivalent reference doc; the contract note went into the KDoc instead.upsertoption is left as-is (it predates this change and remains API-compatible; with standard PUT semantics the header is now effectively a no-op server-side).Notes
uploadand is unsupported.Test results
./gradlew test(unit suite, integration-tagged tests excluded as configured): BUILD SUCCESSFUL — all tests pass, including the 9 new storage tests. (No JDK was on the machine; ran with a locally downloaded Temurin 17.)integrationTestrequires a live InsForge backend and was not run; the existingtest upload file with auto-generated keyintegration test remains valid under the new client-side key minting.🤖 Generated with Claude Code
Summary by cubic
Syncs storage behavior with InsForge SDK JS v1.5.0 by adopting standard PUT create-or-replace semantics and moving auto-key generation to the client. Adds unit tests to cover the new behavior.
New Features
upload(path, ...)now replaces an existing object at the same key (standard PUT).uploadWithAutoKey(...)mints a unique key client-side (<sanitized-base>-<timestamp>-<random><ext>) and uploads viaupload(). The server-side auto-keyPOST /objectspath is removed.generateObjectKeyadded to mirror JS logic. New MockEngine unit tests verify exact-key PUT routing, key format/uniqueness, and empty-data rejection.Migration
upsertoption remains but is effectively a no-op server-side under standard PUT.Written for commit e265813. Summary will update on new commits.