* feat(history): add bounded chronological read projections (#209) * fix(history): validate internal continuation positions (#209) * feat(history): expose fenced history windows and run details (#209) * fix(history): release retired projection connections (#209) * feat(history): resolve reported outputs through current authorization (#209) * fix(history): reject invalid inspection targets (#209) * feat(history): add scoped history state and current-file adapter (#209) * fix(history): preserve slideshow and clear stale reentry actions (#209) * feat(history): render recorded executions and frozen run details (#209) * fix(history): recover receipts and refresh capture evidence (#209) * test(history): verify chronological history across restore and host modes (#209) * fix(history): enforce chronological marker visual parity (#209) * fix(history): resolve final chronological review findings Address #209 recovery, bounded visit restoration, exact receipt availability, and portable regression coverage. * test(history): repair Windows fixtures and review feedback Address #209 and PR #227 native path, cleanup, media and review-comment expectations. Document Windows portability preflight without changing production behavior. * ci: raise the viewerjs job budget for the larger browser suite The cross-browser workflows suite grew from 396 to 496 tests on this branch, which pushed the whole viewerjs job to about 25.5 minutes against the 25 minute cap set in #181. Two exact-head runs on this branch timed out without any test failing: run 39173019283 was killed on test 474 of 496, and run 39175125474 printed "496 passed (12.8m)" and was then cancelled during Playwright shutdown. Raise only the job budget. Browser projects, workers and per-test timeouts are unchanged. Splitting the browsers into parallel jobs is tracked separately.
508 lines
20 KiB
Python
508 lines
20 KiB
Python
"""Wire projections preserve exact bounded evidence independently of role parsing."""
|
|
|
|
import io
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from utils.inspection_response import encode_inspection_response
|
|
from utils.prompt_inspector import (
|
|
InspectionRead,
|
|
MAX_METADATA_BYTES,
|
|
MetadataReadError,
|
|
inspect_prompt_roles,
|
|
read_inspection_file,
|
|
read_inspection_metadata,
|
|
)
|
|
from utils.pair_execution import EXECUTION_KEY
|
|
|
|
CAP = 8 * 1024 * 1024
|
|
IMAGE = {"root_index": 0, "path": "image.png", "filename": "image.png"}
|
|
PROMPTS = {
|
|
"positive": {"status": "resolved", "text": "portrait", "source": "graph-inferred"},
|
|
"negative": {"status": "resolved", "text": "", "source": "graph-inferred"},
|
|
}
|
|
|
|
|
|
def inspection(metadata, *, formats=None, carrier=None):
|
|
return InspectionRead(
|
|
metadata=metadata,
|
|
record_formats=(
|
|
formats
|
|
if formats is not None
|
|
else {key: "original-text" for key in metadata}
|
|
),
|
|
video_carrier_text=carrier,
|
|
format="PNG",
|
|
width=4,
|
|
height=4,
|
|
)
|
|
|
|
|
|
def encode(read, *, prompts=None, file=None, library=None):
|
|
return encode_inspection_response(
|
|
image=IMAGE,
|
|
root_scope="a" * 64,
|
|
file=file
|
|
or {
|
|
"observation": "current-file",
|
|
"source_label": "output",
|
|
"media_type": "image",
|
|
"extension": ".png",
|
|
"size": 100,
|
|
"modified_time": 100,
|
|
},
|
|
prompts=PROMPTS if prompts is None else prompts,
|
|
resolution={"state": "resolved", "sampler_id": "3", "reason": ""},
|
|
inspection=read,
|
|
library=library
|
|
or {
|
|
"status": "none",
|
|
"scope": "saved-library",
|
|
"positive_id": None,
|
|
"pair_id": None,
|
|
"title": None,
|
|
"category": None,
|
|
"tags": [],
|
|
"metadata_scope": None,
|
|
},
|
|
)
|
|
|
|
|
|
def graph_text(count=3):
|
|
graph = {
|
|
"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "portrait"}},
|
|
"2": {"class_type": "CLIPTextEncode", "inputs": {"text": ""}},
|
|
"3": {
|
|
"class_type": "KSampler",
|
|
"inputs": {
|
|
"positive": ["1", 0],
|
|
"negative": ["2", 0],
|
|
"seed": 9007199254740993,
|
|
"cfg": "EXACT_CFG",
|
|
},
|
|
},
|
|
}
|
|
for index in range(4, count + 1):
|
|
graph[str(index)] = {
|
|
"class_type": "Primitive",
|
|
"inputs": {"value": 1, "labels": [True, False, "ordinary"]},
|
|
}
|
|
return json.dumps(graph).replace('"EXACT_CFG"', "6.5000000000000000001")
|
|
|
|
|
|
class TestInspectionResponse(unittest.TestCase):
|
|
def test_normalized_numbers_preserve_exact_literals(self):
|
|
text = graph_text().replace(
|
|
'"cfg": 6.5000000000000000001',
|
|
'"cfg": 6.5000000000000000001, "huge": 1e1000000000',
|
|
)
|
|
result = json.loads(encode(inspection({"prompt": text})))
|
|
inputs = result["metadata"]["prompt_graph"]["value"]["3"]["inputs"]
|
|
self.assertEqual(inputs["seed"], "9007199254740993")
|
|
self.assertEqual(inputs["cfg"], "6.5000000000000000001")
|
|
self.assertEqual(inputs["huge"].lower(), "1e+1000000000")
|
|
self.assertEqual(inputs["positive"], ["1", 0])
|
|
self.assertEqual(result["metadata"]["raw"]["prompt"], text)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_normalization_preserves_150_node_graph_with_more_than_500_json_values(
|
|
self,
|
|
):
|
|
text = graph_text(150)
|
|
self.assertGreater(len(text.split(":")), 500)
|
|
for read in (
|
|
inspection({"prompt": text}),
|
|
inspection(
|
|
{"prompt": json.dumps(json.loads(text))},
|
|
formats={"prompt": "reserialized-json"},
|
|
carrier='{"prompt":' + text + ',"unrelated":{"value":1}}',
|
|
),
|
|
):
|
|
result = json.loads(encode(read))
|
|
graph = result["metadata"]["prompt_graph"]
|
|
self.assertEqual(graph["status"], "parsed")
|
|
self.assertEqual(len(graph["value"]), 150)
|
|
self.assertEqual(
|
|
graph["value"]["3"]["inputs"]["cfg"], "6.5000000000000000001"
|
|
)
|
|
self.assertEqual(graph["value"]["3"]["inputs"]["seed"], "9007199254740993")
|
|
self.assertEqual(graph["value"]["3"]["inputs"]["positive"], ["1", 0])
|
|
self.assertIs(graph["value"]["4"]["inputs"]["labels"][0], True)
|
|
self.assertEqual(result["metadata"]["raw"], read.metadata)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_graph_node_limit_counts_nodes_not_json_fields(self):
|
|
for count, status in ((500, "parsed"), (501, "limited")):
|
|
prompt = graph_text(count)
|
|
workflow = json.dumps(
|
|
{
|
|
"nodes": [
|
|
{"id": i, "type": "Primitive", "widgets_values": [1, True]}
|
|
for i in range(count)
|
|
]
|
|
}
|
|
)
|
|
for read in (
|
|
inspection({"prompt": prompt, "workflow": workflow}),
|
|
inspection(
|
|
{"prompt": json.dumps(json.loads(prompt)), "workflow": workflow},
|
|
formats={
|
|
"prompt": "reserialized-json",
|
|
"workflow": "reserialized-json",
|
|
},
|
|
carrier='{"other":[],"prompt":'
|
|
+ prompt
|
|
+ ',"workflow":'
|
|
+ workflow
|
|
+ "}",
|
|
),
|
|
):
|
|
result = json.loads(encode(read))
|
|
for key in ("prompt_graph", "workflow_graph"):
|
|
self.assertEqual(result["metadata"][key]["status"], status)
|
|
if status == "limited":
|
|
self.assertIsNone(result["metadata"][key]["value"])
|
|
self.assertEqual(result["metadata"]["raw"], read.metadata)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_strict_projection_depth_is_separate_from_semantic_link_depth(self):
|
|
for depth in (64, 65):
|
|
# API map root + node + inputs + nested array containers.
|
|
text = (
|
|
'{"1":{"class_type":"Primitive","inputs":{"value":'
|
|
+ "[" * (depth - 3)
|
|
+ "0"
|
|
+ "]" * (depth - 3)
|
|
+ "}}}"
|
|
)
|
|
result = json.loads(encode(inspection({"prompt": text})))
|
|
self.assertEqual(
|
|
result["metadata"]["prompt_graph"]["status"],
|
|
"parsed" if depth == 64 else "limited",
|
|
)
|
|
carrier = '{"prompt":' + text + "}"
|
|
result = json.loads(
|
|
encode(
|
|
inspection(
|
|
{"prompt": text},
|
|
formats={"prompt": "reserialized-json"},
|
|
carrier=carrier,
|
|
)
|
|
)
|
|
)
|
|
self.assertEqual(result["metadata"]["prompt_graph"]["status"], "limited")
|
|
text = '{"1":{"inputs":{"value":' + "[" * 60 + "0" + "]" * 60 + "}}}"
|
|
result = json.loads(
|
|
encode(
|
|
inspection(
|
|
{"prompt": text},
|
|
formats={"prompt": "reserialized-json"},
|
|
carrier='{"prompt":' + text + "}",
|
|
)
|
|
)
|
|
)
|
|
self.assertEqual(result["metadata"]["prompt_graph"]["status"], "parsed")
|
|
chain = {
|
|
str(i): {
|
|
"class_type": "Reroute",
|
|
"inputs": {"value": [str((i + 1) % 150), 0]},
|
|
}
|
|
for i in range(150)
|
|
}
|
|
result = json.loads(encode(inspection({"prompt": json.dumps(chain)})))
|
|
self.assertEqual(result["metadata"]["prompt_graph"]["status"], "parsed")
|
|
self.assertEqual(len(result["metadata"]["prompt_graph"]["value"]), 150)
|
|
|
|
def test_wide_bounded_values_do_not_consume_semantic_visit_budget(self):
|
|
text = json.dumps(
|
|
{
|
|
"1": {
|
|
"inputs": {
|
|
"values": list(range(20000)),
|
|
"fields": {str(i): True for i in range(1000)},
|
|
}
|
|
}
|
|
}
|
|
)
|
|
self.assertLess(len(text.encode()), MAX_METADATA_BYTES)
|
|
body = encode(inspection({"prompt": text}))
|
|
graph = json.loads(body)["metadata"]["prompt_graph"]
|
|
self.assertEqual(graph["status"], "parsed")
|
|
self.assertEqual(len(graph["value"]["1"]["inputs"]["values"]), 20000)
|
|
self.assertEqual(len(graph["value"]["1"]["inputs"]["fields"]), 1000)
|
|
self.assertLess(len(body), CAP)
|
|
|
|
def test_strict_malformed_records_preserve_original_raw_and_roles(self):
|
|
for text in (
|
|
'{"1":{},"1":{}}',
|
|
'{"1":{"value":NaN}}',
|
|
'{"1":{"value":Infinity}}',
|
|
"{broken",
|
|
"[]",
|
|
"42",
|
|
"[" * 2000,
|
|
):
|
|
result = json.loads(encode(inspection({"prompt": text, "workflow": text})))
|
|
self.assertIn(
|
|
result["metadata"]["prompt_graph"]["status"], ("malformed", "limited")
|
|
)
|
|
self.assertIsNone(result["metadata"]["prompt_graph"]["value"])
|
|
self.assertEqual(
|
|
result["metadata"]["raw"], {"prompt": text, "workflow": text}
|
|
)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_video_missing_exact_source_is_limited_not_rounded(self):
|
|
rounded = json.dumps(json.loads(graph_text()))
|
|
result = json.loads(
|
|
encode(
|
|
inspection({"prompt": rounded}, formats={"prompt": "reserialized-json"})
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
result["metadata"]["prompt_graph"], {"status": "limited", "value": None}
|
|
)
|
|
self.assertEqual(result["metadata"]["raw"]["prompt"], rounded)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_response_escaping_omits_graphs_then_entire_raw_atomically(self):
|
|
prompts = {
|
|
role: {
|
|
"status": "resolved",
|
|
"text": "\x01" * 100000,
|
|
"source": "graph-inferred",
|
|
}
|
|
for role in ("positive", "negative")
|
|
}
|
|
source = json.loads(graph_text())
|
|
source["1"]["inputs"]["text"] = prompts["positive"]["text"]
|
|
source["2"]["inputs"]["text"] = prompts["negative"]["text"]
|
|
raw = json.dumps(source)
|
|
for count, raw_retained in ((450000, True), (550000, False)):
|
|
metadata = {"prompt": raw, "parameters": "\x01" * count}
|
|
self.assertLess(
|
|
sum(len(value.encode()) for value in metadata.values()),
|
|
MAX_METADATA_BYTES,
|
|
)
|
|
body = encode(inspection(metadata), prompts=prompts)
|
|
result = json.loads(body)
|
|
self.assertLessEqual(len(body), CAP)
|
|
self.assertEqual(result["image"], IMAGE)
|
|
self.assertEqual(result["prompts"], prompts)
|
|
self.assertEqual(
|
|
result["metadata"]["prompt_graph"], {"status": "limited", "value": None}
|
|
)
|
|
self.assertEqual(
|
|
result["metadata"]["workflow_graph"],
|
|
{"status": "absent", "value": None},
|
|
)
|
|
self.assertEqual(
|
|
result["metadata"]["record_formats"],
|
|
{key: "original-text" for key in metadata},
|
|
)
|
|
if raw_retained:
|
|
self.assertEqual(result["metadata"]["status"], "available")
|
|
self.assertIsNone(result["metadata"]["omitted_reason"])
|
|
self.assertEqual(result["metadata"]["raw"], metadata)
|
|
self.assertEqual(
|
|
result["metadata"]["parameters"], metadata["parameters"]
|
|
)
|
|
else:
|
|
self.assertEqual(result["metadata"]["raw"], {})
|
|
self.assertIsNone(result["metadata"]["parameters"])
|
|
self.assertEqual(result["metadata"]["status"], "limited")
|
|
self.assertEqual(
|
|
result["metadata"]["omitted_reason"], "response-size-limit"
|
|
)
|
|
|
|
def test_response_byte_boundary_is_inclusive(self):
|
|
prompts = {
|
|
role: {
|
|
"status": "resolved",
|
|
"text": "\x01" * 100000,
|
|
"source": "graph-inferred",
|
|
}
|
|
for role in ("positive", "negative")
|
|
}
|
|
text = graph_text().replace(
|
|
'"cfg": 6.5000000000000000001', '"cfg":6.5000000000000000001,"label":"猫😀"'
|
|
)
|
|
read = inspection({"prompt": text, "parameters": "\x01" * 500000})
|
|
base_file = {
|
|
"observation": "current-file",
|
|
"source_label": "output",
|
|
"media_type": "image",
|
|
"extension": ".png",
|
|
}
|
|
initial_size = len(encode(read, prompts=prompts, file=base_file))
|
|
self.assertLess(initial_size, CAP)
|
|
for target in (CAP - 1, CAP, CAP + 1):
|
|
extra = target - initial_size
|
|
file = {**base_file, "source_label": "output" + ("x" if extra % 2 else "")}
|
|
parameters = read.metadata["parameters"] + "x" * (extra // 2)
|
|
current = inspection({"prompt": text, "parameters": parameters})
|
|
self.assertLess(
|
|
sum(len(value.encode()) for value in current.metadata.values()),
|
|
MAX_METADATA_BYTES,
|
|
)
|
|
body = encode(current, prompts=prompts, file=file)
|
|
result = json.loads(body)
|
|
if target <= CAP:
|
|
self.assertEqual(len(body), target)
|
|
self.assertEqual(result["metadata"]["prompt_graph"]["status"], "parsed")
|
|
else:
|
|
self.assertLessEqual(len(body), CAP)
|
|
self.assertEqual(
|
|
result["metadata"]["prompt_graph"]["status"], "limited"
|
|
)
|
|
self.assertEqual(result["prompts"], prompts)
|
|
self.assertEqual(result["image"], IMAGE)
|
|
self.assertEqual(result["metadata"]["raw"], current.metadata)
|
|
|
|
def test_optional_display_fields_are_validated_and_library_is_bounded(self):
|
|
file = {
|
|
"size": -1,
|
|
"width": True,
|
|
"height": float("inf"),
|
|
"modified_time": float("nan"),
|
|
"format": 12,
|
|
"observation": "current-file",
|
|
}
|
|
library = {
|
|
"status": "linked",
|
|
"positive_id": 2**53,
|
|
"pair_id": True,
|
|
"tags": ["valid", 3],
|
|
"category": {},
|
|
"title": [],
|
|
}
|
|
result = json.loads(encode(None, file=file, library=library))
|
|
for key in ("size", "width", "height", "modified_time", "format"):
|
|
self.assertNotIn(key, result["file"])
|
|
for key in ("positive_id", "pair_id", "category", "title"):
|
|
self.assertNotIn(key, result["library"])
|
|
self.assertEqual(result["library"]["tags"], ["valid"])
|
|
result = json.loads(
|
|
encode(inspection({}), library={"status": "linked", "tags": ["猫" * 12000]})
|
|
)
|
|
self.assertEqual(result["library"]["status"], "limited")
|
|
self.assertEqual(result["library"]["tags"], [])
|
|
self.assertLess(
|
|
len(json.dumps(result["library"], ensure_ascii=False).encode()), 32 * 1024
|
|
)
|
|
|
|
def test_absent_unreadable_invalid_workflow_and_formats_remain_distinct(self):
|
|
for read, status in (
|
|
(None, "unreadable"),
|
|
(inspection({}), "absent"),
|
|
(inspection({}, formats={"workflow": "invalid"}), "available"),
|
|
):
|
|
result = json.loads(encode(read))
|
|
self.assertEqual(result["metadata"]["status"], status)
|
|
self.assertEqual(result["metadata"]["raw"], {})
|
|
self.assertIsNone(result["metadata"]["parameters"])
|
|
self.assertEqual(result["metadata"]["workflow_graph"]["status"], "malformed")
|
|
|
|
def test_video_null_role_marker_raises_before_successful_read(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory) / "clip.mp4"
|
|
path.write_bytes(b"fixture")
|
|
for field in ("prompt", "parameters", EXECUTION_KEY):
|
|
for reader in (read_inspection_file, read_inspection_metadata):
|
|
process = MagicMock()
|
|
process.stdout = io.BytesIO(
|
|
json.dumps(
|
|
{"format": {"tags": {"comment": json.dumps({field: None})}}}
|
|
).encode()
|
|
)
|
|
process.poll.return_value = 0
|
|
process.returncode = 0
|
|
with (
|
|
patch(
|
|
"utils.prompt_inspector.ffprobe_path",
|
|
return_value="/tool/ffprobe",
|
|
),
|
|
patch(
|
|
"utils.prompt_inspector.subprocess.Popen",
|
|
return_value=process,
|
|
),
|
|
):
|
|
with self.assertRaises(MetadataReadError):
|
|
reader(path)
|
|
self.assertTrue(process.stdout.closed)
|
|
|
|
def test_original_role_projection_ignores_workflow_without_losing_saved_warning(
|
|
self,
|
|
):
|
|
self.assertEqual(
|
|
inspect_prompt_roles({"workflow": "{}"}, {"prompt_id": 1})["positive"][
|
|
"source"
|
|
],
|
|
"saved-association",
|
|
)
|
|
|
|
def test_unrepresentable_decimal_exponent_preserves_roles_and_raw(self):
|
|
text = graph_text().replace(
|
|
"6.5000000000000000001", "1e1000000000000000000000000"
|
|
)
|
|
for read in (
|
|
inspection({"prompt": text}),
|
|
inspection(
|
|
{"prompt": json.dumps(json.loads(text))},
|
|
formats={"prompt": "reserialized-json"},
|
|
carrier='{"prompt":' + text + "}",
|
|
),
|
|
):
|
|
result = json.loads(encode(read))
|
|
self.assertEqual(
|
|
result["metadata"]["prompt_graph"],
|
|
{"status": "malformed", "value": None},
|
|
)
|
|
self.assertEqual(result["metadata"]["raw"], read.metadata)
|
|
self.assertEqual(result["prompts"], PROMPTS)
|
|
|
|
def test_history_identity_shares_rooted_evidence_and_library_projection(self):
|
|
from utils.inspection_response import encode_history_inspection_response
|
|
|
|
read = inspection({"prompt": graph_text()})
|
|
identity = {
|
|
"occurrence": {
|
|
"run_id": "run",
|
|
"output_node_id": "node",
|
|
"image_ordinal": 2,
|
|
},
|
|
"reference": {"filename": "image.png"},
|
|
"history_epoch": "epoch",
|
|
"current_scope": "scope",
|
|
"location_key": "location",
|
|
"file_identity": "unverified",
|
|
}
|
|
body = json.loads(
|
|
encode_history_inspection_response(
|
|
current_output=identity,
|
|
file={"size": 7, "observation": "current-file"},
|
|
prompts=PROMPTS,
|
|
resolution={"state": "resolved"},
|
|
inspection=read,
|
|
library={"status": "linked", "tags": ["猫" * 12000]},
|
|
)
|
|
)
|
|
rooted = json.loads(
|
|
encode(
|
|
read,
|
|
file={"size": 7, "observation": "current-file"},
|
|
library={"status": "linked", "tags": ["猫" * 12000]},
|
|
)
|
|
)
|
|
self.assertEqual(body["current_output"], identity)
|
|
self.assertEqual(body.get("history_version"), 1)
|
|
self.assertEqual(body.get("history_epoch"), "epoch")
|
|
for key in ("file", "prompts", "metadata", "library"):
|
|
self.assertEqual(body[key], rooted[key])
|
|
self.assertEqual(body["library"]["status"], "limited")
|
|
self.assertNotIn("image", body)
|
|
self.assertNotIn("root_scope", body)
|