fix: close the residual gaps from the v1 backport review - #6890
Conversation
Three findings from the review of the v1 security backport batch that were not caused by those PRs. Deleting an artifact removed its whole directory, and a nested artifact lives beneath its parent, so deleting doc destroyed doc/nested; listing never reported it either. Deletion now takes only the artifact's own versions directory and prunes what it leaves empty. The GCS backend had the same conflation in its version scan, where a nested artifact's versions were reported as the parent's. A skill name could carry a trailing newline into the resource name, because the pattern ends in $ and was applied with match rather than fullmatch. The RAG temp-file cleanup caught only FileNotFoundError. It runs in a finally, so any other OSError displaced the exception already propagating, including CancelledError.
f456cef to
326279a
Compare
| os.remove(temp_file_path) | ||
| except FileNotFoundError: | ||
| pass | ||
| except OSError: |
There was a problem hiding this comment.
Checked the other three against upstream and they line up: _prune_empty_dirs, _iter_artifact_dirs and _delete_artifact_sync are byte-identical to origin/main, as is the GCS _parse_version. Good catch on the list_versions side of the GCS conflation -- a read resolving max(versions) to a version the artifact does not own was not something I had traced.
Also, thank you for correcting the skill-name suggestion. I offered .fullmatch() or quote() as equivalent options and they are not: v1 hands a resource name to self._client.skills.get(name=...) where main builds a URL string, so percent-encoding here would double-encode. fullmatch is the right call and the comment you added says why.
This one is the exception to the pattern, and it is the only thing I would raise. It is not a port -- origin/main:283-288 is still except FileNotFoundError: pass, so this leaves v1 ahead of main and the next backport touching this file has a divergence to reconcile by hand. The description already makes the argument that would justify fixing main too: "the displacement is a property of finally rather than of one platform". The to_thread wrapper makes it easier to hit on v1, but a read-only mount or a permissions problem displaces the propagating exception on main just the same.
Non-blocking, and I would not hold this PR for it -- would it be worth sending the same three lines upstream so the two converge rather than drift?
Closes the three code findings from the review of the v1 security backport batch (#6788–#6809) that were not regressions from those PRs and so were left for a follow-up.
Deleting an artifact no longer deletes the artifacts nested under it. Ports
08d21cc35anda5864a0ed, which are a pair. Filenames may contain/, sodocanddoc/nestedare two distinct artifacts, and a nested artifact is stored beneath its parent. Deletion removed the artifact's whole directory, so deletingdocdestroyeddoc/nestedas well, anddoc/nestednever appeared inlist_artifact_keys. Deletion now removes only the artifact's ownversions/directory and then prunes whatever it leaves empty, bounded by the scope root. Listing keeps walking past an artifact directory, skipping only that artifact's versions.The GCS half of
08d21cc35is carried too, because the same conflation is present here: version scans matched every blob under the prefix, solist_versions("doc")reported versions belonging todoc/nested. Loading and saving resolvemax(versions), so a read without an explicit version could resolve to a version the artifact does not have and silently return None, and the next write skipped version numbers. A blob now counts only when its name is the prefix followed by a single decimal segment, which also replaces an unguardedint()that raised on any object in the bucket not written by ADK.A skill name may no longer carry a trailing newline.
_SNAKE_OR_KEBAB_NAME_PATTERNends in$, which outsidere.MULTILINEalso matches just before a trailing newline, somatchlet"my-skill\n"through into the resource name.fullmatchdoes not. Traversal was already blocked either way, so this only closes the newline. Upstream covers the same gap with a second layer,quote(name, safe=''), at the point where it interpolates the name into a URL;v1calls the SDK with a resource name rather than building a URL, so percent-encoding would be wrong here and the validator is the right place.A failing temp-file cleanup no longer masks the error that caused it. The cleanup in
add_session_to_memoryruns in afinally, so an exception raised there displaces the one already propagating, includingCancelledError. It caught onlyFileNotFoundError. A cancelledasyncio.to_threadreturns while the worker thread may still hold the file, and on Windows the remove then fails withPermissionError. Now anyOSErroris logged and swallowed, which is what a best-effort cleanup should do. I could not reproduce the Windows-specific failure here, since POSIXunlinksucceeds on an open file, but the displacement is a property offinallyrather than of one platform.Each change is covered by a test that fails when only that change is reverted. Artifact coverage is upstream's, parametrized across the in-memory, GCS and file backends; two assertions compare artifact text rather than
Partequality becausev1's GCS backend round-trips a text part asinline_data, which is a pre-existing difference frommainand not touched here.Not included, from the same review: the ten defects that are byte-identical on
main, which belong upstream, and the release-note items for the artifact storage layout and the API registry credential scoping.