Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion opencontractserver/corpuses/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ def _is_readme_caml_document(doc: Document) -> bool:
trip these signals into a spurious cache refresh. The refresh is
idempotent and corpus-scoped (it re-derives from the current CAML head via
DocumentPath), so a false positive is wasteful but not corrupting. A
model-level flag would harden this; left as a follow-up.
model-level flag (e.g. a ``Document.is_corpus_readme`` boolean) would
harden this; tracked as TODO(#1848).
"""
from opencontractserver.constants.document_processing import (
CAML_ARTICLE_TITLE,
Expand Down
10 changes: 9 additions & 1 deletion opencontractserver/llms/tools/core_tools/caml_article.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,15 @@ def _apply_caml_article_edit(
from opencontractserver.corpuses.models import Corpus
from opencontractserver.documents.versioning import import_document

corpus = Corpus.objects.get(pk=corpus_id)
# Lock order inside this atomic block: Document (locked above via
# ``select_for_update``) -> Corpus -> DocumentPath (locked inside
# ``import_document`` via ``.select_for_update().first()`` — only when
# the path already exists; the new-document case has no row to lock).
# Lock the Corpus row too so a concurrent corpus
# delete, or a ``readme_caml_document_id`` cache refresh, cannot race
# between the Document lock and ``import_document`` and leave this edit
# operating on a stale Corpus.
corpus = Corpus.objects.select_for_update().get(pk=corpus_id)
new_doc, _status, _path = import_document(
corpus=corpus,
path=CAML_ARTICLE_TITLE,
Expand Down
4 changes: 3 additions & 1 deletion opencontractserver/llms/tools/core_tools/descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def get_corpus_description(
# description (a corpus-level README, not per-document data) is effectively
# gated upstream. Threading the live caller through here for defence-in-depth
# would require a tool-signature + injection change and is tracked as a
# follow-up rather than done inline.
# follow-up rather than done inline. TODO(#1848): thread the invoking user
# through ``get_corpus_description`` once the agent framework injects it,
# and drop the ``corpus.creator`` fallback.
#
# Defensive: ``corpus.creator`` can be NULL if the creating user was hard-
# deleted/deactivated. Without a creator there is no identity to scope the
Expand Down
2 changes: 1 addition & 1 deletion opencontractserver/tests/test_caml_review_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ def test_preview_window_clamps_at_document_boundaries(self):
self.assertEqual(result["char_offset"], 0)


class AapplyCamlArticleEditCreatesVersionTreeSiblingTest(TransactionTestCase):
class ApplyCamlArticleEditCreatesVersionTreeSiblingTest(TransactionTestCase):
"""Pin Task 8.5: agent CAML edits create a version-tree sibling.

After the refactor, ``_apply_caml_article_edit`` routes its write through
Expand Down