Why: an unset S3 client passed through the Store interface is not a nil interface, so uploads panic instead of using the local artifact directory.
Evidence: TestIsNil and TestFinishOpenTypedNilS3UsesLocalArtifacts failed before this change and pass after it. The PostgreSQL case skips unless AGENT_PG_TEST_DSN is set.
Impact: only an unset blob store falls back; a constructed S3 or local store is unchanged.
Refs: internal/blobstore/blobstore.go internal/store/store.go
Co-authored-by: Cursor <cursoragent@cursor.com>
Why: passwordless activation/recovery must redeem only into a
set-password session; account ops must not create empty stores.
Evidence: SELF go test store/server login-key and OpenExisting cases.
Impact: auth middleware gates; CLI user create defaults to activate key.
Refs: docs/svc-deploy/u4-one-time-login-key.md
Co-authored-by: Cursor <cursoragent@cursor.com>
Why: plan U5 remainder (KTD5/KTD9 + exit criteria) - commands must commit atomically with business writes, artifacts need a shared object store for multi-instance, and the two-real-process acceptance matrix must run in CI.
Evidence: full suite green on SQLite and real PostgreSQL 18.6 (35 packages each tier) - outbox tx test (rollback removes the command, commit makes both visible, idempotent re-enqueue), S3 store tests (SigV4-signed round trip, 404 sentinel, idempotent delete, presigned GET, env fallback), real-process acceptance (testbinary self-spawn of two serve processes with per-process identity and command consumers against one schema: same-conversation contention, cross-instance cancel command claimed exactly once, stale-owner settle fenced with run unchanged).
Impact: EnqueueCommandInTx for in-transaction command outbox and AppendEvents now notifies in the same transaction (KTD6 front half); blobstore.S3 (SigV4, presign, prefix isolation) injected from BLOBSTORE_S3_* env when configured, local fallback otherwise; realproc_test provides the process-level multi-instance acceptance gate (PG-env gated, runs in CI test-pg job).
Refs: proposals/2026-10-11-collaboration-storage-and-distribution.md U5 KTD5/KTD9, section 5 exit criteria; R4/R7/R10.
Why: Local.Put joined the caller-supplied id straight into the store directory, and store.UpdateArtifactContent feeds the HTTP path parameter into it, so a traversal id (encoded ../ in the artifact path) reaches an arbitrary-path file write; Get/Delete took stored refs unvalidated too. The 2026-09-30 deep security scan flagged the unsanitized join as the one actionable finding after triage.
Evidence: new internal/blobstore tests cover rejection of empty/dot/dotdot/traversal/absolute/backslash/NUL ids with no residue in the store dir, a normal Put/Get/Delete roundtrip, and refusal of out-of-store refs for Get and Delete; go vet ./... and go test -count=1 ./... pass.
Impact: internal/blobstore only; plain-filename ids (the only form produced today via newID and artifact path params) keep working; no schema, API, or caller change.
Refs: Dev-side task fix/blobstore-id-hardening (gitlink pin follows after combined verification)