109 lines
4.6 KiB
Python
109 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
|
|
from control_plane.authoring.models import ChapterRevision, ChapterStateDocument, StateChange
|
|
from control_plane.authoring.services import DjangoStoryWorkflowServices
|
|
from graph.models import GraphApproval, GraphApprovalStatus
|
|
from model_router.providers import providers_from_resources
|
|
from model_router.router import ModelRouter
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Build, inspect, or query the immutable story state ledger."
|
|
|
|
def add_arguments(self, parser) -> None:
|
|
parser.add_argument("action", choices=["build", "show", "history"])
|
|
parser.add_argument("--revision")
|
|
parser.add_argument("--slug")
|
|
parser.add_argument("--entity")
|
|
parser.add_argument("--reuse-extraction", action="store_true")
|
|
|
|
def handle(self, *args, **options) -> None:
|
|
if options["action"] == "history":
|
|
self._history(options)
|
|
return
|
|
if not options["revision"]:
|
|
raise CommandError("build and show require --revision")
|
|
revision = ChapterRevision.objects.select_related("chapter__story").get(
|
|
id=options["revision"]
|
|
)
|
|
if options["action"] == "show":
|
|
self._show(revision)
|
|
return
|
|
services = DjangoStoryWorkflowServices(
|
|
ModelRouter(providers_from_resources(), persist_requests=True)
|
|
)
|
|
state = {
|
|
"revision_id": str(revision.id),
|
|
"story_id": str(revision.chapter.story_id),
|
|
"context_snapshot_id": str(revision.context_snapshot_id or ""),
|
|
}
|
|
if not options["reuse_extraction"]:
|
|
services.extract_continuity(state)
|
|
elif not ChapterStateDocument.objects.filter(revision=revision).exists():
|
|
raise CommandError("--reuse-extraction requested but no state document exists")
|
|
result = services.judge_state_contract(state)
|
|
payload = services.state_approval_payload(state)
|
|
approval = GraphApproval.objects.filter(
|
|
reason=f"STORY_CHAPTER_APPROVAL:{revision.id}",
|
|
status=GraphApprovalStatus.PENDING,
|
|
).first()
|
|
if approval is not None:
|
|
approval.payload = {**approval.payload, **payload}
|
|
approval.save(update_fields=["payload", "updated_at"])
|
|
self.stdout.write(json.dumps({**result, **payload}, ensure_ascii=False, indent=2))
|
|
|
|
def _show(self, revision: ChapterRevision) -> None:
|
|
document = ChapterStateDocument.objects.get(revision=revision)
|
|
self.stdout.write(
|
|
json.dumps(
|
|
{
|
|
"id": str(document.id),
|
|
"status": document.status,
|
|
"verdict": document.verdict,
|
|
"coverage": document.coverage,
|
|
"observed_state": document.observed_state,
|
|
"proposed_delta": document.proposed_delta,
|
|
"json_artifact_uri": document.json_artifact_uri,
|
|
"markdown_artifact_uri": document.markdown_artifact_uri,
|
|
},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
)
|
|
)
|
|
|
|
def _history(self, options: dict) -> None:
|
|
if not options.get("slug") or not options.get("entity"):
|
|
raise CommandError("history requires --slug and --entity")
|
|
changes = StateChange.objects.filter(
|
|
story__slug=options["slug"],
|
|
entity__entity_key=options["entity"],
|
|
status="COMMITTED",
|
|
).select_related("revision__chapter", "related_entity")
|
|
self.stdout.write(
|
|
json.dumps(
|
|
[
|
|
{
|
|
"chapter": change.effective_chapter,
|
|
"revision_id": str(change.revision_id),
|
|
"sequence": change.sequence,
|
|
"change_type": change.change_type,
|
|
"predicate": change.predicate,
|
|
"operation": change.operation,
|
|
"previous_value": change.previous_value,
|
|
"new_value": change.new_value,
|
|
"related_entity": (
|
|
change.related_entity.entity_key if change.related_entity else None
|
|
),
|
|
"evidence_quote": change.evidence_quote,
|
|
"evidence_location": change.evidence_location,
|
|
}
|
|
for change in changes.order_by("effective_chapter", "sequence")
|
|
],
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
)
|
|
)
|