Skip to content

Commit e927783

Browse files
JSv4claude
andauthored
Address PR #1825 review follow-ups: CAML edit lock ordering + description follow-up tracking (#1848) (#1855)
* Harden Readme.CAML edit lock ordering; track description follow-ups (#1848) - caml_article: acquire a row lock on the Corpus inside the atomic CAML edit block (select_for_update) so a concurrent corpus delete or readme_caml_document cache refresh cannot race between the Document lock and import_document. Documents the Document -> Corpus -> DocumentPath lock order (issue #1848 item 5). - descriptions/signals: replace bare follow-up prose with TODO(#1848) references for threading the live caller through get_corpus_description and for the readme-document signal-guard hardening (items 1 and 3). * Revert historical migration 0018; fix CAML test class typo (#1848) - Restore opencontractserver/corpuses/migrations/0018_* to its pre-#1825 contents (the no-op RunPython appended in #1825 mutated an already-shipped migration, which can diverge migration hash-checks in CI). The op was a no-op so reverting has no schema/data effect (issue #1848 item 6). - Rename test class AapplyCamlArticleEditCreatesVersionTreeSiblingTest -> ApplyCamlArticleEditCreatesVersionTreeSiblingTest (merge-artifact typo). * Restore migration 0018 inline upload_to helper (#1848) The prior revert (b504705) reintroduced a dangling reference to opencontractserver.corpuses.models.calculate_description_filepath, which the canonical-CAML refactor (#1825) removed. Django cannot import the historical migration, breaking the test suite. Restore the inlined calculate_description_filepath callable so 0018 stays importable on fresh installs. https://claude.ai/code/session_01Wa4U1t8WG2uAff2JY9d7z1 * Address PR #1855 review: clarify DocumentPath lock comment The lock-order comment implied DocumentPath is always locked inside import_document; it is locked via .select_for_update().first() only when the path already exists (the new-document case has no row to lock). Comment-only; no behavior change. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 07b62e6 commit e927783

4 files changed

Lines changed: 15 additions & 4 deletions

File tree

opencontractserver/corpuses/signals.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ def _is_readme_caml_document(doc: Document) -> bool:
329329
trip these signals into a spurious cache refresh. The refresh is
330330
idempotent and corpus-scoped (it re-derives from the current CAML head via
331331
DocumentPath), so a false positive is wasteful but not corrupting. A
332-
model-level flag would harden this; left as a follow-up.
332+
model-level flag (e.g. a ``Document.is_corpus_readme`` boolean) would
333+
harden this; tracked as TODO(#1848).
333334
"""
334335
from opencontractserver.constants.document_processing import (
335336
CAML_ARTICLE_TITLE,

opencontractserver/llms/tools/core_tools/caml_article.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,15 @@ def _apply_caml_article_edit(
522522
from opencontractserver.corpuses.models import Corpus
523523
from opencontractserver.documents.versioning import import_document
524524

525-
corpus = Corpus.objects.get(pk=corpus_id)
525+
# Lock order inside this atomic block: Document (locked above via
526+
# ``select_for_update``) -> Corpus -> DocumentPath (locked inside
527+
# ``import_document`` via ``.select_for_update().first()`` — only when
528+
# the path already exists; the new-document case has no row to lock).
529+
# Lock the Corpus row too so a concurrent corpus
530+
# delete, or a ``readme_caml_document_id`` cache refresh, cannot race
531+
# between the Document lock and ``import_document`` and leave this edit
532+
# operating on a stale Corpus.
533+
corpus = Corpus.objects.select_for_update().get(pk=corpus_id)
526534
new_doc, _status, _path = import_document(
527535
corpus=corpus,
528536
path=CAML_ARTICLE_TITLE,

opencontractserver/llms/tools/core_tools/descriptions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def get_corpus_description(
5656
# description (a corpus-level README, not per-document data) is effectively
5757
# gated upstream. Threading the live caller through here for defence-in-depth
5858
# would require a tool-signature + injection change and is tracked as a
59-
# follow-up rather than done inline.
59+
# follow-up rather than done inline. TODO(#1848): thread the invoking user
60+
# through ``get_corpus_description`` once the agent framework injects it,
61+
# and drop the ``corpus.creator`` fallback.
6062
#
6163
# Defensive: ``corpus.creator`` can be NULL if the creating user was hard-
6264
# deleted/deactivated. Without a creator there is no identity to scope the

opencontractserver/tests/test_caml_review_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,7 @@ def test_preview_window_clamps_at_document_boundaries(self):
11481148
self.assertEqual(result["char_offset"], 0)
11491149

11501150

1151-
class AapplyCamlArticleEditCreatesVersionTreeSiblingTest(TransactionTestCase):
1151+
class ApplyCamlArticleEditCreatesVersionTreeSiblingTest(TransactionTestCase):
11521152
"""Pin Task 8.5: agent CAML edits create a version-tree sibling.
11531153
11541154
After the refactor, ``_apply_caml_article_edit`` routes its write through

0 commit comments

Comments
 (0)