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
76 changes: 75 additions & 1 deletion scripts/live_multi_turn_followup_prompt_bank.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,38 @@
}
]
},
{
"case_id": "jira_idempotent_label_write_readback",
"domain": "jira",
"motivating_issues": ["JVNAUTOSCI-2568", "JVNAUTOSCI-2591"],
"turns": [
{
"role": "task",
"prompt": "On Jira issue JVNAUTOSCI-2591, add the label uc05-live-validated if it is absent, without changing any other field. Then fetch that exact issue directly from Jira and report its key, status, and complete label list so the change is verified by read-back.",
"expectations": {
"require_completed": true,
"response_must_mention_all_groups": [
["JVNAUTOSCI-2591"],
["uc05-live-validated"]
],
"notes": "Authorised bounded UC-05 mutation. The turn must resolve the exact key, preserve other fields, perform at most one label update, and verify canonical Jira state after the write."
}
},
{
"role": "explicit_continuation",
"prompt": "Repeat that exact operation for JVNAUTOSCI-2591. Do not create a duplicate label or change another field. Fetch the issue again and report the complete label list from Jira.",
"expectations": {
"require_completed": true,
"allow_obligation_carry_forward": true,
"response_must_mention_all_groups": [
["JVNAUTOSCI-2591"],
["uc05-live-validated"]
],
"notes": "Repeated UC-05 mutation must be idempotent: the same label remains present exactly once after direct Jira read-back."
}
}
]
},
{
"case_id": "generic_concept_followup_family",
"domain": "vontology_generic",
Expand Down Expand Up @@ -203,6 +235,43 @@
}
]
},
{
"case_id": "lab_status_digest_obligation_watch",
"domain": "team_continuity",
"motivating_issues": ["JVNAUTOSCI-2590"],
"turns": [
{
"role": "task",
"prompt": "Create today's operational-reliability programme status digest from current JVNAUTOSCI Jira evidence, current repository evidence, and the prior represented digest. Persist it as the canonical digest work product. Include verified changes, milestones, blockers, unresolved or stale obligations, decisions needed, deadlines, and evidence gaps. Keep Jira Done, workflow completed, and capability passed distinct. Read-only outside the digest work product: do not transition, comment on, close, assign, or otherwise change Jira, GitHub, mail, or workflow instances.",
"expectations": {
"expected_workflow_id": "#V#lab_project_status_digest_workflow",
"require_completed": true,
"response_must_mention_all_groups": [
["#V#operational_reliability_status_digest"],
["JVNAUTOSCI-"],
["evidence gap", "unavailable"],
["capability", "accepted", "passed"]
],
"notes": "Real UC-06 foreground path. The represented workflow must read current Jira/repository evidence and prior durable state, update only the stable digest concept, read it back, and avoid conflating issue, workflow, and capability states."
}
},
{
"role": "explicit_continuation",
"prompt": "Run that digest again against current evidence. Reuse the same represented digest work product, carry forward only obligations that are still unresolved, and do not duplicate an obligation with the same source, owner, and due date. Tell me what changed since the prior digest and what evidence is unavailable. Keep every external system read-only.",
"expectations": {
"expected_workflow_id": "#V#lab_project_status_digest_workflow",
"require_completed": true,
"allow_obligation_carry_forward": true,
"response_must_mention_all_groups": [
["#V#operational_reliability_status_digest"],
["evidence gap", "unavailable"],
["obligation", "unresolved"]
],
"notes": "Repeated UC-06 execution checks stable work-product identity and evidence-based obligation carry-forward without duplication or external mutations."
}
}
]
},
{
"case_id": "arxiv_degraded_success_ingestion",
"domain": "arxiv",
Expand All @@ -215,7 +284,12 @@
"expected_workflow_id": "#V#arxiv_paper_representation_workflow",
"require_completed": true,
"response_must_mention_any": ["2408.12065"],
"response_must_not_mention": ["remote_file_copy_timeout"],
"response_must_not_mention": [
"remote_file_copy_timeout",
"arxiv_paper_representation_not_executed",
"failed to complete the mandatory scholarly representation",
"scholarly representation of the paper could not be executed"
],
"notes": "The JVNAUTOSCI-2553 motivating paper. Success is metadata representation + read-back; PDF acquisition is optional enrichment. If acquisition failed, the answer must say so honestly (degraded success), never convert it into a terminal failure or surface raw support-error codes as the answer."
}
}
Expand Down
225 changes: 225 additions & 0 deletions scripts/migrate_jvnautosci_2591_arxiv_degraded_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
"""Preview or publish the UC-03 arXiv degraded-path output contract repair."""

from __future__ import annotations

import argparse
import copy
import json
from pathlib import Path
import sys
from typing import Any, Mapping

from dotenv import load_dotenv


_PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(_PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(_PROJECT_ROOT))
load_dotenv(_PROJECT_ROOT / ".env", override=False)

from src.backend.security.access_control import override_current_actor # noqa: E402
from src.backend.workflows.vontology_loader import ( # noqa: E402
load_workflow_definition_from_vontology,
)
from src.backend.workflows.workflow_authoring_service import ( # noqa: E402
serialise_workflow_definition_to_authoring_spec,
)
from src.backend.workflows.workflow_definition_identity_service import ( # noqa: E402
build_workflow_definition_identity,
)
from src.backend.workflows.workflow_studio_service import ( # noqa: E402
apply_workflow_authoring_spec,
preview_workflow_authoring_spec,
)


WORKFLOW_ID = "#V#arxiv_paper_representation_workflow"
STATE_SUFFIX = "_record_pdf_import_skipped"
DELEGATE_STATE_SUFFIX = "_delegate_to_general_paper_workflow"
DELEGATED_WORKFLOW_ID = "#V#scholarly_article_metadata_representation_workflow"
DIAGNOSTIC_KEYS = frozenset(
{
"arxiv_pdf_import_error",
"arxiv_pdf_import_message",
"arxiv_pdf_import_status_code",
"arxiv_pdf_import_final_url",
}
)
DEFAULT_ACTOR_USER_ID = "#V#zhan_von_witbrock"
DEFAULT_ACTOR_ORGANISATION_ID = "#V#university_of_auckland_strong_ai_lab"


def _clean_text(value: Any) -> str:
return str(value or "").strip() if isinstance(value, str) else ""


def rewrite_degraded_pdf_contract(
authoring_spec: Mapping[str, Any],
) -> tuple[dict[str, Any], bool]:
spec = copy.deepcopy(dict(authoring_spec))
raw_steps = spec.get("steps")
if not isinstance(raw_steps, list):
raise ValueError("workflow_authoring_spec_missing_steps")
matches = [
step
for step in raw_steps
if isinstance(step, dict)
and _clean_text(step.get("state_id")).endswith(STATE_SUFFIX)
]
if len(matches) != 1:
raise ValueError(f"workflow_state_match_count:{STATE_SUFFIX}:{len(matches)}")
state = matches[0]
bindings = state.get("static_input_bindings")
if not isinstance(bindings, list):
raise ValueError("record_pdf_import_skipped_static_inputs_missing")
assignment_bindings = [
binding
for binding in bindings
if isinstance(binding, dict)
and _clean_text(binding.get("tool_param") or binding.get("key"))
== "assignments"
]
if len(assignment_bindings) != 1:
raise ValueError("record_pdf_import_skipped_assignments_ambiguous")
assignments = assignment_bindings[0].get("value")
if not isinstance(assignments, list):
raise ValueError("record_pdf_import_skipped_assignments_invalid")

seen: set[str] = set()
for assignment in assignments:
if not isinstance(assignment, dict):
continue
key = _clean_text(assignment.get("key"))
if key not in DIAGNOSTIC_KEYS:
continue
seen.add(key)
assignment.pop("skip_if_unresolved", None)
missing = sorted(DIAGNOSTIC_KEYS - seen)
if missing:
raise ValueError(
"record_pdf_import_skipped_diagnostics_missing:" + ",".join(missing)
)

delegate_matches = [
step
for step in raw_steps
if isinstance(step, dict)
and _clean_text(step.get("state_id")).endswith(DELEGATE_STATE_SUFFIX)
]
if len(delegate_matches) != 1:
raise ValueError(
f"workflow_state_match_count:{DELEGATE_STATE_SUFFIX}:"
f"{len(delegate_matches)}"
)
delegate_step = delegate_matches[0]
existing_subworkflow_id = _clean_text(delegate_step.get("subworkflow_id"))
if existing_subworkflow_id and existing_subworkflow_id != DELEGATED_WORKFLOW_ID:
raise ValueError("delegate_subworkflow_id_conflict")
delegate_step["subworkflow_id"] = DELEGATED_WORKFLOW_ID
return spec, spec != dict(authoring_spec)


def _identity(definition: Any) -> dict[str, Any]:
return build_workflow_definition_identity(
workflow_id=WORKFLOW_ID,
source="vontology",
definition=definition,
authoritative_definition=definition,
)


def run_migration(
*, apply: bool, actor_user_id: str, actor_organisation_id: str
) -> dict[str, Any]:
with override_current_actor(
user_concept_id=actor_user_id,
organisation_concept_id=actor_organisation_id,
):
before = load_workflow_definition_from_vontology(WORKFLOW_ID)
if before is None:
raise ValueError(f"workflow_not_found:{WORKFLOW_ID}")
before_identity = _identity(before)
before_spec = serialise_workflow_definition_to_authoring_spec(before)
candidate_spec, changed = rewrite_degraded_pdf_contract(before_spec)
base_hash = _clean_text(before_identity.get("definition_hash"))
preview = preview_workflow_authoring_spec(
WORKFLOW_ID,
authoring_spec=candidate_spec,
base_definition_hash=base_hash,
)
preview_body = preview.get("preview") or {}
validation = preview_body.get("contract_validation") or {}
result: dict[str, Any] = {
"workflow_id": WORKFLOW_ID,
"actor_user_id": actor_user_id,
"actor_organisation_id": actor_organisation_id,
"mode": "apply" if apply else "preview",
"changed": changed,
"base_definition_hash": base_hash,
"candidate_definition_hash": (
preview_body.get("definition_identity") or {}
).get("definition_hash"),
"contract_valid": bool(validation.get("valid")),
"contract_errors": validation.get("errors") or [],
"contract_warnings": validation.get("warnings") or [],
"subworkflow_contract_issues": validation.get(
"subworkflow_contract_issues"
)
or [],
"diff_summary": preview_body.get("diff_summary"),
}
if not apply:
return result
applied = apply_workflow_authoring_spec(
WORKFLOW_ID,
authoring_spec=candidate_spec,
base_definition_hash=base_hash,
)
publication = applied.get("publication") or {}
counts = publication.get("counts") or {}
published_workflow_ids = publication.get("published_workflow_ids") or []
if counts.get("errors") or WORKFLOW_ID not in published_workflow_ids:
raise ValueError(
"workflow_publication_failed:" + json.dumps(
publication,
sort_keys=True,
default=str,
)
)
after = load_workflow_definition_from_vontology(WORKFLOW_ID)
if after is None:
raise ValueError("workflow_missing_after_publication")
after_spec = serialise_workflow_definition_to_authoring_spec(after)
_readback_spec, still_changed = rewrite_degraded_pdf_contract(after_spec)
if still_changed:
raise ValueError("workflow_publication_readback_mismatch")
result["publication"] = publication
result["readback_definition_hash"] = _identity(after).get("definition_hash")
result["canonical_readback_verified"] = True
return result


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--apply", action="store_true")
parser.add_argument("--actor-user-id", default=DEFAULT_ACTOR_USER_ID)
parser.add_argument(
"--actor-organisation-id", default=DEFAULT_ACTOR_ORGANISATION_ID
)
args = parser.parse_args()
print(
json.dumps(
run_migration(
apply=bool(args.apply),
actor_user_id=str(args.actor_user_id).strip(),
actor_organisation_id=str(args.actor_organisation_id).strip(),
),
indent=2,
sort_keys=True,
default=str,
)
)


if __name__ == "__main__":
main()
Loading
Loading