feat(inspect): server-identical local projection and payload

Port utils/inspection_response.py to the browser: a strict parse with Python's
Decimal spelling, duplicate-key rejection, NaN/Infinity as null and integers
past 2^53-1 as text; the prompt, workflow and carrier projection with the
depth-64 and 20,000-node ceilings; _metadata; and the 8 MiB omission steps
(workflow graph, then prompt graph, then all raw) measured as UTF-8 bytes.

local-inspection.mjs builds the inspection_version 1 wire for one chosen File
from the Task 1 and Task 2 readers (observation selected-local-file, interim
unresolved roles, unavailable library), fits it to the response budget, and
exposes localFileAdapter for the shared controller, inspector and viewer:
handles are session-owned by a private token, so a tampered or hand-made
descriptor is rejected and blob: URLs are never resolved through the API
client. projectInspectionPayload admits selected-local-file only for a
local-file identity.

A Python oracle test compares projectMetadata with _metadata byte for byte over
the parity PNG corpus, the committed video fixtures and tag maps, 75 committed
adversarial record sets (projection-cases.json) and node-ceiling sets built at
test time.
This commit is contained in:
Vito Sansevero
2026-10-22 07:16:39 -07:00
parent 2133f715e7
commit 58157052c2
10 changed files with 2266 additions and 24 deletions
+15
View File
@@ -67,6 +67,21 @@ for f in core-faststart.mp4 core-moov-end.mp4 core.mov core.m4v core.mkv core-ta
done
```
# Local Inspect projection cases
`local-inspect/projection-cases.json` holds synthetic record sets (prompt, workflow,
parameters and pair-record texts, optional `record_formats`, a video `carrier_text`
and `limited` records) that exercise every branch of the server's inspection
projection: Python's Decimal spellings and exact-construction limits, the 4300-digit
integer limit, duplicate keys, NaN/Infinity, the depth-64 and node ceilings, prompt
node key filtering and the carrier rule. `records: null` stands for an unreadable
file. `expect` pins both graph statuses; `needs` names an interpreter feature a case
depends on. `tests/js/workspace-local-projection.test.js` runs them through
`web/js/workspace/local-projection.mjs`, and `tests/test_local_inspect_parity.py`
asserts that its answer equals `utils.inspection_response._metadata` byte for byte.
All texts are original synthetic content, dedicated to the public domain under
CC0-1.0.
# Inspection parity fixtures
`inspection_parity/` holds small synthetic PNGs (`f01b.png` ... `f24.png`, each
+849
View File
@@ -0,0 +1,849 @@
{
"version": 1,
"about": "Synthetic record sets for utils/inspection_response.py _metadata parity with web/js/workspace/local-projection.mjs projectMetadata. records null = an unreadable file. record_formats default to original-text for each record. expect pins both graph statuses; needs names an interpreter feature the Python side must have.",
"cases": [
{
"name": "unreadable_read",
"records": null,
"expect": {
"prompt_graph": "absent",
"workflow_graph": "absent"
}
},
{
"name": "absent_records",
"records": {},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "absent"
}
},
{
"name": "prompt_and_workflow_parsed",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "all_four_records",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}",
"parameters": "cat\nNegative prompt: dog\nSteps: 20, Sampler: Euler, Seed: 7",
"comfyassets_prompt_pairs": "{\"version\":1,\"nodes\":{}}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "parameters_only",
"records": {
"parameters": "cat\nSteps: 20"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "absent"
}
},
{
"name": "number_spellings",
"records": {
"workflow": "{\"nodes\":[],\"a\":[1e3,1e0,-0.0,6.50,0.0100,1.0e2,0e5,0.0000001,0.000001,1E+3,1e-0,0.5e1,0.05e1,0.0e0,1.5e+300,123e-8,2.5e-3,1.0,-1.50,0.0,100.0,1234567e-6,0.5E-6,0.5E-5,1e22,0.000000,-0.000000e-3,7.0e-6,7.0e-7,12345678901234567890.123,9007199254740992.0]}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "integer_spellings",
"records": {
"workflow": "{\"a\":[9007199254740991,-9007199254740991,9007199254740992,-9007199254740992,10000000000000000,123456789012345678901234567890,0,-0,42,-7,100]}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "nonfinite_literals_are_null",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"n\":NaN,\"i\":Infinity,\"m\":-Infinity},\"_meta\":{\"n\":NaN}}}",
"workflow": "{\"a\":[NaN,Infinity,-Infinity],\"b\":NaN}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "exponent_edges_valid",
"records": {
"workflow": "{\"a\":[1e999999999999999999,1e-1999999999999999997,0.0e1000000000000000000,1.5e999999999999999999,0.1e-1999999999999999996]}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "exponent_edge_invalid_1e1000000000000000000",
"records": {
"workflow": "{\"a\":1e1000000000000000000}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_15e999999999999999999",
"records": {
"workflow": "{\"a\":15e999999999999999999}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_1e_1999999999999999998",
"records": {
"workflow": "{\"a\":1e-1999999999999999998}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_1_5e_1999999999999999997",
"records": {
"workflow": "{\"a\":1.5e-1999999999999999997}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_0e1000000000000000000",
"records": {
"workflow": "{\"a\":0e1000000000000000000}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_1e9223372036854775808",
"records": {
"workflow": "{\"a\":1e9223372036854775808}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_0_0e1000000000000000001",
"records": {
"workflow": "{\"a\":0.0e1000000000000000001}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "exponent_edge_invalid_1e99999999999999999999999999",
"records": {
"workflow": "{\"a\":1e99999999999999999999999999}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "int_4300_digits_kept",
"records": {
"workflow": "{\"a\":7777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "int_4301_digits_malformed",
"records": {
"workflow": "{\"a\":77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777}"
},
"needs": "int-max-str-digits",
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "int_negative_4301_digits_malformed",
"records": {
"workflow": "{\"a\":-77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777}"
},
"needs": "int-max-str-digits",
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "string_escapes",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"t\":\"é😀\\u0000\\u001f/\\\\\\\"\\b\\f\\n\\r\\t\",\"raw\":\"é😀 — 
\"}}}",
"workflow": "{\"s\":\"\\u00E9\\uD83D\\uDE00\\uD800\\uDC00x\\u0000\\u001f\\/\\\\\\\"\\b\\f\\n\\r\\t\\uD800\\u0041\\uDC00\"}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "lone_surrogate_in_raw_text",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"t\":\"𐏿\\udc00\"}}}",
"parameters": "\ud800 lone\nSteps: 1"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "keys_with_escapes_proto_and_order",
"records": {
"prompt": "{\"10\":{\"class_type\":\"B\",\"inputs\":{}},\"3\":{\"class_type\":\"A\",\"inputs\":{}},\"__proto__\":{\"class_type\":\"P\",\"inputs\":{\"constructor\":1}},\"constructor\":{\"class_type\":\"C\",\"inputs\":{}},\"\\u0000\":5,\"\":[]}",
"workflow": "{\"10\":1,\"3\":2,\"b\":3,\"a\":4,\"__proto__\":{\"x\":1},\"\":{}}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "duplicate_key_top_level",
"records": {
"workflow": "{\"a\":1,\"a\":2}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "duplicate_key_nested",
"records": {
"workflow": "{\"a\":{\"b\":[{\"c\":1,\"c\":1}]}}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "duplicate_key_in_dropped_prompt_meta",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"a\":1},\"_meta\":{\"t\":1,\"t\":2}}}"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "duplicate_key_in_prompt_inputs",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"a\":1,\"a\":1}}}"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "duplicate_key_after_deep_nest",
"records": {
"workflow": "{\"x\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],\"a\":1,\"a\":2}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "workflow_depth_64_kept",
"records": {
"workflow": "{\"a\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "workflow_depth_65_limited",
"records": {
"workflow": "{\"a\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "limited"
}
},
{
"name": "prompt_inputs_depth_64_kept",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"v\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "prompt_inputs_depth_65_limited",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"v\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}"
},
"expect": {
"prompt_graph": "limited",
"workflow_graph": "absent"
}
},
{
"name": "prompt_dropped_meta_depth_65_limited",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{},\"_meta\":{\"v\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}"
},
"expect": {
"prompt_graph": "limited",
"workflow_graph": "absent"
}
},
{
"name": "prompt_non_object_nodes_kept",
"records": {
"prompt": "{\"1\":5,\"2\":[1,2.50,\"x\",null,true],\"3\":\"s\",\"4\":null,\"5\":{\"inputs\":[1],\"class_type\":null,\"other\":{\"deep\":[NaN]}}}"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "prompt_root_array_malformed",
"records": {
"prompt": "[]"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "prompt_root_string_malformed",
"records": {
"prompt": "\"{}\""
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "prompt_root_number_malformed",
"records": {
"prompt": "5"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "prompt_root_null_malformed",
"records": {
"prompt": "null"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "workflow_root_array_malformed",
"records": {
"workflow": "[1,2]"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "workflow_root_nan_malformed",
"records": {
"workflow": "NaN"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "empty_text_malformed",
"records": {
"prompt": "",
"workflow": " "
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "bom_malformed",
"records": {
"workflow": "{}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "malformed"
}
},
{
"name": "trailing_data_malformed",
"records": {
"workflow": "{\"a\":1} x",
"prompt": "{}\u0000"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "python_whitespace_only",
"records": {
"workflow": " \t\r\n{\n\t\"a\" :\r 1 }\n",
"prompt": "\u000b{}"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "parsed"
}
},
{
"name": "syntax_errors",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{},}}",
"workflow": "[1,]"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "bad_escape_and_control_char",
"records": {
"prompt": "{\"1\":\"\\x\"}",
"workflow": "{\"a\":\"\u0001\"}"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "number_grammar",
"records": {
"prompt": "{\"1\":01}",
"workflow": "{\"a\":1.,\"b\":.5}"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "nodes_not_list_counts_zero",
"records": {
"workflow": "{\"nodes\":{\"a\":1},\"definitions\":{\"subgraphs\":[{\"nodes\":5},7,{\"nodes\":[1,2]}]}}"
},
"expect": {
"prompt_graph": "absent",
"workflow_graph": "parsed"
}
},
{
"name": "limited_workflow_record",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"limited": [
"workflow"
],
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "limited"
}
},
{
"name": "limited_workflow_without_text",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"limited": [
"workflow"
],
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "limited"
}
},
{
"name": "invalid_workflow_format",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"record_formats": {
"prompt": "original-text",
"workflow": "invalid"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "malformed"
}
},
{
"name": "reserialized_without_carrier_is_limited",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"record_formats": {
"prompt": "reserialized-json",
"workflow": "reserialized-json"
},
"expect": {
"prompt_graph": "limited",
"workflow_graph": "limited"
}
},
{
"name": "unknown_format_dropped_but_projected",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"record_formats": {
"prompt": "weird"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "non_text_record_dropped",
"records": {
"prompt": 5,
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"record_formats": {
"prompt": "original-text",
"workflow": "original-text"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "parsed"
}
},
{
"name": "format_without_text_is_malformed",
"records": {},
"record_formats": {
"prompt": "original-text"
},
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "unknown_record_keys_ignored",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"Software": "ComfyUI",
"WORKFLOW": "{\"nodes\":[],\"version\":0.4}"
},
"record_formats": {
"prompt": "original-text",
"Software": "original-text",
"WORKFLOW": "original-text"
},
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_string_workflow_object",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\": [], \"v\": 1}"
},
"record_formats": {
"prompt": "original-text",
"workflow": "reserialized-json"
},
"carrier_text": "{\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\",\"workflow\":{\"nodes\":[],\"v\":1}}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "carrier_prompt_object_projected",
"records": {
"prompt": "{\"1\": {\"class_type\": \"A\", \"inputs\": {\"x\": 8.0}, \"_meta\": {\"t\": \"x\"}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"record_formats": {
"prompt": "reserialized-json",
"workflow": "original-text"
},
"carrier_text": "{\"prompt\":{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":8.0},\"_meta\":{\"t\":\"x\"}}},\"workflow\":{\"nodes\":[],\"version\":0.4},\"title\":\"n\",\"extra\":[1e3]}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "parsed"
}
},
{
"name": "carrier_root_array_fails_all",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"carrier_text": "[1]",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "malformed"
}
},
{
"name": "carrier_duplicate_key_fails_all",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"carrier_text": "{\"prompt\":\"{}\",\"prompt\":\"{}\"}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_other_member_too_deep_limited",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"carrier_text": "{\"title\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\"}",
"expect": {
"prompt_graph": "limited",
"workflow_graph": "absent"
}
},
{
"name": "carrier_other_member_depth_64_kept",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"carrier_text": "{\"title\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]],\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\"}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_object_depth_shift",
"records": {
"prompt": "x"
},
"record_formats": {
"prompt": "reserialized-json"
},
"carrier_text": "{\"prompt\":{\"1\":{\"class_type\":\"A\",\"inputs\":{\"v\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_object_depth_shift_limited",
"records": {
"prompt": "x"
},
"record_formats": {
"prompt": "reserialized-json"
},
"carrier_text": "{\"prompt\":{\"1\":{\"class_type\":\"A\",\"inputs\":{\"v\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}}",
"expect": {
"prompt_graph": "limited",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_string_invalid_json",
"records": {
"prompt": "{",
"workflow": "{\"nodes\":[],\"version\":0.4}"
},
"carrier_text": "{\"prompt\":\"{\",\"workflow\":\"{\\\"nodes\\\":[],\\\"version\\\":0.4}\"}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "parsed"
}
},
{
"name": "carrier_prompt_string_too_deep",
"records": {
"prompt": "x"
},
"carrier_text": "{\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"v\\\":[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]}}}\"}",
"expect": {
"prompt_graph": "limited",
"workflow_graph": "absent"
}
},
{
"name": "carrier_workflow_number_malformed",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}",
"workflow": "5"
},
"record_formats": {
"prompt": "original-text",
"workflow": "original-text"
},
"carrier_text": "{\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\",\"workflow\":5}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "malformed"
}
},
{
"name": "carrier_prompt_nonfinite_malformed",
"records": {
"prompt": "NaN"
},
"carrier_text": "{\"prompt\":NaN}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_bigint_malformed",
"records": {
"prompt": "123456789012345678901"
},
"carrier_text": "{\"prompt\":123456789012345678901}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_prompt_decimal_malformed",
"records": {
"prompt": "1.5"
},
"carrier_text": "{\"prompt\":1.5}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_missing_member_malformed",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"carrier_text": "{\"workflow\":{}}",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
},
{
"name": "carrier_without_graph_records",
"records": {
"parameters": "cat"
},
"carrier_text": "{\"parameters\":\"cat\"}",
"expect": {
"prompt_graph": "absent",
"workflow_graph": "absent"
}
},
{
"name": "carrier_with_limited_workflow",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"limited": [
"workflow"
],
"carrier_text": "{\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\"}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "limited"
}
},
{
"name": "carrier_invalid_workflow_format",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"record_formats": {
"prompt": "original-text",
"workflow": "invalid"
},
"carrier_text": "{\"prompt\":\"{\\\"1\\\":{\\\"class_type\\\":\\\"A\\\",\\\"inputs\\\":{\\\"x\\\":1}}}\",\"workflow\":null}",
"expect": {
"prompt_graph": "parsed",
"workflow_graph": "malformed"
}
},
{
"name": "carrier_non_string_text_malformed",
"records": {
"prompt": "{\"1\":{\"class_type\":\"A\",\"inputs\":{\"x\":1}}}"
},
"carrier_text": "",
"expect": {
"prompt_graph": "malformed",
"workflow_graph": "absent"
}
}
]
}
+35 -21
View File
@@ -1,14 +1,19 @@
// The browser's local readers run over files on disk, for
// The browser's local readers and projection run over files on disk, for
// tests/test_local_inspect_parity.py, which compares every answer with the server's
// utils.prompt_inspector.read_inspection_file on the same file. Each file is opened
// as a Blob (fs.openAsBlob) so the reader slices it exactly as it slices a File.
// stdin: [{name, path} | {name, tags, limited?}]
// stdout: {name: {classified, result}} where result is readPngMetadataFile's
// utils.prompt_inspector.read_inspection_file and utils.inspection_response._metadata
// on the same file. Each file is opened as a Blob (fs.openAsBlob) so the reader
// slices it exactly as it slices a File.
// stdin: [{name, path} | {name, tags, limited?} | {name, records, record_formats?, carrier_text?, limited?}]
// stdout: {name: {classified, result, metadata}} where result is readPngMetadataFile's
// answer for a PNG, readImageHeader's for another image, for a video
// {read: readVideoTags(...), selection: selectVideoCarrier(read.tags, {limited})}
// (selection null unless the read completed), else null.
// A {tags} entry answers {classified: null, result: {selection}} for the
// carrier rule alone, as the server selects from ffprobe's tag map.
// (selection null unless the read completed), else null; metadata is
// projectMetadata(...) of the local read (the server's _metadata) for every
// classified file, else null.
// A {tags} entry answers {classified: null, result: {selection}, metadata} for
// the carrier rule alone, as the server selects from ffprobe's tag map.
// A {records} entry (records null = an unreadable file) answers
// {classified: null, result: null, metadata} for the projection alone.
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
@@ -16,28 +21,37 @@ import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const { classifyPrefix, readPngMetadataFile, readImageHeader } = require(path.join(here, '..', '..', '..', 'web', 'js', 'png-metadata.js'));
const { readVideoTags, selectVideoCarrier } = await import(path.join(here, '..', '..', '..', 'web', 'js', 'workspace', 'local-video.mjs'));
const pngMetadata = require(path.join(here, '..', '..', '..', 'web', 'js', 'png-metadata.js'));
const web = path.join(here, '..', '..', '..', 'web', 'js', 'workspace');
const { selectVideoCarrier } = await import(path.join(web, 'local-video.mjs'));
const { projectMetadata } = await import(path.join(web, 'local-projection.mjs'));
const { readLocalFile, toInspectionRead } = await import(path.join(web, 'local-inspection.mjs'));
const selectionRead = (selection) => selection.status === 'complete' ?
{ records: selection.records, record_formats: selection.record_formats, carrierText: selection.carrierText, limited: selection.limited } : null;
const chunks = [];
for await (const chunk of process.stdin) chunks.push(chunk);
const output = {};
for (const entry of JSON.parse(Buffer.concat(chunks).toString('utf8'))) {
const { name } = entry;
if (entry.records !== undefined) {
const read = entry.records === null ? null : {
records: entry.records,
record_formats: entry.record_formats ?? Object.fromEntries(Object.keys(entry.records).map((key) => [key, 'original-text'])),
carrierText: entry.carrier_text ?? null, limited: entry.limited ?? [],
};
output[name] = { classified: null, result: null, metadata: projectMetadata(read) };
continue;
}
if (entry.path === undefined) {
output[name] = { classified: null, result: { selection: selectVideoCarrier(entry.tags, { limited: entry.limited || [] }) } };
const selection = selectVideoCarrier(entry.tags, { limited: entry.limited || [] });
output[name] = { classified: null, result: { selection }, metadata: projectMetadata(selectionRead(selection)) };
continue;
}
const file = await fs.openAsBlob(entry.path);
const prefix = new Uint8Array(await file.slice(0, Math.min(32, file.size)).arrayBuffer());
const classified = classifyPrefix(prefix);
let result = null;
if (classified?.kind === 'png') result = await readPngMetadataFile(file, { prefix });
else if (classified?.media === 'image') result = await readImageHeader(file, classified.kind);
else if (classified?.media === 'video') {
const read = await readVideoTags(file, { prefix, kind: classified.kind });
result = { read, selection: read.status === 'complete' ? selectVideoCarrier(read.tags, { limited: read.limited }) : null };
}
output[name] = { classified, result };
const { classification, result } = await readLocalFile(file, { pngMetadata });
const metadata = classification === null ? null : projectMetadata(toInspectionRead(classification, result).read);
output[name] = { classified: classification, result, metadata };
}
process.stdout.write(JSON.stringify(output));
+308
View File
@@ -0,0 +1,308 @@
// web/js/workspace/local-inspection.mjs turns one chosen File's local reads into
// the inspection wire the workspace already renders, fits it into the 8 MiB
// response budget exactly as the server does, and plugs into the shared
// inspection controller, inspector and viewer through localFileAdapter.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { raw, ihdr, idat, iend, tEXt, iTXt, utf8 } from "./helpers/png-bytes.mjs";
import { createInspectionController, toInspectionRecord, projectInspectionPayload } from "../../web/js/workspace/inspection-state.mjs";
import { inspectionDownloads } from "../../web/js/workspace/file-inspector.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const PngMetadata = require("../../web/js/png-metadata.js");
const ComfyMetadata = require("../../web/js/comfy-metadata.js");
const { extractGenerationFacts } = ComfyMetadata;
// The page loads both as scripts; the legacy fill and the PNG reader find them on the global.
globalThis.PngMetadata = PngMetadata;
globalThis.ComfyMetadata = ComfyMetadata;
const m = await import("../../web/js/workspace/local-inspection.mjs").catch((error) => {
if (error.code !== "ERR_MODULE_NOT_FOUND") throw error;
return {};
});
const FIXTURES = path.join(here, "..", "fixtures", "local-inspect");
const PARITY = path.join(here, "..", "fixtures", "inspection_parity");
const PROMPT = '{"3":{"class_type":"KSampler","inputs":{"seed":7,"steps":20,"cfg":8.0,"sampler_name":"euler","scheduler":"normal","denoise":1.0,"model":["4",0],"positive":["6",0],"negative":["7",0],"latent_image":["5",0]}},"4":{"class_type":"CheckpointLoaderSimple","inputs":{"ckpt_name":"synthetic-v1.safetensors"}},"5":{"class_type":"EmptyLatentImage","inputs":{"width":512,"height":512,"batch_size":1}},"6":{"class_type":"CLIPTextEncode","inputs":{"text":"a red fox in snow, detailed fur","clip":["4",1]}},"7":{"class_type":"CLIPTextEncode","inputs":{"text":"blurry, lowres","clip":["4",1]}}}';
const WORKFLOW = '{"last_node_id":7,"nodes":[{"id":3,"type":"KSampler","widgets_values":[7,"fixed",20,8.0,"euler","normal",1.0]}],"links":[],"version":0.4}';
const CONTAINER_WORKFLOW = "Original workflow JSON text is unavailable for this container record. Download metadata or the original file.";
const LIMIT = 8388608;
const plain = (value) => JSON.parse(JSON.stringify(value));
const bytes = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
function pngFile(name, chunks, { lastModified = 1_700_000_000_123 } = {}) {
return new File([raw(ihdr(640, 480), idat(), ...chunks, iend())], name, { type: "image/png", lastModified });
}
const fixtureFile = (name, directory = FIXTURES) => new File([fs.readFileSync(path.join(directory, name))], name, { lastModified: 1_700_000_000_000 });
const classify = async (file) => PngMetadata.classifyPrefix(new Uint8Array(await file.slice(0, 32).arrayBuffer()));
async function handleFor(file, { url = "blob:http://host.test/11111111-2222-3333-4444-555555555555", filename } = {}) {
return m.createLocalFileHandle({ file, url, classification: await classify(file), filename });
}
async function recordFor(file, options) {
const handle = await handleFor(file, options);
const wire = await m.localFileAdapter.read(handle);
return { handle, wire, record: m.localFileAdapter.toRecord(wire, handle, extractGenerationFacts) };
}
test("contract", () => {
for (const name of ["buildLocalPayload", "fitLocalPayload", "createLocalFileHandle", "readLocalFile", "toInspectionRead", "sanitizeLocalFilename"]) {
assert.equal(typeof m[name], "function", `${name} is exported`);
}
assert.equal(typeof m.localFileAdapter, "object");
for (const name of ["validate", "key", "scope", "inspectionPath", "read", "toRecord", "originalDownload", "mediaUrl"]) {
assert.equal(typeof m.localFileAdapter[name], "function", `adapter.${name}`);
}
assert.ok(Object.isFrozen(m.localFileAdapter));
});
test("a PNG File becomes the server-shaped wire with local provenance", async () => {
const file = pngFile("Fox Render.PNG", [tEXt("prompt", PROMPT), tEXt("workflow", WORKFLOW)]);
const { wire, record, handle } = await recordFor(file);
assert.deepEqual(wire.file, { observation: "selected-local-file", source_label: "Selected local file", media_type: "image", extension: ".png", format: "PNG",
size: file.size, width: 640, height: 480, modified_time: 1_700_000_000.123 });
assert.equal(wire.success, true);
assert.equal(wire.inspection_version, 1);
assert.deepEqual(wire.prompts, { positive: { status: "unresolved" }, negative: { status: "unresolved" } });
assert.deepEqual(wire.resolution, { state: "unresolved", sampler_id: null, reason: "Resolved in this browser from the workflow.", reason_code: null, node: null });
assert.deepEqual(wire.library, { status: "unavailable", scope: "saved-library", tags: [], category_scope: null, tags_scope: null });
assert.equal(wire.metadata.status, "available");
assert.deepEqual(wire.metadata.raw, { prompt: PROMPT, workflow: WORKFLOW });
assert.deepEqual(wire.metadata.record_formats, { prompt: "original-text", workflow: "original-text" });
assert.equal(wire.metadata.prompt_graph.status, "parsed");
assert.equal(plain(wire.metadata.prompt_graph.value)["3"].inputs.cfg, "8.0");
assert.equal(wire.metadata.workflow_graph.status, "parsed");
assert.equal(wire.metadata.parameters, null);
for (const key of Object.keys(wire)) assert.ok(["success", "inspection_version", "file", "prompts", "resolution", "metadata", "library"].includes(key), key);
// The existing projection renders it: facts from the API graph, legacy fill for the roles, no library link.
assert.deepEqual(record.identity, { kind: "local-file", key: handle.key, path: "Fox Render.PNG" });
assert.equal(record.file.observation, "selected-local-file");
assert.equal(record.generation.seed.value, "7");
assert.equal(record.generation.cfg.value, "8.0");
assert.equal(record.prompts.positive.text, "a red fox in snow, detailed fur");
assert.equal(record.prompts.positive.source, "graph-inferred");
assert.equal(record.prompts.negative.text, "blurry, lowres");
assert.equal(record.historyTarget, null);
assert.deepEqual(record.download, { url: "blob:http://host.test/11111111-2222-3333-4444-555555555555", filename: "Fox Render.PNG" });
const downloads = inspectionDownloads(record);
assert.equal(downloads.metadata.available, true);
assert.equal(downloads.workflow.bodyText, WORKFLOW);
assert.equal(downloads.metadata.filename, "Fox Render.metadata.json");
const exported = JSON.parse(downloads.metadata.bodyText);
assert.equal(exported.identity.kind, "local-file");
assert.equal(exported.file.source_label, "Selected local file");
});
test("file facts follow the server: extension like Path.suffix, null when unknown", async () => {
const cases = [["a.b.PnG", ".png"], [".hidden", ""], ["noext", ""], ["dot.", ""], ["x.tar.gz", ".gz"]];
for (const [name, extension] of cases) {
const wire = m.buildLocalPayload({ read: { status: "complete", records: {}, limited: [], width: 2, height: 3 }, file: { name, size: 10, lastModified: 0 }, classification: { media: "image", kind: "png" } });
assert.equal(wire.file.extension, extension, name);
assert.equal(wire.file.modified_time, 0);
}
const sparse = m.buildLocalPayload({ read: { status: "complete", records: {}, limited: [], width: 2, height: 3 }, file: { name: "a.png" }, classification: { media: "image", kind: "png" } });
assert.deepEqual([sparse.file.size, sparse.file.modified_time], [null, null]);
assert.equal(m.buildLocalPayload({ read: { status: "complete", records: {}, limited: [], width: 2, height: 3 }, file: { name: "a.png", size: -1, lastModified: NaN }, classification: { media: "image", kind: "png" } }).file.size, null);
assert.throws(() => m.buildLocalPayload({ read: null, file: { name: "a" }, classification: null }), TypeError);
assert.throws(() => m.buildLocalPayload({ read: null, file: { name: "a" }, classification: { media: "text", kind: "svg" } }), TypeError);
});
test("sanitized filenames carry no separators or control characters", () => {
assert.equal(m.sanitizeLocalFilename("../a/b\\c\u0000\u001f\u007f.png"), ".._a_b_c.png");
assert.equal(m.sanitizeLocalFilename(" spaced .png "), "spaced .png");
assert.equal(m.sanitizeLocalFilename(""), "file");
assert.equal(m.sanitizeLocalFilename(undefined), "file");
assert.equal(m.sanitizeLocalFilename("\u0000"), "file");
assert.equal([...m.sanitizeLocalFilename("😀".repeat(300))].length, 255);
});
test("unreadable PNG metadata is unreadable, not absent", async () => {
const { wire, record } = await recordFor(pngFile("bad.png", [tEXt("prompt", PROMPT, { crc: 0 })]));
assert.equal(wire.metadata.status, "unreadable");
assert.deepEqual([wire.file.format, wire.file.width, wire.file.height], [null, null, null]);
assert.equal(record.generation.seed.reason, "metadata-unreadable");
assert.equal(record.prompts.positive.status, "unresolved");
assert.equal(JSON.parse(inspectionDownloads(record).metadata.bodyText).metadata.status, "unreadable");
const limited = await recordFor(pngFile("big.png", [tEXt("prompt", PROMPT), tEXt("workflow", "[" + "0,".repeat(2_097_153) + "]")]));
assert.deepEqual(limited.wire.metadata.record_formats, { prompt: "original-text", workflow: "limited" });
assert.equal(limited.wire.metadata.workflow_graph.status, "limited");
assert.equal(limited.record.metadata.record_formats.workflow, "limited");
});
test("JPEG and WebP previews have header facts and absent metadata", async () => {
for (const [name, format, width, height] of [["tiny.jpg", "JPEG", 48, 32], ["tiny.webp", "WEBP", 40, 24]]) {
const { wire, record } = await recordFor(fixtureFile(name));
assert.deepEqual([wire.file.format, wire.file.width, wire.file.height, wire.file.media_type], [format, width, height, "image"], name);
assert.equal(wire.metadata.status, "absent");
assert.equal(record.generation.seed.status, "not-recorded");
}
});
test("video payloads", async () => {
const legacy = await recordFor(fixtureFile("legacy-comment.mp4"));
assert.deepEqual([legacy.wire.file.media_type, legacy.wire.file.format, legacy.wire.file.width, legacy.wire.file.extension], ["video", null, null, ".mp4"]);
assert.equal(legacy.wire.metadata.record_formats.workflow, "reserialized-json");
assert.equal(legacy.wire.metadata.record_formats.prompt, "original-text");
assert.equal(legacy.wire.metadata.workflow_graph.status, "parsed");
assert.equal(inspectionDownloads(legacy.record).workflow.available, false);
assert.equal(inspectionDownloads(legacy.record).workflow.reason, CONTAINER_WORKFLOW);
assert.equal(legacy.record.prompts.positive.text, "a red fox in snow, detailed fur");
// Ruling M1: a Matroska COMMENT tag is not the exact-case `comment` envelope, so it is absent, as Images reports it.
const webm = await recordFor(fixtureFile("legacy-comment.webm"));
assert.equal(webm.wire.metadata.status, "absent");
const tags = await recordFor(fixtureFile("core-tags.webm"));
assert.deepEqual(tags.wire.metadata.record_formats, { prompt: "original-text", workflow: "original-text" });
assert.equal(tags.record.generation.seed.value, "7");
// AVI and ASF are recognised, previewable, and never read.
const avi = new File([utf8("RIFF"), new Uint8Array([0, 0, 0, 0]), utf8("AVI LIST"), new Uint8Array(64)], "clip.avi");
const aviRecord = await recordFor(avi);
assert.equal(aviRecord.wire.metadata.status, "unreadable");
assert.equal(aviRecord.wire.file.media_type, "video");
assert.equal(aviRecord.record.generation.seed.reason, "metadata-unreadable");
const asf = new File([new Uint8Array([0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c]), new Uint8Array(64)], "clip.wmv");
assert.equal((await recordFor(asf)).wire.metadata.status, "unreadable");
// A pre-classified video needs no PNG reader; an image without one is an error, not a silent absence.
const clip = fixtureFile("core.mkv");
assert.equal((await m.readLocalFile(clip, { classification: { media: "video", kind: "matroska" }, pngMetadata: null })).result.read.status, "complete");
await assert.rejects(m.readLocalFile(clip, { pngMetadata: null }), /PNG reader/);
await assert.rejects(m.readLocalFile(fixtureFile("tiny.jpg"), { classification: { media: "image", kind: "jpeg" }, pngMetadata: null }), /PNG reader/);
await assert.rejects(m.readLocalFile("not a blob"), TypeError);
assert.deepEqual(await m.readLocalFile(new File([utf8("<svg/>")], "x.png"), { pngMetadata: PngMetadata }), { classification: null, result: null });
// A container that fails to parse is unreadable too.
const broken = new File([utf8("\0\0\0\x08ftypisom"), utf8("\0\0\0\x01moov")], "broken.mp4");
assert.equal((await recordFor(broken)).wire.metadata.status, "unreadable");
});
test("local cap measures escaped UTF-8 atomically", async () => {
// A 1 MiB Latin-1 prompt of 'é' (2 UTF-8 bytes, 1 code unit each) and a 4 MiB UTF-8 workflow of
// emoji (4 bytes, 2 units): the UTF-16 length of the wire is well under the limit while its UTF-8
// size is not, so a cap counting code units would keep too much.
const prompt = `{"1":{"class_type":"A","inputs":{"t":"${"é".repeat(1_000_000)}"}}}`;
const workflow = `{"nodes":[],"u":"${"😀".repeat(1_000_000)}"}`;
const file = pngFile("wide.png", [iTXt("workflow", workflow), tEXt("prompt", prompt)]);
const handle = await handleFor(file);
const raw = await m.readLocalFile(handle.file, { classification: handle.classification, pngMetadata: PngMetadata });
assert.equal(raw.result.status, "complete");
const wire = m.buildLocalPayload({ read: raw.result, file: { name: file.name, size: file.size, lastModified: file.lastModified }, classification: handle.classification });
const text = JSON.stringify(wire);
assert.ok(text.length < LIMIT && bytes(wire) > LIMIT, `${text.length} code units, ${bytes(wire)} bytes`);
const fitted = m.fitLocalPayload(wire);
assert.ok(bytes(fitted) <= LIMIT);
assert.equal(fitted.metadata.workflow_graph.status, "limited", "the workflow graph goes first");
assert.equal(fitted.metadata.prompt_graph.status, "parsed");
assert.deepEqual(Object.keys(fitted.metadata.raw).sort(), ["prompt", "workflow"], "raw is kept");
const record = m.localFileAdapter.toRecord(fitted, handle, extractGenerationFacts);
assert.equal(inspectionDownloads(record).metadata.available, true);
assert.equal(inspectionDownloads(record).workflow.available, false);
// Whole-raw omission is atomic and marks the response-size limit; the inspector offers no metadata download.
const squeezed = m.fitLocalPayload(wire, bytes(fitted) - 1);
assert.deepEqual([squeezed.metadata.workflow_graph.status, squeezed.metadata.prompt_graph.status], ["limited", "limited"]);
const tiny = m.fitLocalPayload(wire, 2000);
assert.equal(tiny.metadata.status, "limited");
assert.equal(tiny.metadata.omitted_reason, "response-size-limit");
assert.deepEqual(tiny.metadata.raw, {});
assert.equal(tiny.metadata.parameters, null);
assert.ok(bytes(tiny) <= 2000);
const omitted = m.localFileAdapter.toRecord(tiny, handle, extractGenerationFacts);
assert.equal(inspectionDownloads(omitted).metadata.available, false);
assert.equal(omitted.generation.seed.reason, "projection-limited");
assert.throws(() => m.fitLocalPayload(wire, 100), /exceeds/);
assert.equal(wire.metadata.status, "available", "the input wire is not mutated");
assert.equal((await m.localFileAdapter.read(handle)).metadata.workflow_graph.status, "limited", "adapter.read returns the fitted wire");
});
test("adapter rejects foreign blob URLs and never resolves owned ones", async () => {
const file = pngFile("a.png", [tEXt("prompt", PROMPT)]);
const classification = await classify(file);
const owned = m.createLocalFileHandle({ file, url: "blob:http://host.test/0b0e", classification });
assert.ok(Object.isFrozen(owned));
assert.deepEqual(m.localFileAdapter.originalDownload(owned), { url: "blob:http://host.test/0b0e", filename: "a.png" });
assert.equal(m.localFileAdapter.mediaUrl(owned), "blob:http://host.test/0b0e");
// The controller and the viewer session spread-copy descriptors: a copy is the same selection.
const copy = { ...owned, occurrence: {}, reference: {} };
assert.equal(m.localFileAdapter.key(copy), owned.key);
assert.equal(m.localFileAdapter.mediaUrl(copy), "blob:http://host.test/0b0e");
// A tampered URL, key, File or name, or a descriptor this module never made, is foreign.
const tampered = [{ ...owned, url: "blob:http://host.test/other" }, { ...owned, key: "local:1" }, { ...owned, file: new File([], "x.png") }, { ...owned, filename: "other.png" },
{ kind: "local-file", key: owned.key, filename: owned.filename, url: owned.url, file, is_video: false, classification }, null, "local:1"];
for (const foreign of tampered) {
for (const method of ["validate", "originalDownload", "mediaUrl", "key", "scope", "inspectionPath"]) assert.throws(() => m.localFileAdapter[method](foreign), TypeError, method);
await assert.rejects(m.localFileAdapter.read(foreign), TypeError);
assert.throws(() => m.localFileAdapter.toRecord({ success: true, inspection_version: 1 }, foreign, extractGenerationFacts), TypeError);
}
for (const url of ["http://host.test/x.png", "/prompt_manager/images/serve/a.png", "javascript:alert(1)", "data:image/png;base64,AA==", "", 5]) {
assert.throws(() => m.createLocalFileHandle({ file, url, classification }), TypeError, String(url));
}
assert.throws(() => m.createLocalFileHandle({ file: "not a blob", url: "blob:http://host.test/1", classification }), TypeError);
assert.throws(() => m.createLocalFileHandle({ file, url: "blob:http://host.test/1", classification: null }), TypeError);
assert.throws(() => m.createLocalFileHandle({ file, url: "blob:http://host.test/1", classification: { media: "text", kind: "svg" } }), TypeError);
// A proxy-mounted host resolves API URLs; blob URLs are session-owned and bypass it.
const before = Object.getOwnPropertyDescriptor(globalThis, "window");
Object.defineProperty(globalThis, "window", { configurable: true, value: { location: { href: "https://example.test/proxy/prompt_manager/workspace" }, ApiClient: { resolveUrl: (url) => `/proxy${url}` } } });
try {
assert.equal(m.localFileAdapter.mediaUrl(owned), "blob:http://host.test/0b0e");
assert.equal(m.localFileAdapter.originalDownload(owned).url, "blob:http://host.test/0b0e");
} finally {
if (before) Object.defineProperty(globalThis, "window", before); else delete globalThis.window;
}
// Without an object URL the record has no download and the viewer cannot show the original.
const previewless = m.createLocalFileHandle({ file, url: null, classification });
assert.equal(m.localFileAdapter.mediaUrl(previewless), null);
assert.throws(() => m.localFileAdapter.originalDownload(previewless), TypeError);
const record = m.localFileAdapter.toRecord(await m.localFileAdapter.read(previewless), previewless, extractGenerationFacts);
assert.equal(record.download, null);
assert.equal(inspectionDownloads(record).metadata.filename, "a.metadata.json");
});
test("select reuses the same local key", async () => {
const file = pngFile("a.png", [tEXt("prompt", PROMPT)]);
const classification = await classify(file);
const first = m.createLocalFileHandle({ file, url: "blob:http://host.test/1", classification });
const second = m.createLocalFileHandle({ file, url: "blob:http://host.test/2", classification });
assert.notEqual(first.key, second.key, "the same bytes chosen twice are two selections");
assert.equal(m.localFileAdapter.scope(first), "local");
assert.equal(m.localFileAdapter.inspectionPath(first), `local-file:${first.key}`);
assert.equal(m.localFileAdapter.key(first), first.key);
const reads = [];
// Until the loader seam lands (PR L2 Task 4) the controller reaches the adapter through api.get.
const api = { get: (sentinel) => { reads.push(sentinel); return m.localFileAdapter.read(reads.length === 1 || sentinel === `local-file:${first.key}` ? first : second); } };
const controller = createInspectionController({ api, extractFacts: extractGenerationFacts, adapter: m.localFileAdapter });
const pending = controller.select(first);
assert.equal(controller.select(first), pending, "the same handle returns the pending read");
await pending;
assert.deepEqual(reads, [`local-file:${first.key}`]);
assert.equal(controller.state.record.identity.key, first.key);
assert.equal(controller.state.record.prompts.positive.text, "a red fox in snow, detailed fur");
await controller.select(second);
assert.equal(reads.length, 2);
assert.equal(controller.state.record.identity.key, second.key);
await controller.select({ ...second, occurrence: {}, reference: {} });
assert.equal(reads.length, 2, "the controller's own copy is the same selection");
await controller.select({ ...second, url: "blob:http://host.test/evil" });
assert.equal(controller.state.error, true, "a foreign handle is published as an error, never read");
assert.equal(reads.length, 2);
controller.dispose();
});
test("fileFor admits selected-local-file only for a local identity", async () => {
const file = pngFile("a.png", [tEXt("prompt", PROMPT)]);
const { wire, handle } = await recordFor(file);
const rooted = { root_index: 0, path: "folder/a.png", filename: "a.png", url: "/prompt_manager/images/serve/folder/a.png?root=0" };
assert.throws(() => toInspectionRecord({ ...wire, image: { root_index: 0, path: "folder/a.png", filename: "a.png" } }, rooted, extractGenerationFacts), /Invalid inspection file/);
assert.throws(() => projectInspectionPayload({ ...wire, file: { ...wire.file, observation: "current-file" } }, { kind: "local-file", key: handle.key, path: "a.png" }, null, extractGenerationFacts), /Invalid inspection file/);
assert.throws(() => m.localFileAdapter.toRecord({ ...wire, success: false }, handle, extractGenerationFacts), TypeError);
assert.throws(() => m.localFileAdapter.toRecord({ ...wire, inspection_version: 2 }, handle, extractGenerationFacts), TypeError);
assert.throws(() => m.localFileAdapter.toRecord(null, handle, extractGenerationFacts), TypeError);
});
test("the parity corpus PNGs read into renderable records", async () => {
for (const name of ["f02.png", "f06a.png", "f23.png"]) {
const { wire, record } = await recordFor(fixtureFile(name, PARITY));
assert.equal(wire.metadata.status, "available", name);
assert.equal(record.file.format, "PNG");
assert.ok(["parsed", "limited", "malformed"].includes(wire.metadata.prompt_graph.status));
}
});
+234
View File
@@ -0,0 +1,234 @@
// web/js/workspace/local-projection.mjs ports utils/inspection_response.py: the
// strict Decimal/duplicate-key parse, the prompt/workflow/carrier projection, the
// depth and node ceilings, _metadata and the 8 MiB omission steps. These units pin
// the rules the plan names; tests/test_local_inspect_parity.py proves byte parity
// with the server over the committed corpus.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const m = await import("../../web/js/workspace/local-projection.mjs").catch((error) => {
if (error.code !== "ERR_MODULE_NOT_FOUND") throw error;
return {};
});
const CASES = JSON.parse(fs.readFileSync(path.join(here, "..", "fixtures", "local-inspect", "projection-cases.json"), "utf8")).cases;
const plain = (value) => JSON.parse(JSON.stringify(value));
const bytes = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
const graphOf = (text, kind = "workflow") => plain(m.projectGraph(text, kind));
const nested = (depth, close = "]", open = "[") => open.repeat(depth) + close.repeat(depth);
const nodes = (count) => `{${Array.from({ length: count }, (_, i) => `"${i}":{"class_type":"A","inputs":{}}`).join(",")}}`;
test("contract", () => {
for (const name of ["projectGraph", "projectMetadata", "fitInspectionPayload", "inspectionBytes", "parseStrictJson"]) {
assert.equal(typeof m[name], "function", `${name} is exported`);
}
assert.equal(m.MAX_RESPONSE_BYTES, 8388608);
assert.equal(m.MAX_PROJECTION_DEPTH, 64);
assert.equal(m.GRAPH_NODE_CEILING, 20000);
});
test("projection spells numbers like the server", () => {
// literal -> Python str(Decimal(literal)) (or the int when it is a safe integer)
const table = [
["1e3", "1E+3"], ["1e0", "1"], ["-0.0", "-0.0"], ["6.50", "6.50"], ["0.0100", "0.0100"], ["1.0e2", "1.0E+2"],
["0e5", "0E+5"], ["0.0000001", "1E-7"], ["0.000001", "0.000001"], ["1E+3", "1E+3"], ["1e-0", "1"], ["0.5e1", "5"],
["0.05e1", "0.5"], ["0.0e0", "0.0"], ["1.5e+300", "1.5E+300"], ["123e-8", "0.00000123"], ["2.5e-3", "0.0025"],
["1.0", "1.0"], ["-1.50", "-1.50"], ["0.0", "0.0"], ["100.0", "100.0"], ["1234567e-6", "1.234567"], ["0.5E-6", "5E-7"],
["0.5E-5", "0.000005"], ["1e22", "1E+22"], ["0.000000", "0.000000"], ["-0.000000e-3", "-0E-9"], ["7.0e-6", "0.0000070"],
["7.0e-7", "7.0E-7"], ["12345678901234567890.123", "12345678901234567890.123"], ["9007199254740992.0", "9007199254740992.0"],
["1e999999999999999999", "1E+999999999999999999"], ["1e-1999999999999999997", "1E-1999999999999999997"],
["0.0e1000000000000000000", "0E+999999999999999999"], ["1.5e999999999999999999", "1.5E+999999999999999999"],
["0.1e-1999999999999999996", "1E-1999999999999999997"],
["9007199254740991", 9007199254740991], ["-9007199254740991", -9007199254740991], ["9007199254740992", "9007199254740992"],
["-9007199254740992", "-9007199254740992"], ["10000000000000000", "10000000000000000"], ["123456789012345678901234567890", "123456789012345678901234567890"],
["0", 0], ["-0", 0], ["42", 42], ["-7", -7],
];
for (const [literal, expected] of table) {
const graph = graphOf(`{"n":${literal}}`);
assert.equal(graph.status, "parsed", literal);
assert.deepEqual(graph.value, { n: expected }, literal);
}
assert.deepEqual(graphOf('{"n":[NaN,Infinity,-Infinity]}').value, { n: [null, null, null] });
// A 4300-digit integer is Python's int() limit; one more digit fails the parse.
assert.deepEqual(graphOf(`{"n":${"1".repeat(4300)}}`).value, { n: "1".repeat(4300) });
assert.equal(graphOf(`{"n":${"1".repeat(4301)}}`).status, "malformed");
assert.equal(graphOf(`{"n":-${"1".repeat(4301)}}`).status, "malformed");
// Exact Decimal construction fails past the C library's exponent range.
for (const literal of ["1e1000000000000000000", "15e999999999999999999", "1e-1999999999999999998", "1.5e-1999999999999999997",
"0e1000000000000000000", "1e9223372036854775808", "0.0e1000000000000000001", "1e99999999999999999999999999"]) {
assert.equal(graphOf(`{"n":${literal}}`).status, "malformed", literal);
}
// Escapes: surrogate pairs join, lone surrogates stay, control characters decode.
const text = graphOf('{"s":"\\u00E9\\uD83D\\uDE00\\uD800\\u0000\\u001f\\/\\\\\\"\\b\\f\\n\\r\\t\\uD800\\u0041"}').value.s;
assert.equal(text, "é\u{1f600}\ud800\u0000\u001f/\\\"\b\f\n\r\t\ud800A");
assert.equal(JSON.stringify(m.projectGraph('{"s":"\\ud800"}', "workflow").value), '{"s":"\\ud800"}');
});
test("projection rejects what Python's json rejects and nothing else", () => {
const rejected = ["{}", "\f1", "  1", '{"a":1,}', "[1,]", "{'a':1}", '{"a"}', "[1 2]", "tru", "nul", "-NaN", "1.", ".5", "01", "+1",
"1e", "-", '"\\x"', '"abc', "", " ", '"\u0001"', '{"a":1}x', '"\\u12"', '{"a":1,"a":2}', '{"a":{"b":1,"b":1}}', "[1,[2,{\"k\":1,\"k\":2}]]",
'{"a":1 "b":2}', "[,1]", "{,}", '{"a":}', "[1}", "{1:2}", "
[]", "[]\u000b", "0x10", "1_000", "--1", "1e+", "1E-", '"\\uD800\\uDC"'];
for (const text of rejected) assert.equal(graphOf(text).status, "malformed", JSON.stringify(text));
for (const text of [" \t\r\n{}\n\t ", '{"a":[1,{"b":null}],"c":"d"}', '{"\\u0000":1}', '{"a":1,"A":1}', '{"":[]}']) {
assert.equal(graphOf(text).status, "parsed", JSON.stringify(text));
}
// Top-level values that are not objects parse but are not graphs.
for (const text of ["[]", '"x"', "1", "null", "true", "NaN", "1.5", "-Infinity"]) assert.equal(graphOf(text).status, "malformed", text);
assert.equal(graphOf(undefined).status, "malformed");
assert.equal(graphOf(null).status, "malformed");
});
test("projection limits", () => {
// A workflow's root is depth 1; the 64th nested container is kept, the 65th is limited.
assert.equal(graphOf(`{"a":${nested(63)}}`).status, "parsed");
assert.equal(graphOf(`{"a":${nested(64)}}`).status, "limited");
// Prompt: root 1, node 2, inputs 3; a value chain reaching depth 64 is kept, 65 is limited.
assert.equal(graphOf(`{"1":{"class_type":"A","inputs":{"v":${nested(61)}}}}`, "prompt").status, "parsed");
assert.equal(graphOf(`{"1":{"class_type":"A","inputs":{"v":${nested(62)}}}}`, "prompt").status, "limited");
// Dropped node keys are still validated for depth.
assert.equal(graphOf(`{"1":{"class_type":"A","inputs":{},"_meta":{"v":${nested(61)}}}}`, "prompt").status, "parsed");
assert.equal(graphOf(`{"1":{"class_type":"A","inputs":{},"_meta":{"v":${nested(62)}}}}`, "prompt").status, "limited");
// A workflow is normalized before its shape is checked, so a deep non-object is limited; a
// prompt checks its root first. Depth never overflows the parser (100,000 nested arrays).
assert.equal(graphOf(nested(10)).status, "malformed");
assert.equal(graphOf(nested(5000)).status, "limited");
assert.equal(graphOf(nested(5000), "prompt").status, "malformed");
assert.equal(graphOf(`{"a":${nested(100000)}}`).status, "limited");
// Node ceilings: 20,000 API nodes parse, 20,001 are limited.
assert.equal(graphOf(nodes(20000), "prompt").status, "parsed");
assert.equal(graphOf(nodes(20001), "prompt").status, "limited");
const list = (count) => `[${Array.from({ length: count }, () => "{}").join(",")}]`;
assert.equal(graphOf(`{"nodes":${list(20000)}}`).status, "parsed");
assert.equal(graphOf(`{"nodes":${list(20001)}}`).status, "limited");
assert.equal(graphOf(`{"nodes":${list(10001)},"definitions":{"subgraphs":[{"nodes":${list(10000)}},5,{"nodes":3}]}}`).status, "limited");
assert.equal(graphOf(`{"nodes":${list(10000)},"definitions":{"subgraphs":[{"nodes":${list(10000)}}]}}`).status, "parsed");
assert.equal(graphOf(`{"nodes":{"${"x".repeat(5)}":1},"definitions":[]}`).status, "parsed");
// Duplicate keys anywhere are malformed, including inside dropped node keys.
assert.equal(graphOf('{"1":{"class_type":"A","inputs":{},"_meta":{"t":1,"t":2}}}', "prompt").status, "malformed");
assert.equal(graphOf('{"1":{"class_type":"A","inputs":{"a":1,"a":1}}}', "prompt").status, "malformed");
});
test("prompt nodes keep only class_type and inputs on null-prototype objects", () => {
const text = '{"3":{"class_type":"KSampler","inputs":{"seed":9007199254740993,"cfg":8.0,"n":NaN},"_meta":{"title":"x"},"is_changed":[1]},"4":5,"5":[1,2.50],"__proto__":{"class_type":"P","inputs":{}},"constructor":"c"}';
const graph = m.projectGraph(text, "prompt");
assert.equal(graph.status, "parsed");
assert.equal(Object.getPrototypeOf(graph.value), null);
assert.equal(Object.getPrototypeOf(graph.value["3"]), null);
assert.equal(Object.getPrototypeOf(graph.value["3"].inputs), null);
// JSON.parse, like the projection, makes "__proto__" an own key; an object literal would not.
assert.deepEqual(plain(graph.value), JSON.parse('{"3":{"class_type":"KSampler","inputs":{"seed":"9007199254740993","cfg":"8.0","n":null}},"4":5,"5":[1,"2.50"],"__proto__":{"class_type":"P","inputs":{}},"constructor":"c"}'));
assert.ok(Object.hasOwn(graph.value, "__proto__"));
assert.deepEqual(plain(graph.value["__proto__"]), { class_type: "P", inputs: {} });
assert.equal(graph.value.constructor, "c");
for (const text of ["[]", '"3"', "5", "null", '{"nodes":[]}x']) assert.equal(m.projectGraph(text, "prompt").status, text === '{"nodes":[]}x' ? "malformed" : "malformed", text);
// A workflow keeps everything, normalized.
assert.deepEqual(graphOf('{"nodes":[{"id":1,"widgets_values":[7,"fixed",8.0,1e3]}],"links":[],"version":0.4}').value,
{ nodes: [{ id: 1, widgets_values: [7, "fixed", "8.0", "1E+3"] }], links: [], version: "0.4" });
});
test("projectMetadata mirrors _metadata for records, formats and carriers", () => {
const prompt = '{"1":{"class_type":"A","inputs":{"x":1.0}}}', workflow = '{"nodes":[],"version":0.4}';
const read = (records, record_formats, carrierText = null, limited = []) => ({ records, record_formats, carrierText, limited });
assert.deepEqual(plain(m.projectMetadata(null)), { status: "unreadable", omitted_reason: null, raw: {}, record_formats: {}, parameters: null,
prompt_graph: { status: "absent", value: null }, workflow_graph: { status: "absent", value: null } });
assert.deepEqual(plain(m.projectMetadata(read({}, {}))), { status: "absent", omitted_reason: null, raw: {}, record_formats: {}, parameters: null,
prompt_graph: { status: "absent", value: null }, workflow_graph: { status: "absent", value: null } });
const full = plain(m.projectMetadata(read({ prompt, workflow, parameters: "cat\nSteps: 1", comfyassets_prompt_pairs: "{}", other: "x" },
{ prompt: "original-text", workflow: "original-text", parameters: "original-text", comfyassets_prompt_pairs: "original-text", other: "original-text" })));
assert.deepEqual(full, { status: "available", omitted_reason: null,
raw: { prompt, workflow, parameters: "cat\nSteps: 1", comfyassets_prompt_pairs: "{}" },
record_formats: { prompt: "original-text", workflow: "original-text", parameters: "original-text", comfyassets_prompt_pairs: "original-text" },
parameters: "cat\nSteps: 1", prompt_graph: { status: "parsed", value: { 1: { class_type: "A", inputs: { x: "1.0" } } } },
workflow_graph: { status: "parsed", value: { nodes: [], version: "0.4" } } });
// limited records lose their text; invalid is malformed; reserialized without a carrier is limited.
const limited = plain(m.projectMetadata(read({ prompt, workflow }, { prompt: "original-text" }, null, ["workflow"])));
assert.deepEqual(limited.raw, { prompt });
assert.deepEqual(limited.record_formats, { prompt: "original-text", workflow: "limited" });
assert.equal(limited.workflow_graph.status, "limited");
assert.equal(plain(m.projectMetadata(read({ prompt }, { prompt: "original-text", workflow: "invalid" }))).workflow_graph.status, "malformed");
assert.equal(plain(m.projectMetadata(read({ workflow }, { workflow: "reserialized-json" }))).workflow_graph.status, "limited");
assert.equal(plain(m.projectMetadata(read({ prompt: "{" }, { prompt: "original-text" }))).prompt_graph.status, "malformed");
// Unknown formats are dropped from record_formats but the raw text still projects.
const odd = plain(m.projectMetadata(read({ prompt }, { prompt: "weird" })));
assert.deepEqual(odd.record_formats, {});
assert.equal(odd.prompt_graph.status, "parsed");
// A non-text record vanishes from raw and its named graph is malformed.
assert.equal(plain(m.projectMetadata(read({ prompt: 5 }, { prompt: "original-text" }))).prompt_graph.status, "malformed");
// Carriers: strings re-parse by kind, objects were projected at depth 2; a bad carrier fails every present graph.
const envelope = JSON.stringify({ prompt, workflow: { nodes: [], v: 1 } });
const carried = plain(m.projectMetadata(read({ prompt, workflow: '{"nodes": [], "v": 1}' }, { prompt: "original-text", workflow: "reserialized-json" }, envelope)));
assert.deepEqual(carried.prompt_graph, { status: "parsed", value: { 1: { class_type: "A", inputs: { x: "1.0" } } } });
assert.deepEqual(carried.workflow_graph, { status: "parsed", value: { nodes: [], v: 1 } });
const broken = plain(m.projectMetadata(read({ prompt, workflow }, { prompt: "original-text", workflow: "original-text" }, "[1]")));
assert.deepEqual([broken.prompt_graph.status, broken.workflow_graph.status], ["malformed", "malformed"]);
const deep = plain(m.projectMetadata(read({ prompt }, { prompt: "original-text" }, `{"title":${nested(70)},"prompt":${JSON.stringify(prompt)}}`)));
assert.equal(deep.prompt_graph.status, "limited");
const partial = plain(m.projectMetadata(read({ prompt, workflow }, { prompt: "original-text", workflow: "original-text" }, '{"prompt":"{","workflow":5}')));
assert.deepEqual([partial.prompt_graph.status, partial.workflow_graph.status], ["malformed", "malformed"]);
const missing = plain(m.projectMetadata(read({ prompt }, { prompt: "original-text" }, '{"workflow":{}}')));
assert.deepEqual([missing.prompt_graph.status, missing.workflow_graph.status], ["malformed", "absent"]);
// The carrier's own prompt object is projected: depth 2, node keys filtered.
const objectPrompt = plain(m.projectMetadata(read({ prompt: "x" }, { prompt: "reserialized-json" }, '{"prompt":{"1":{"class_type":"A","inputs":{},"_meta":{}}}}')));
assert.deepEqual(objectPrompt.prompt_graph, { status: "parsed", value: { 1: { class_type: "A", inputs: {} } } });
});
test("fitInspectionPayload omits the workflow graph, then the prompt graph, then all raw atomically", () => {
const payload = {
success: true, inspection_version: 1, metadata: {
status: "available", omitted_reason: null, raw: { prompt: "é".repeat(100), workflow: "{}" }, record_formats: { prompt: "original-text", workflow: "original-text" },
parameters: null, prompt_graph: { status: "parsed", value: { 1: { class_type: "A", inputs: { t: "\ud800".repeat(50) } } } },
workflow_graph: { status: "parsed", value: { nodes: Array.from({ length: 50 }, (_, i) => ({ id: i })) } },
},
};
const text = JSON.stringify(payload);
// UTF-8 bytes, not UTF-16 code units: 'é' is 2 bytes, an escaped lone surrogate 6.
assert.equal(m.inspectionBytes(payload), Buffer.byteLength(text, "utf8"));
assert.ok(m.inspectionBytes(payload) > text.length);
const whole = m.fitInspectionPayload(payload, bytes(payload));
assert.equal(whole, payload, "a payload within the limit is returned as is");
const first = m.fitInspectionPayload(payload, bytes(payload) - 1);
assert.equal(first.metadata.workflow_graph.status, "limited");
assert.equal(first.metadata.prompt_graph.status, "parsed");
assert.deepEqual(first.metadata.raw, payload.metadata.raw);
assert.ok(bytes(first) <= bytes(payload) - 1);
const second = m.fitInspectionPayload(payload, bytes(first) - 1);
assert.deepEqual([second.metadata.workflow_graph.status, second.metadata.prompt_graph.status], ["limited", "limited"]);
assert.equal(second.metadata.status, "available");
assert.deepEqual(second.metadata.raw, payload.metadata.raw);
const third = m.fitInspectionPayload(payload, bytes(second) - 1);
assert.deepEqual(third.metadata, { ...second.metadata, raw: {}, parameters: null, status: "limited", omitted_reason: "response-size-limit" });
assert.ok(bytes(third) <= bytes(second) - 1);
assert.throws(() => m.fitInspectionPayload(payload, bytes(third) - 1), /exceeds/);
// The caller's payload is never mutated and absent graphs are left alone.
assert.equal(JSON.stringify(payload), text);
const absent = { ...payload, metadata: { ...payload.metadata, workflow_graph: { status: "absent", value: null } } };
const fitted = m.fitInspectionPayload(absent, bytes(absent) - 1);
assert.equal(fitted.metadata.workflow_graph.status, "absent");
assert.equal(fitted.metadata.prompt_graph.status, "limited");
assert.equal(m.fitInspectionPayload(payload).metadata.status, "available", "the default limit is 8 MiB");
});
test("the committed projection corpus has every outcome and parses with the oracle entry shape", () => {
assert.ok(CASES.length >= 40);
const names = new Set(), statuses = new Set(), graphs = new Set();
for (const entry of CASES) {
assert.equal(typeof entry.name, "string");
assert.ok(!names.has(entry.name), `duplicate case ${entry.name}`);
names.add(entry.name);
const read = entry.records === null ? null : { records: entry.records, record_formats: entry.record_formats ?? Object.fromEntries(Object.keys(entry.records).map((key) => [key, "original-text"])),
carrierText: entry.carrier_text ?? null, limited: entry.limited ?? [] };
const metadata = m.projectMetadata(read);
statuses.add(metadata.status);
graphs.add(metadata.prompt_graph.status);
graphs.add(metadata.workflow_graph.status);
if (entry.expect) {
for (const [field, status] of Object.entries(entry.expect)) assert.equal(metadata[field].status, status, `${entry.name} ${field}`);
}
}
assert.deepEqual([...statuses].sort(), ["absent", "available", "unreadable"]);
assert.deepEqual([...graphs].sort(), ["absent", "limited", "malformed", "parsed"]);
});
+247
View File
@@ -16,6 +16,14 @@ webm}`` with their recorded ffprobe tag maps in ``video-tags-v1.json``), recorde
and synthetic tag maps, and multi-megabyte per-key containers built at test time,
and assert that the local carrier equals the server's, which selects from the
same tag map through a mocked probe, or from the same file through real ffprobe.
``web/js/workspace/local-projection.mjs`` ports ``utils/inspection_response.py``:
the strict Decimal/duplicate-key parse, the prompt, workflow and carrier
projection, the depth and node ceilings and ``_metadata``. The projection tests
hand the oracle every file above plus the committed adversarial record sets in
``tests/fixtures/local-inspect/projection-cases.json`` and node-ceiling sets built
at test time, and assert that ``projectMetadata`` equals ``_metadata`` byte for
byte, as the server's ``_bytes`` writes it with sorted keys.
"""
import io
@@ -24,6 +32,7 @@ import os
import shutil
import struct
import subprocess # nosec B404
import sys
import tempfile
import unittest
import zlib
@@ -33,9 +42,11 @@ from unittest.mock import patch
from PIL import Image
from PIL.PngImagePlugin import PngInfo
from utils.inspection_response import GRAPH_NODE_CEILING, _bytes, _metadata
from utils.prompt_inspector import (
ROLE_METADATA_BYTES,
WORKFLOW_METADATA_BYTES,
InspectionRead,
MetadataReadError,
read_inspection_file,
)
@@ -56,6 +67,9 @@ VIDEO_FIXTURES = HEADER_FIXTURES
RECORDED_TAGS = json.loads(
(VIDEO_FIXTURES / "video-tags-v1.json").read_text(encoding="utf-8")
)["fixtures"]
PROJECTION_CASES = json.loads(
(HEADER_FIXTURES / "projection-cases.json").read_text(encoding="utf-8")
)["cases"]
NODE_TIMEOUT_SECONDS = 180
PNG_CLASS = {"media": "image", "kind": "png"}
_FOOTER = "Steps: 20, Sampler: Euler, Batch index: {}, Seed: 7"
@@ -690,5 +704,238 @@ class LocalVideoCarrierParityTests(unittest.TestCase):
read_inspection_file(files["roles_over_budget"])
def _canonical(value):
"""``value`` with every dict's keys sorted, so two orders compare as one."""
if isinstance(value, dict):
return {key: _canonical(value[key]) for key in sorted(value)}
if isinstance(value, list):
return [_canonical(item) for item in value]
return value
def _canonical_bytes(value):
return _bytes(_canonical(value))
def _server_metadata(path):
try:
return _metadata(read_inspection_file(path))
except MetadataReadError:
return _metadata(None)
def _synthetic_read(case):
if case["records"] is None:
return None
formats = case.get("record_formats")
if formats is None:
formats = {key: "original-text" for key in case["records"]}
return InspectionRead(
case["records"],
formats,
case.get("carrier_text"),
None,
None,
None,
frozenset(case.get("limited", ())),
)
def _case_supported(case):
"""Whether this interpreter has the feature a case depends on."""
if case.get("needs") == "int-max-str-digits":
limit = getattr(sys, "get_int_max_str_digits", None)
return limit is not None and limit() == 4300
return True
def _node_ceiling_cases():
"""Record sets at and past the 20,000-node ceiling, too large to commit."""
api = lambda count: "{%s}" % ",".join( # noqa: E731
'"%d":{"class_type":"A","inputs":{}}' % index for index in range(count)
)
ui = lambda count: "[%s]" % ",".join("{}" for _ in range(count)) # noqa: E731
half = GRAPH_NODE_CEILING // 2
return [
{"name": "prompt_at_ceiling", "records": {"prompt": api(GRAPH_NODE_CEILING)}},
{
"name": "prompt_over_ceiling",
"records": {"prompt": api(GRAPH_NODE_CEILING + 1)},
},
{
"name": "workflow_at_ceiling",
"records": {"workflow": '{"nodes":%s}' % ui(GRAPH_NODE_CEILING)},
},
{
"name": "workflow_over_ceiling",
"records": {"workflow": '{"nodes":%s}' % ui(GRAPH_NODE_CEILING + 1)},
},
{
"name": "workflow_subgraphs_over_ceiling",
"records": {
"workflow": '{"nodes":%s,"definitions":{"subgraphs":[{"nodes":%s},'
'5,{"nodes":3}]}}' % (ui(half + 1), ui(half))
},
},
{
"name": "workflow_subgraphs_at_ceiling",
"records": {
"workflow": '{"nodes":%s,"definitions":{"subgraphs":[{"nodes":%s}]}}'
% (ui(half), ui(half))
},
},
{
"name": "carrier_prompt_over_ceiling",
"records": {"prompt": "x"},
"record_formats": {"prompt": "reserialized-json"},
"carrier_text": '{"prompt":%s}' % api(GRAPH_NODE_CEILING + 1),
},
]
class LocalProjectionParityTests(unittest.TestCase):
"""The local projection answers byte for byte as ``_metadata`` does."""
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.addCleanup(assert_closed, self, temporary.name)
self.directory = Path(temporary.name)
def _files(self):
files = {}
for name in parity.COMMITTED:
files[name] = parity.fixture_path(name)
for name in parity.TEST_TIME:
path = parity.fixture_path(name, self.directory)
parity.write_png(path, parity.build(name))
files[name] = path
for name, write in EXTRA_SHAPES.items():
path = self.directory / f"{name}.png"
write(path)
files[name] = path
for header in ("tiny.jpg", "tiny.webp"):
files[header] = HEADER_FIXTURES / header
for name in RECORDED_TAGS:
files[name] = VIDEO_FIXTURES / name
return files
def test_local_metadata_matches_route_for_parity_corpus(self):
files = self._files()
cases = [
case
for case in PROJECTION_CASES + _node_ceiling_cases()
if _case_supported(case)
]
self.assertGreater(len(cases), len(PROJECTION_CASES) - 3)
entries = [{"name": name, "path": str(path)} for name, path in files.items()]
entries += [
{
"name": "case:" + case["name"],
"records": case["records"],
"record_formats": case.get("record_formats"),
"carrier_text": case.get("carrier_text"),
"limited": case.get("limited", []),
}
for case in cases
]
observed = _run_oracle(self, entries)
outcomes = set()
for name, path in files.items():
with self.subTest(fixture=name):
self.assertIsNotNone(observed[name]["classified"], name)
if path.suffix in VIDEO_CONTAINERS and not HAVE_FFMPEG:
# Without ffprobe the server cannot read the file; the tag-map
# vectors below cover the carrier projection instead.
continue
server = _server_metadata(path)
local = observed[name]["metadata"]
self.assertEqual(_canonical_bytes(local), _canonical_bytes(server))
outcomes.add(server["status"])
for field in ("prompt_graph", "workflow_graph"):
outcomes.add(field + ":" + server[field]["status"])
for case in cases:
with self.subTest(case=case["name"]):
server = _metadata(_synthetic_read(case))
local = observed["case:" + case["name"]]["metadata"]
self.assertEqual(_canonical_bytes(local), _canonical_bytes(server))
expect = case.get("expect") or {}
for field, status in expect.items():
self.assertEqual(server[field]["status"], status, field)
outcomes.add(server["status"])
for field in ("prompt_graph", "workflow_graph"):
outcomes.add(field + ":" + server[field]["status"])
# Every projection outcome must occur, or agreement proves little.
self.assertEqual(
outcomes,
{
"available",
"absent",
"unreadable",
"prompt_graph:parsed",
"prompt_graph:limited",
"prompt_graph:malformed",
"prompt_graph:absent",
"workflow_graph:parsed",
"workflow_graph:limited",
"workflow_graph:malformed",
"workflow_graph:absent",
},
)
def test_recorded_video_tag_maps_project_as_the_server(self):
"""The carrier projection on the recorded maps, with or without ffprobe."""
vectors = [(name, tags, []) for name, tags in RECORDED_TAGS.items()]
vectors += [
(name, tags, limited)
for name, tags, limited in TAG_MAP_VECTORS
if name
in {
"per_key_wins_over_comment",
"object_prompt_reserialized",
"python_number_spellings",
"integer_like_keys_keep_order",
"invalid_envelope_workflow_keeps_roles",
"workflow_only_invalid_envelope",
"reader_limited_workflow_with_per_key",
"envelope_workflow_over_budget_limited",
}
]
observed = _run_oracle(
self,
[
{"name": name, "tags": tags, "limited": limited}
for name, tags, limited in vectors
],
)
seen = set()
for name, tags, limited in vectors:
with self.subTest(vector=name):
with (
patch(
"utils.prompt_inspector.ffprobe_path",
return_value="/tool/ffprobe",
),
patch(
"utils.prompt_inspector._probe_video_tags",
side_effect=_fake_probe(
tags, drop_workflow="workflow" in limited
),
),
):
try:
server = _metadata(read_inspection_file(Path("vector.mp4")))
except MetadataReadError:
server = _metadata(None)
local = observed[name]["metadata"]
self.assertEqual(_canonical_bytes(local), _canonical_bytes(server))
seen.update(server["record_formats"].values())
seen.add(server["workflow_graph"]["status"])
self.assertTrue(
{"reserialized-json", "original-text", "invalid", "limited"} <= seen
)
self.assertTrue({"parsed", "malformed", "limited"} <= seen)
if __name__ == "__main__":
unittest.main()
+12
View File
@@ -293,6 +293,18 @@
"functions": 96,
"statements": 96
},
"workspace/local-inspection.mjs": {
"lines": 98,
"branches": 85,
"functions": 98,
"statements": 98
},
"workspace/local-projection.mjs": {
"lines": 97,
"branches": 95,
"functions": 98,
"statements": 97
},
"workspace/local-video.mjs": {
"lines": 98,
"branches": 93,
+5 -3
View File
@@ -87,8 +87,10 @@ function promptsFor(value) {
return value;
}
function fileFor(value) {
if (!object(value) || value.observation !== 'current-file' || typeof value.source_label !== 'string' ||
function fileFor(value, identity) {
// A local record observes the selected file itself; every other adapter observes a current server file.
const observation = identity?.kind === 'local-file' ? 'selected-local-file' : 'current-file';
if (!object(value) || value.observation !== observation || typeof value.source_label !== 'string' ||
!['image', 'video'].includes(value.media_type) || typeof value.extension !== 'string' ||
(value.format !== null && typeof value.format !== 'string')) throw new TypeError('Invalid inspection file');
const file = { observation: value.observation, source_label: value.source_label, media_type: value.media_type, extension: value.extension, format: value.format };
@@ -281,7 +283,7 @@ export function projectInspectionPayload(result, identity, download, extractFact
};
if (legacy) return record;
const resolution = resolutionFor(result.resolution);
record.file = fileFor(result.file);
record.file = fileFor(result.file, identity);
record.metadata = metadataFor(result.metadata);
record.library = libraryFor(result.library);
if (typeof extractFacts !== 'function') throw new TypeError('Inspection facts parser unavailable');
+175
View File
@@ -0,0 +1,175 @@
// One chosen local File, read entirely in this browser, as the inspection wire
// the workspace already renders. The readers are ports of the server's
// (png-metadata.js readPngMetadataFile/readImageHeader, local-video.mjs); the
// projection is local-projection.mjs. Nothing here builds a URL or sends bytes:
// the only URL a handle carries is the session-owned blob: URL it was created with.
import { readVideoTags, selectVideoCarrier } from './local-video.mjs';
import { projectMetadata, fitInspectionPayload, MAX_RESPONSE_BYTES } from './local-projection.mjs';
import { projectInspectionPayload } from './inspection-state.mjs';
export const LOCAL_OBSERVATION = 'selected-local-file';
export const LOCAL_SOURCE_LABEL = 'Selected local file';
// Interim roles until PR L3 ports the inspection resolver: every role is open, and
// the legacy fill in projectInspectionPayload shows what the legacy pages show.
const LOCAL_RESOLUTION_REASON = 'Resolved in this browser from the workflow.';
const LOCAL_LIBRARY = Object.freeze({ status: 'unavailable', scope: 'saved-library', tags: [], category_scope: null, tags_scope: null });
const LOCAL_SCOPE = 'local';
const PREFIX_BYTES = 32;
const MAX_FILENAME_CHARS = 255;
const MEDIA_TYPES = new Set(['image', 'video']);
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
const pngReader = () => globalThis.PngMetadata || globalThis.window?.PngMetadata;
/** A display name with no path separators or control characters; never empty. */
export function sanitizeLocalFilename(name) {
const text = typeof name === 'string' ? name : '';
const cleaned = text.replace(/[\x00-\x1f\x7f]/g, '').replace(/[\\/]/g, '_').trim();
return [...cleaned].slice(0, MAX_FILENAME_CHARS).join('') || 'file';
}
/** `pathlib.Path(name).suffix.lower()`. */
function localExtension(filename) {
const dot = filename.lastIndexOf('.');
return dot > 0 && dot < filename.length - 1 ? filename.slice(dot).toLowerCase() : '';
}
function classificationOf(value) {
if (!object(value) || !MEDIA_TYPES.has(value.media) || typeof value.kind !== 'string' || !value.kind) throw new TypeError('Unsupported local file');
return Object.freeze({ media: value.media, kind: value.kind });
}
/**
* Run the readers the server would run for these bytes: the bounded PNG reader,
* the JPEG/WebP header reader, or the container tag readers plus the carrier rule.
* Returns `{classification, result}` in the oracle's shape; `result` is null for
* bytes no reader claims.
*/
export async function readLocalFile(file, { classification, signal, pngMetadata = pngReader() } = {}) {
if (!(file instanceof Blob)) throw new TypeError('A local file is required');
const reader = () => {
if (!pngMetadata || typeof pngMetadata.classifyPrefix !== 'function') throw new TypeError('The local PNG reader is unavailable');
return pngMetadata;
};
const prefix = new Uint8Array(await file.slice(0, Math.min(PREFIX_BYTES, file.size)).arrayBuffer());
const classified = classification === undefined ? reader().classifyPrefix(prefix) : classification;
if (classified === null) return { classification: null, result: null };
const { media, kind } = classificationOf(classified);
if (kind === 'png') return { classification: classified, result: await reader().readPngMetadataFile(file, { signal, prefix }) };
if (media === 'image') return { classification: classified, result: await reader().readImageHeader(file, kind) };
const read = await readVideoTags(file, { signal, prefix, kind });
return { classification: classified, result: { read, selection: read.status === 'complete' ? selectVideoCarrier(read.tags, { limited: read.limited }) : null } };
}
/**
* The server's InspectionRead for a reader result: `read` is null when the server
* would have raised MetadataReadError (unreadable), else
* `{records, record_formats, carrierText, limited}`; `format`, `width` and
* `height` follow `read_inspection_file` (a video has none).
*/
export function toInspectionRead(classification, result) {
const { media, kind } = classificationOf(classification);
const none = { read: null, format: null, width: null, height: null };
if (kind === 'png') {
if (!object(result) || result.status !== 'complete') return none;
const limited = [...(result.limited ?? [])];
const record_formats = Object.fromEntries(Object.keys(result.records).map(key => [key, 'original-text']));
for (const key of limited) record_formats[key] = 'limited';
return { read: { records: { ...result.records }, record_formats, carrierText: null, limited }, format: 'PNG', width: result.width ?? null, height: result.height ?? null };
}
if (media === 'image') {
if (!object(result)) return none;
return { read: { records: {}, record_formats: {}, carrierText: null, limited: [] }, format: result.format ?? null, width: result.width ?? null, height: result.height ?? null };
}
const selection = result?.selection;
if (result?.read?.status !== 'complete' || !object(selection) || selection.status !== 'complete') return none;
return { read: { records: { ...selection.records }, record_formats: { ...selection.record_formats }, carrierText: selection.carrierText ?? null, limited: [...(selection.limited ?? [])] }, format: null, width: null, height: null };
}
const dimension = value => Number.isSafeInteger(value) && value > 0 ? value : null;
/** The `inspection_version: 1` wire for one local read, before the response budget. */
export function buildLocalPayload({ read, file, classification }) {
const classified = classificationOf(classification);
const inspection = toInspectionRead(classified, read);
const filename = sanitizeLocalFilename(file?.name);
const size = Number.isSafeInteger(file?.size) && file.size >= 0 ? file.size : null;
const modified = typeof file?.lastModified === 'number' && Number.isFinite(file.lastModified) && file.lastModified >= 0 ? file.lastModified / 1000 : null;
return {
success: true, inspection_version: 1,
file: {
observation: LOCAL_OBSERVATION, source_label: LOCAL_SOURCE_LABEL, media_type: classified.media, extension: localExtension(filename),
format: inspection.format, size, width: dimension(inspection.width), height: dimension(inspection.height), modified_time: modified,
},
prompts: { positive: { status: 'unresolved' }, negative: { status: 'unresolved' } },
resolution: { state: 'unresolved', sampler_id: null, reason: LOCAL_RESOLUTION_REASON, reason_code: null, node: null },
metadata: projectMetadata(inspection.read),
library: { ...LOCAL_LIBRARY, tags: [] },
};
}
/** The server's 8 MiB response budget, applied to the local wire with the same omission order. */
export function fitLocalPayload(wire, limit = MAX_RESPONSE_BYTES) {
return fitInspectionPayload(wire, limit);
}
// Handles this module created, by a private token: the controller and the viewer
// session spread-copy descriptors, so a copy of an owned handle is the same
// selection, while a hand-made descriptor or a tampered URL is foreign.
const TOKEN = Symbol('local-file');
const OWNED = new WeakMap();
let sequence = 0;
/**
* One selection of a local file. `url` is the session's own `blob:` URL (or null
* when none could be created); every selection gets a new key, so choosing the
* same bytes twice never reuses a stale read.
*/
export function createLocalFileHandle({ file, url = null, classification, filename = file?.name }) {
if (!(file instanceof Blob)) throw new TypeError('A local file is required');
if (url !== null && (typeof url !== 'string' || !url.startsWith('blob:'))) throw new TypeError('Local media must be a blob: URL');
const classified = classificationOf(classification);
const token = Object.freeze({});
const secret = Object.freeze({ key: `local:${++sequence}`, filename: sanitizeLocalFilename(filename), url, file, classification: classified });
OWNED.set(token, secret);
return Object.freeze({ kind: 'local-file', key: secret.key, filename: secret.filename, is_video: classified.media === 'video', url, file, classification: classified, [TOKEN]: token });
}
/** The owned selection behind a handle (or a copy of one); throws for anything else. */
function owned(handle) {
const secret = object(handle) ? OWNED.get(handle[TOKEN]) : undefined;
if (!secret || secret.key !== handle.key || secret.url !== handle.url || secret.file !== handle.file || secret.filename !== handle.filename) throw new TypeError('Unknown local file');
return secret;
}
function validate(handle) {
owned(handle);
}
function originalDownload(handle) {
const secret = owned(handle);
if (secret.url === null) throw new TypeError('Original media is unavailable');
return { url: secret.url, filename: secret.filename };
}
export const localFileAdapter = Object.freeze({
validate,
key(handle) { return owned(handle).key; },
scope(handle) { owned(handle); return LOCAL_SCOPE; },
// An inert sentinel: the controller's same-key check works and no URL is ever built.
inspectionPath(handle) { return `local-file:${owned(handle).key}`; },
async read(handle, { signal } = {}) {
const secret = owned(handle);
const { result } = await readLocalFile(secret.file, { classification: secret.classification, signal });
return fitLocalPayload(buildLocalPayload({ read: result, file: { name: secret.filename, size: secret.file.size, lastModified: secret.file.lastModified }, classification: secret.classification }));
},
toRecord(result, handle, extractFacts) {
const secret = owned(handle);
if (!object(result) || result.success !== true || result.inspection_version !== 1) throw new TypeError('Invalid local inspection');
return projectInspectionPayload(result, { kind: 'local-file', key: secret.key, path: secret.filename },
secret.url === null ? null : originalDownload(handle), extractFacts);
},
originalDownload,
// Session-owned, never resolved through the API client.
mediaUrl(handle) { return owned(handle).url; },
});
+386
View File
@@ -0,0 +1,386 @@
// Browser port of utils/inspection_response.py: the strict parse (Decimal
// spelling, no duplicate keys, NaN/Infinity as null, integers past 2^53-1 as
// text), the prompt/workflow/carrier projection with its depth and node
// ceilings, `_metadata`, and the response-size omission steps of
// `_encode_inspection`. A local file must project exactly as the server does for
// the same bytes; tests/test_local_inspect_parity.py holds both to one answer.
// Pure: no DOM, no I/O.
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
export const MAX_PROJECTION_DEPTH = 64;
// Backstop, not an expected outcome: executable API nodes for a prompt; UI nodes
// including subgraph definitions for a workflow.
export const GRAPH_NODE_CEILING = 20000;
export const RECORD_KEYS = Object.freeze(['prompt', 'workflow', 'parameters', 'comfyassets_prompt_pairs']);
const FORMATS = new Set(['original-text', 'reserialized-json', 'invalid', 'limited']);
const PROMPT_NODE_KEYS = new Set(['class_type', 'inputs']);
const MAX_SAFE_DIGITS = '9007199254740991';
// Python's int() refuses more digits than sys.get_int_max_str_digits() (4300).
const INT_MAX_STR_DIGITS = 4300;
// decimal's exact constructor (libmpdec, 64-bit) refuses values outside its
// maximal context: adjusted exponent above MAX_EMAX or exponent below Etiny.
const DECIMAL_MAX_EMAX = 999999999999999999n;
const DECIMAL_ETINY = -1999999999999999997n;
const NUMBER = /-?(?:0|[1-9][0-9]*)(\.[0-9]+)?([eE][-+]?[0-9]+)?/y;
const STRING_STOP = /["\\\u0000-\u001f]/g;
const HEX4 = /^[0-9a-fA-F]{4}$/;
const ESCAPES = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
// Python's json: `null`/`true`/`false`, then the constants parse_constant receives.
const LITERALS = [['null', null], ['true', true], ['false', false], ['NaN', null], ['Infinity', null], ['-Infinity', null]];
/** A present graph cannot be projected within inspection limits (`_ProjectionLimit`). */
export class ProjectionLimit extends Error {
constructor() {
super('Projection exceeds inspection limits');
this.name = 'ProjectionLimit';
}
}
/** The server's ValueError/TypeError family: the text or tree is malformed. */
export class ProjectionError extends Error {
constructor(message) {
super(message);
this.name = 'ProjectionError';
}
}
const isArray = Array.isArray;
const isObject = value => value !== null && typeof value === 'object' && !isArray(value);
const isContainer = value => value !== null && typeof value === 'object';
/** Python's `str(Decimal(literal))` from the literal's digits and exponent, never through a float. */
export function spellDecimal(literal) {
const negative = literal[0] === '-';
const body = negative ? literal.slice(1) : literal;
const exponentAt = body.search(/[eE]/);
const mantissa = exponentAt < 0 ? body : body.slice(0, exponentAt);
const dot = mantissa.indexOf('.');
const integer = dot < 0 ? mantissa : mantissa.slice(0, dot), fraction = dot < 0 ? '' : mantissa.slice(dot + 1);
const exponent = (exponentAt < 0 ? 0n : BigInt(body.slice(exponentAt + 1))) - BigInt(fraction.length);
const digits = (integer + fraction).replace(/^0+/, '') || '0';
const adjusted = exponent + BigInt(digits.length) - 1n;
if (adjusted > DECIMAL_MAX_EMAX || exponent < DECIMAL_ETINY) throw new ProjectionError('JSON decimal exceeds supported range');
const leftDigits = exponent + BigInt(digits.length);
const dotPlace = exponent <= 0n && leftDigits > -6n ? leftDigits : 1n;
let intPart, fracPart;
if (dotPlace <= 0n) {
intPart = '0'; fracPart = '.' + '0'.repeat(Number(-dotPlace)) + digits;
} else if (dotPlace >= BigInt(digits.length)) {
intPart = digits + '0'.repeat(Number(dotPlace - BigInt(digits.length))); fracPart = '';
} else {
intPart = digits.slice(0, Number(dotPlace)); fracPart = '.' + digits.slice(Number(dotPlace));
}
const shift = leftDigits - dotPlace;
const exp = shift === 0n ? '' : `E${shift > 0n ? '+' : '-'}${shift < 0n ? -shift : shift}`;
return (negative ? '-' : '') + intPart + fracPart + exp;
}
function numberValue(match) {
const [literal, fraction, exponent] = match;
if (fraction || exponent) return spellDecimal(literal);
const digits = literal[0] === '-' ? literal.slice(1) : literal;
if (digits.length > INT_MAX_STR_DIGITS) throw new ProjectionError('Exceeds the limit for integer string conversion');
if (digits.length > MAX_SAFE_DIGITS.length || (digits.length === MAX_SAFE_DIGITS.length && digits > MAX_SAFE_DIGITS)) return literal;
return Number(literal) || 0;
}
/**
* `json.loads(text, parse_float=Decimal, object_pairs_hook=_pairs, parse_constant=_nonfinite)`
* with its results already normalized: objects have a null prototype, fractions and
* exponents are Decimal text, integers past 2^53-1 are their literal text, and
* NaN/Infinity are null. Iterative, so nesting depth never overflows the stack.
*/
export function parseStrictJson(text) {
if (typeof text !== 'string') throw new ProjectionError('JSON text must be a string');
let pos = 0;
const fail = message => new ProjectionError(`${message} at ${pos}`);
const skip = () => {
while (pos < text.length) {
const c = text.charCodeAt(pos);
if (c !== 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d) break;
pos++;
}
};
function string() {
pos++;
let out = '';
for (;;) {
STRING_STOP.lastIndex = pos;
const stop = STRING_STOP.exec(text);
if (!stop) throw fail('Unterminated string');
out += text.slice(pos, stop.index);
pos = stop.index + 1;
if (stop[0] === '"') return out;
if (stop[0] !== '\\') throw fail('Invalid control character');
const escape = text[pos];
if (escape === 'u') {
const hex = text.slice(pos + 1, pos + 5);
if (!HEX4.test(hex)) throw fail('Invalid \\uXXXX escape');
pos += 5;
const code = parseInt(hex, 16);
if (code >= 0xd800 && code <= 0xdbff && text[pos] === '\\' && text[pos + 1] === 'u') {
const low = text.slice(pos + 2, pos + 6);
const lowCode = HEX4.test(low) ? parseInt(low, 16) : -1;
if (lowCode >= 0xdc00 && lowCode <= 0xdfff) {
out += String.fromCharCode(code, lowCode);
pos += 6;
continue;
}
}
out += String.fromCharCode(code);
} else if (Object.hasOwn(ESCAPES, escape)) {
out += ESCAPES[escape];
pos++;
} else throw fail('Invalid \\escape');
}
}
function key() {
if (text[pos] !== '"') throw fail('Expecting property name enclosed in double quotes');
const name = string();
skip();
if (text[pos] !== ':') throw fail("Expecting ':' delimiter");
pos++;
skip();
return name;
}
function scalar() {
if (text[pos] === '"') return string();
for (const [literal, value] of LITERALS) {
if (text.startsWith(literal, pos)) {
pos += literal.length;
return value;
}
}
NUMBER.lastIndex = pos;
const match = NUMBER.exec(text);
if (!match) throw fail('Expecting value');
pos += match[0].length;
return numberValue(match);
}
skip();
const stack = [];
let result;
for (;;) {
let value;
if (text[pos] === '{') {
pos++; skip();
const frame = { value: Object.create(null), object: true, key: null, duplicate: false };
if (text[pos] === '}') pos++;
else { frame.key = key(); stack.push(frame); continue; }
value = frame.value;
} else if (text[pos] === '[') {
pos++; skip();
const frame = { value: [], object: false };
if (text[pos] === ']') pos++;
else { stack.push(frame); continue; }
value = frame.value;
} else value = scalar();
// Attach the finished value, closing containers whose last member it was.
for (;;) {
if (!stack.length) { result = value; break; }
const frame = stack[stack.length - 1];
if (frame.object) {
if (Object.hasOwn(frame.value, frame.key)) frame.duplicate = true;
frame.value[frame.key] = value;
} else frame.value.push(value);
skip();
if (text[pos] === ',') {
pos++; skip();
if (frame.object) frame.key = key();
break;
}
if (text[pos] !== (frame.object ? '}' : ']')) throw fail("Expecting ',' delimiter");
pos++;
stack.pop();
// `_pairs` runs when the object closes, after everything inside it parsed.
if (frame.object && frame.duplicate) throw new ProjectionError('Duplicate JSON key');
value = frame.value;
}
if (!stack.length) break;
}
skip();
if (pos !== text.length) throw fail('Extra data');
return result;
}
/** `_normalize`: walk once; unretained values are only checked against the depth bound. */
function normalize(value, depth, retain) {
if (!isContainer(value)) return retain ? value : null;
if (depth > MAX_PROJECTION_DEPTH) throw new ProjectionLimit();
if (isArray(value)) {
const result = retain ? [] : null;
for (const child of value) {
const normalized = normalize(child, depth + 1, retain);
if (retain) result.push(normalized);
}
return result;
}
const result = retain ? Object.create(null) : null;
for (const key of Object.keys(value)) {
const normalized = normalize(value[key], depth + 1, retain);
if (retain) result[key] = normalized;
}
return result;
}
/** `_project_prompt`: each API node keeps exactly `class_type` and `inputs`; other keys are validated and dropped. */
function projectPrompt(tree, depth) {
if (!isObject(tree)) throw new ProjectionError('Prompt graph must be an object of nodes');
if (depth + 1 > MAX_PROJECTION_DEPTH) throw new ProjectionLimit();
const result = Object.create(null);
for (const id of Object.keys(tree)) {
const node = tree[id];
if (!isObject(node)) {
result[id] = normalize(node, depth + 1, true);
continue;
}
const projected = Object.create(null);
for (const key of Object.keys(node)) {
const keep = PROMPT_NODE_KEYS.has(key);
const normalized = normalize(node[key], depth + 2, keep);
if (keep) projected[key] = normalized;
}
result[id] = projected;
}
return result;
}
function project(tree, kind, depth) {
if (kind === 'prompt') return projectPrompt(tree, depth);
if (kind === 'workflow') return normalize(tree, depth, true);
if (!isObject(tree)) throw new ProjectionError('Malformed carrier');
const result = Object.create(null);
for (const key of Object.keys(tree)) {
const member = tree[key];
if (key !== 'prompt' && key !== 'workflow') normalize(member, depth + 1, false);
else if (typeof member === 'string') result[key] = member; // projected by its own kind when read
else if (key === 'prompt' && !isObject(member)) result[key] = normalize(member, depth + 1, true);
else result[key] = project(member, key, depth + 1);
}
return result;
}
/** `_strict_projection`: parse exactly and project by record kind ('prompt'|'workflow'|'carrier'). */
export function strictProjection(text, kind) {
return project(parseStrictJson(text), kind, 1);
}
function nodeCount(value, workflow) {
if (!workflow) return Object.keys(value).length;
const nodes = value.nodes;
let count = isArray(nodes) ? nodes.length : 0;
const definitions = value.definitions;
const subgraphs = isObject(definitions) ? definitions.subgraphs : null;
for (const subgraph of isArray(subgraphs) ? subgraphs : []) {
const inner = isObject(subgraph) ? subgraph.nodes : null;
count += isArray(inner) ? inner.length : 0;
}
return count;
}
function graph(value, workflow) {
if (!isObject(value)) return { status: 'malformed', value: null };
if (nodeCount(value, workflow) > GRAPH_NODE_CEILING) return { status: 'limited', value: null };
return { status: 'parsed', value };
}
function outcome(error) {
if (error instanceof ProjectionLimit) return { status: 'limited', value: null };
if (error instanceof ProjectionError) return { status: 'malformed', value: null };
throw error;
}
/**
* One record's graph as the inspector projects it: `{status, value}` with `status`
* `parsed` (the projected graph the facts parser receives), `limited` (past the
* depth or node bound) or `malformed`.
*/
export function projectGraph(text, kind) {
try {
return graph(strictProjection(text, kind), kind === 'workflow');
} catch (error) {
return outcome(error);
}
}
/**
* `_metadata` for one local read: `{records, record_formats, carrierText, limited}`
* (the server's InspectionRead) or null when the file's metadata is unreadable.
*/
export function projectMetadata(inspection) {
const formats = {}, raw = {};
if (inspection) {
for (const [key, value] of Object.entries(inspection.record_formats ?? {})) {
if (RECORD_KEYS.includes(key) && FORMATS.has(value)) formats[key] = value;
}
// A record over its read budget has no text: like `invalid`, raw omits it,
// and its graph is `limited` rather than parsed from nothing.
for (const key of inspection.limited ?? []) if (RECORD_KEYS.includes(key)) formats[key] = 'limited';
for (const [key, value] of Object.entries(inspection.records ?? {})) {
if (RECORD_KEYS.includes(key) && typeof value === 'string' && formats[key] !== 'limited') raw[key] = value;
}
}
const result = {
status: inspection === null || inspection === undefined ? 'unreadable' : Object.keys(raw).length || Object.keys(formats).length ? 'available' : 'absent',
omitted_reason: null, raw, record_formats: formats, parameters: Object.hasOwn(raw, 'parameters') ? raw.parameters : null,
};
let carrier = null, failed = null;
if (inspection && inspection.carrierText !== null && inspection.carrierText !== undefined) {
try { carrier = strictProjection(inspection.carrierText, 'carrier'); } catch (error) { failed = outcome(error).status; }
}
for (const [key, field] of [['prompt', 'prompt_graph'], ['workflow', 'workflow_graph']]) {
let value = { status: 'absent', value: null };
if (Object.hasOwn(raw, key) || Object.hasOwn(formats, key)) {
if (failed || formats[key] === 'limited') value.status = failed || 'limited';
else if (formats[key] === 'invalid') value.status = 'malformed';
else if (carrier === null && formats[key] === 'reserialized-json') value.status = 'limited';
else if (carrier === null) value = projectGraph(raw[key], key);
else {
try {
let member = carrier[key];
if (typeof member === 'string') member = strictProjection(member, key);
value = graph(member, key === 'workflow');
} catch (error) { value.status = outcome(error).status; }
}
}
result[field] = value;
}
return result;
}
/** UTF-8 length of `JSON.stringify(value)`: the server's `_bytes` count (lone surrogates are already escaped). */
export function inspectionBytes(value) {
const text = JSON.stringify(value);
let count = 0;
for (let index = 0; index < text.length; index++) {
const code = text.charCodeAt(index);
if (code < 0x80) count += 1;
else if (code < 0x800) count += 2;
else if (code >= 0xd800 && code <= 0xdbff) { count += 4; index++; } else count += 3;
}
return count;
}
const withMetadata = (payload, changes) => ({ ...payload, metadata: { ...payload.metadata, ...changes } });
/**
* The omission steps of `_encode_inspection`: the workflow graph first, then the
* prompt graph, then all raw records atomically, each measured as encoded bytes.
* Returns a new payload; throws when even the bare payload is over `limit`.
*/
export function fitInspectionPayload(payload, limit = MAX_RESPONSE_BYTES) {
let fitted = payload, body = inspectionBytes(payload);
for (const field of ['workflow_graph', 'prompt_graph']) {
if (body <= limit) break;
if (fitted.metadata[field].status !== 'absent') {
fitted = withMetadata(fitted, { [field]: { status: 'limited', value: null } });
body = inspectionBytes(fitted);
}
}
if (body > limit) {
fitted = withMetadata(fitted, { raw: {}, parameters: null, status: 'limited', omitted_reason: 'response-size-limit' });
body = inspectionBytes(fitted);
}
if (body > limit) throw new RangeError('Inspection response exceeds limits');
return fitted;
}