Compare commits

...
2 Commits
Author SHA1 Message Date
Agent Control Runner d2581afd26 P23-03: 生活の観察 2026-10-16 19:28:26 +09:00
Agent Control Runner 059c2f743b P23-03: 生活の観察 2026-10-16 19:18:11 +09:00
8 changed files with 322 additions and 4 deletions
@@ -3707,7 +3707,7 @@
"template": "P23 life observation: <checks> checks, <failures> failures",
"required": true,
"fixed_check_count_used": false,
"source_sha256_at_schema_audit": "5c52db4dd70f9702c096e918d849703d14aec1acb88bb2458ca01049da71ce3c",
"source_sha256_at_schema_audit": "750254af7d4b8b00508e25d1ef744837d3a7d14ddebe12398e8014bf5e8c68e8",
"authority": {
"path": "docs/spec/acceptance/P23-03.json",
"scope": "component and read-only observation only; native unexecuted; whole unit blocked"
@@ -0,0 +1,69 @@
# Repair36 worker submission
Scope: world_preview observation labels, independent checker, Python/GD controls.
No Runtime, service, data, art, policy, original negative, or acceptance changes.
Input authority: registered BUILD `audit-support/badge-repair36-exact-inputs/EXACT_INPUTS.json`, source 26f60095; original paths are provenance identifiers.
The resolver places fixed cabin rectangles/badges first, then business, supply,
landing, aggregation in array order. A positive-area collision advances to the
first blocking rectangle's right edge and repeats. With finite rectangles of
positive width this always terminates on the anchor row; the specified upper
and lower fallback rows are therefore unreachable. No margin, pixel threshold,
clipping, truncation, member alteration, or new viewport rule is introduced.
Original anchors and ordered blocker identities are retained in deep-copy reports.
Traffic obstacles use the resolved positions; actor placement/traffic subtraction
and cabin/transit guards retain their existing implementations.
The checker separately projects anchors back into the original report shape for
existing raw-state queue/business/aggregation recomputation. It independently
replays label geometry and validates reported positions and blocker identities.
All label pairs must have zero positive-area intersection. Both production image
review consumers require explicit indexed `judgments`; the legacy `rows` helper
interface remains available for unchanged historical unit controls only. Compact
bits are never accepted for new image-review evidence.
## Static all40 comparison
Inputs: all40 original manifests plus their unchanged original requests in
`p23-normal-os-26f-r17`, read only. This is a counterfactual static projection,
not a new rendered screenshot or native/OS acceptance.
Original manifest label pairs: exactly one intersection, B/writer-request-0021,
rows 5 (queue) and 26 (aggregation). Resolved labels: zero intersections in all
40 frames, including fixed cabin labels. Thirty frames have no movement.
The following is the complete movement list in local canvas coordinates:
| Frame | Label | Original | Resolved | displaced_by |
| --- | --- | --- | --- | --- |
| A/reader-request-0003 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
| A/writer-request-0014 | landing/0 | 563,468 | 845,468 | cabin/0/badge |
| A/writer-request-0021 | landing/0 | 563,468 | 875,468 | cabin/0/badge |
| A/writer-request-0025 | landing/0 | 563,468 | 855,468 | cabin/0/badge |
| A/writer-request-0030 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
| B/reader-request-0003 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
| B/writer-request-0014 | landing/0 | 563,468 | 1101,468 | cabin/0/badge, cabin/1/badge |
| B/writer-request-0014 | landing/1 | 819,468 | 1217,468 | cabin/1/badge, landing/0 |
| B/writer-request-0021 | landing/0 | 563,468 | 755,468 | cabin/0/rect |
| B/writer-request-0021 | landing/1 | 819,468 | 1011,468 | cabin/1/rect |
| B/writer-request-0021 | aggregation/0 | 563,372 | 825,372 | landing/2, cabin/0/badge |
| B/writer-request-0025 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
| B/writer-request-0030 | landing/1 | 819,468 | 1111,468 | cabin/0/badge, cabin/1/badge |
The affected queue anchor remains local [563,372], corresponding to original
manifest screenshot [642,469]. Its five members remain hidden and its complete
label remains unchanged. The historical reviewer index24/text26 disagreement
and all prior negatives remain untouched; no staff defect or acceptance credit
is inferred.
## Validation boundary
Worker runs Python syntax/local semantic tests only. GD controls are added but
not executed here. Root still must replay currentbad, run localized/native,
new normal OS A/B with indexed independent image review, then one full regression
and formal review/integration/ledger in the recorded order. No formal acceptance,
original88 credit, Windows, held-gesture, performance, or asset credit is claimed.
Local result: Python AST syntax PASS; 146 Python tests PASS (142 retained plus
four focused controls). All 142 pre-existing test method ASTs match HEAD exactly.
`git diff --check` PASS. Only the existing P23 GD marker source SHA was refreshed;
no marker requirements or policies changed. No Godot/native/OS/full was run.
@@ -0,0 +1,21 @@
# Repair37 portable recorded negative fixture
Candidate starts at `059c2f743be655c11627a2b19d8eab9f86a000a6` under the unchanged P23-03 contract. No Git write or Runner operation was performed.
`tests/fixtures/p23-03/recorded-badge-negative.json.gz` is copied byte-for-byte from Root's `.runtime/P23-03/build/audit-support/badge-repair37-portable-fixture/PROPOSED_PORTABLE_FIXTURE.json.gz` in the parent worktree directory. SHA256: `17b1596fb1dca0436cd656ac21a9dc754c75b4485335a0aebe533f70e1c1b5ac`; gzip length 83,518 bytes; decompressed length 3,490,211 bytes. Repeated decompression yields identical bytes.
The bundle records observed source `26f60095bff015efbf7d7cf89b5b0bbab1fec31e`, observed tested commit `dfe0dc44b0b88ace598c1b8bd05283c6084cb081`, and original PID 87103. These are preserved negative-input provenance, not acceptance evidence for 059 or this candidate. Original relative paths below are relative to the preserved `p23-normal-os-26f-r17` evidence root:
| Payload | Original relative path | Bytes | SHA256 |
| --- | --- | ---: | --- |
| request | `os-pair/B/writer-request-0021/request.json` | 655285 | `f9cd2d5ff9dd63d9cf7ae4a295ccef8eea16f75ac784696dc40906703731e1d8` |
| state | `os-pair/B/writer-stream/objects/724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82.json` | 1867011 | `724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82` |
| manifest | `os-pair/B/writer-request-0021/review-image-0/manifest.json` | 75726 | `a98782ec1263aba400d542862dd084d2f1dd4cf7a0851cf7f0679867e545a338` |
The loader verifies all three payload lengths and hashes on every call. Request uses JSON, report and state use the existing typed decoder; state retains the whole-Dictionary check. There is no runtime lookup, fallback, cache, extraction, or skip.
Local validation: all 146 Python tests pass; AST audit against 059c2f7 confirms 145 test methods are source-byte and AST identical, and the remaining method is AST identical after replacing only the manifest assignment value with the same fixed placeholder. Python syntax compilation passes. A fresh isolated copy of Git-tracked candidate files plus the fixture, without `.runtime`, passes Repair36LabelTests 4/4. Separate corrupt-payload and missing-fixture controls fail with fatal errors. No extra fixture is retained.
Frozen whole-file SHA256 values remain unchanged: renderer `4bc49a19a38b9c3b4d6bfc243ddf46f1f25469a8273adb5e812911ef9424a1f2`; checker `e41479ae51bccdfab3f31bd348794b441c2f341179bbe33222e2aacdf88f987f`.
Native and OS checks are still pending. Root's final local run on the new tested commit, raw geometry/native checks, new normal OS run, every indexed-image review, mandatory full 179, formal all 7, integration and ledger remain pending. No Godot or full suite was run by this worker.
+34
View File
@@ -637,6 +637,7 @@ func _build_life_observation() -> Dictionary:
&"at": rect.position + Vector2(2, 16), &"requests": requests,
&"text": RoomPresentation.supply_label(requests)})
model[&"badges"] = badges
_resolve_observation_labels(model)
var obstacles: Array[Rect2] = []
for cabin: Dictionary in model[&"cabins"]:
obstacles.append(cabin[&"rect"])
@@ -691,6 +692,39 @@ func _traffic_segments(band: Rect2, cell: Rect2, obstacles: Array[Rect2]) -> Arr
return []
func _resolve_observation_labels(model: Dictionary) -> void:
var placed: Array[Dictionary] = []
for i: int in model[&"cabins"].size():
var cabin: Dictionary = model[&"cabins"][i]
placed.append({&"id": "cabin/%d/rect" % i, &"rect": cabin[&"rect"]})
placed.append({&"id": "cabin/%d/badge" % i, &"rect": cabin[&"badge_rect"]})
for kind: String in ["business", "supply", "landing", "aggregation"]:
var collection: Array = model[&"badges"] if kind in ["business", "supply"] else model[&"landing_badges"] if kind == "landing" else model.get(&"actor_aggregations", [])
for i: int in collection.size():
var badge: Dictionary = collection[i]
if kind in ["business", "supply"] and badge[&"kind"] != kind:
continue
var anchor: Vector2 = badge[&"at"]
var at := anchor
var displaced_by: Array[String] = []
# Each collision advances to an existing right edge. Positive widths
# guarantee termination; touching edges are allowed.
while true:
var blocker: Dictionary = {}
for previous: Dictionary in placed:
if _readable_badge_rect(at, badge[&"text"]).intersection(previous[&"rect"]).has_area():
blocker = previous
break
if blocker.is_empty():
break
displaced_by.append(blocker[&"id"])
at.x = (blocker[&"rect"] as Rect2).end.x
badge[&"anchor"] = anchor
badge[&"at"] = at
badge[&"displaced_by"] = displaced_by.duplicate(true)
placed.append({&"id": "%s/%d" % [kind, i], &"rect": _readable_badge_rect(at, badge[&"text"])})
func _draw_life_badges(canvas: CanvasItem) -> void:
for badge: Dictionary in _life_observation.get(&"badges", []):
_draw_readable_badge(badge[&"at"], badge[&"text"], canvas)
Binary file not shown.
+26
View File
@@ -100,6 +100,31 @@ func repair28_geometry() -> void:
func _initialize() -> void:
call_deferred("run")
func repair36_labels() -> void:
var view := WorldPreview.new()
var members: Array[String] = ["guest/a", "guest/b", "guest/c"]
var identities: Array[Dictionary] = []
for key: String in members:
identities.append({&"key": key, &"id": key.trim_prefix("guest/"), &"state": 0, &"cell": Vector3i.ZERO, &"mode": &"idle"})
var queue := {&"at": Vector2(40,40), &"text": "待ち3人(他3人)", &"cell": Vector3i.ZERO, &"members": members.duplicate(), &"visible_count": 0, &"overflow": members.size()}
var aggregate := {&"at": queue[&"at"], &"text": "+3", &"members": identities.duplicate(true), &"drawn_keys": []}
var model := {&"cabins": [], &"badges": [], &"landing_badges": [queue], &"actor_aggregations": [aggregate]}
var blocked := Rect2(100,100,20,20)
model[&"cabins"] = [{&"rect": blocked, &"badge_at": Vector2(200,200), &"badge": "EV", &"badge_rect": view._readable_badge_rect(Vector2(200,200), "EV")}]
model[&"shaft_footprint"] = []
var region: Dictionary = view._life_landing_region({&"bounds": blocked, &"transit": false}, Vector3i.ZERO, model)
expect(region.get(&"no_lane", false) and region[&"badge_at"] == queue[&"at"], "lane-less stranded actor uses hidden queue anchor")
view._resolve_observation_labels(model)
expect(not view._readable_badge_rect(queue[&"at"], queue[&"text"]).intersection(view._readable_badge_rect(aggregate[&"at"], aggregate[&"text"])).has_area(), "hidden queue and stranded aggregate labels do not overlap")
expect(queue[&"text"] == "待ち3人(他3人)" and aggregate[&"text"] == "+3", "complete counts remain readable")
expect(queue[&"members"] == members and aggregate[&"members"] == identities and aggregate[&"drawn_keys"].is_empty(), "label placement preserves member identities and visibility")
expect(queue[&"at"] == queue[&"anchor"] and aggregate[&"anchor"] == queue[&"anchor"], "uncontested queue keeps anchor and stranded anchor remains recorded")
var solitary := {&"at": Vector2(400,400), &"text": "+1"}
model[&"actor_aggregations"] = [solitary]
view._resolve_observation_labels(model)
expect(solitary[&"at"] == solitary[&"anchor"] and solitary[&"displaced_by"].is_empty(), "uncontested label stays at anchor")
view.free()
func expect(value: bool, message: String) -> void:
checks += 1
if not value:
@@ -118,6 +143,7 @@ func run() -> void:
var clock: Variant = root.get_node("GameClock")
clock.set_paused(true)
repair28_geometry()
repair36_labels()
# Isolated real typed components test state labels, without claiming gameplay.
var labels: Dictionary = {}
for state: int in FacilityInstance.State.values():
+74 -3
View File
@@ -310,6 +310,9 @@ def paired_crop_noise(before, after, rect):
def drawing_rectangle_rows(report):
"""Derive identities and local positions; do not read audit verdicts."""
if any('anchor' in b for key in ('badges', 'landing_badges', 'actor_aggregations')
for b in report.get(key, [])):
validate_observation_labels(report)
expected = []
tower = 'scripts/ui/pixel_tower_art.gd'
people = 'scripts/ui/pixel_people.gd'
@@ -549,13 +552,16 @@ def write_frame_review(directory, frame, provenance):
return manifest, digest
def reviewed_pixel_categories(manifest, digest, verdict_path):
def reviewed_pixel_categories(manifest, digest, verdict_path, *, require_judgments=False):
"""The worker never writes verdicts; absent independent review earns nothing."""
if not Path(verdict_path).is_file():
return set()
verdict = json.loads(Path(verdict_path).read_bytes())
require(verdict['manifest_sha256'] == digest, 'review manifest SHA mismatch')
require(verdict['reviewer'] == 'independent-opus-direct-image', 'independent image reviewer required')
if require_judgments:
require(isinstance(verdict.get('judgments'), list), 'explicit indexed judgments required')
verdict = dict(verdict, rows=verdict['judgments'])
eligible = [r for r in manifest['rows'] if r['eligibility'] != 'outside']
require([v['row_index'] for v in verdict['rows']] == [r['row_index'] for r in eligible],
'review row coverage')
@@ -651,7 +657,7 @@ def inspect_offline_mirror(directory):
receipt_sha256=sha(folder/'cua-receipt.json')))
review_dir = ROOT/'docs/plans/original-completion-2026-10-02/execution/p23-03/offline-r07-review'/name
manifest, digest = write_frame_review(review_dir, frame, provenance)
credited = reviewed_pixel_categories(manifest, digest, review_dir/'independent-verdict.json')
credited = reviewed_pixel_categories(manifest, digest, review_dir/'independent-verdict.json', require_judgments=True)
present = {r['category'] for r in frame['rows'] if r['eligibility'] != 'outside'}
overlays = [dict(cell=r['cell'], rect=r['rect']) for r in report['traffic']
if (r['traffic']['current_density'] > 0 or r['traffic']['temporary_block'])
@@ -803,6 +809,61 @@ def badge_rectangle(at, text):
return list(at) + [len(text) * 10 + 6, 14]
def observation_label_layout(report):
"""Independent ordered rectangle placement; never consume reported at/reasons."""
placed, result = [], []
for i, cabin in enumerate(report['cabins']):
placed.extend([(f'cabin/{i}/rect', cabin['rect']),
(f'cabin/{i}/badge', badge_rectangle(cabin['badge_at'], cabin['badge']))])
for kind, collection in [('business', 'badges'), ('supply', 'badges'),
('landing', 'landing_badges'), ('aggregation', 'actor_aggregations')]:
for i, row in enumerate(report.get(collection, [])):
if collection == 'badges' and row['kind'] != kind:
continue
anchor = list(row.get('anchor', row['at']))
at, reasons = anchor[:], []
while True:
conflict = next(((identity, rect) for identity, rect in placed
if intersects(badge_rectangle(at, row['text']), rect)), None)
if conflict is None:
break
identity, rect = conflict
reasons.append(identity)
at[0] = rect[0] + rect[2]
result.append((collection, i, dict(anchor=anchor, at=at, displaced_by=reasons)))
placed.append((f'{kind}/{i}', badge_rectangle(at, row['text'])))
return result
def observation_anchor_report(report):
"""Projection for existing raw-state semantic recomputation, not evidence."""
import copy
raw = copy.deepcopy(report)
for collection in ('badges', 'landing_badges', 'actor_aggregations'):
for row in raw.get(collection, []):
if 'anchor' in row:
row['at'] = row.pop('anchor')
row.pop('displaced_by', None)
return raw
def validate_observation_labels(report):
require(all(key in report for key in ('cabins', 'badges', 'landing_badges', 'actor_aggregations')),
'missing observation label collections')
rectangles = [badge_rectangle(c['badge_at'], c['badge']) for c in report['cabins']]
rectangles += [badge_rectangle(b['at'], b['text'])
for key in ('badges', 'landing_badges', 'actor_aggregations')
for b in report.get(key, [])]
for i, rect in enumerate(rectangles):
require(not any(intersects(rect, other) for other in rectangles[:i]),
'observation label intersection')
for collection, index, wanted in observation_label_layout(report):
row = report[collection][index]
for key, value in wanted.items():
require(key in row, 'missing label ' + key)
close(row[key], value, 'observation label/' + key)
def intersects(a, b):
cut = rect_intersection(a, b)
return cut[2] > 0 and cut[3] > 0
@@ -965,6 +1026,9 @@ def validate_life_overlay(report, root=ROOT, snapshot=None):
def compare_queues(snapshot, report):
if any('anchor' in b for b in report.get('landing_badges', [])):
validate_observation_labels(report)
report = observation_anchor_report(report)
runtime = snapshot['systems']['runtime']
transform = Transform(runtime, report)
facilities = codec.indexed(runtime['facilities']['instances'], 'instance_id')
@@ -1222,6 +1286,9 @@ def compare_goods(snapshot, report, root=ROOT):
def compare_badges(state, report):
if any('anchor' in b for b in report.get('badges', [])):
validate_observation_labels(report)
report = observation_anchor_report(report)
runtime = state['systems']['runtime']
transform = Transform(runtime, report)
expected = []
@@ -1490,6 +1557,9 @@ def landing_actor_region(state, report, transform, cell, region, transit):
def compare_actor_geometry(state, report):
if any('anchor' in b for b in report.get('actor_aggregations', [])):
validate_observation_labels(report)
report = observation_anchor_report(report)
runtime = state['systems']['runtime']
compare_queues(state, report)
compare_room_services(state, report)
@@ -1800,6 +1870,7 @@ def inspect_stream(directory, root=ROOT):
report = read_object(directory, row['report'])
fields = compare_actor_rows(state, report, root)
require(report['elevators'] == elevator_report(state), 'typed elevator report mismatch')
validate_observation_labels(report)
compare_room_services(state, report, root)
compare_rest(state, report, read_object(directory, row['rest_audit']), root)
compare_queues(state, report)
@@ -2240,7 +2311,7 @@ def inspect_os_requests(directory, layout, role, pid, root=ROOT):
require(frame['mechanical_pass'], 'frame mechanical checks: ' + str(frame['errors'])
+ str([r['failures'] for r in frame['rows'] if not r['mechanical_pass']]))
pixel_categories.update(reviewed_pixel_categories(
manifest, manifest_sha, review_dir/'independent-verdict.json'))
manifest, manifest_sha, review_dir/'independent-verdict.json', require_judgments=True))
records.append(dict(request=request, ack=ack, observed=observed, cua=cua, request_sha256=digest))
require(len(seen) == len(bindings), 'unattributed screenshot bindings')
return records, pixel_categories
@@ -8,6 +8,7 @@ import contextlib
import io
import json
import hashlib
import gzip
import struct
import zlib
import tempfile
@@ -2293,5 +2294,101 @@ class FrameReviewTests(unittest.TestCase):
self.assertEqual(check.reviewed_pixel_categories(manifest, digest, verdict_path), set())
class Repair36LabelTests(unittest.TestCase):
inputs = check.ROOT / 'tests/fixtures/p23-03/recorded-badge-negative.json.gz'
def _recorded_payloads(self):
bundle = json.loads(gzip.decompress(self.inputs.read_bytes()))
expected = {
'request': (655285, 'f9cd2d5ff9dd63d9cf7ae4a295ccef8eea16f75ac784696dc40906703731e1d8'),
'state': (1867011, '724d8fe362ba3e05eddd83c4b7c54da101899df6cd91ae88250feea3d4feff82'),
'manifest': (75726, 'a98782ec1263aba400d542862dd084d2f1dd4cf7a0851cf7f0679867e545a338'),
}
payloads = {}
for name, (length, digest) in expected.items():
payload = bundle['files'][name]['raw_utf8'].encode('utf-8')
check.require(len(payload) == length and hashlib.sha256(payload).hexdigest() == digest,
'recorded fixture payload mismatch: ' + name)
payloads[name] = payload
return payloads
def recorded(self):
payloads = self._recorded_payloads()
request = json.loads(payloads['request'])
typed_state = json.loads(payloads['state'])
check.require(typed_state.get('type') == 'Dictionary', 'whole dictionary required')
return request, check.decode(typed_state), check.decode(request['report'])
def resolved(self, report):
result = copy.deepcopy(report)
for collection, index, fields in check.observation_label_layout(report):
result[collection][index].update(fields)
return result
def test_recorded_negative_and_positive_with_raw_semantics(self):
request, state, old = self.recorded()
rectangles = check.drawing_rectangle_rows(old)
self.assertTrue(check.intersects(rectangles[5][2], rectangles[26][2]))
with self.assertRaises(ValueError):
check.validate_observation_labels(old)
report = self.resolved(old)
check.validate_observation_labels(report)
check.compare_queues(state, report)
check.compare_badges(state, report)
check.compare_actor_geometry(state, report)
queue = next(b for b in report['landing_badges'] if b['visible_count'] == 0)
manifest = json.loads(self._recorded_payloads()['manifest'])
original = manifest['rows'][5]
self.assertEqual(queue['anchor'], original['local_rect'][:2])
self.assertEqual(original['screenshot_rect'][:2], [642,469])
self.assertEqual(queue['members'], next(b['members'] for b in old['landing_badges'] if b['visible_count'] == 0))
def test_recorded_forgery_controls(self):
_, state, old = self.recorded()
good = self.resolved(old)
for attack in ('at', 'displaced_by', 'count', 'hidden', 'anchor'):
report = copy.deepcopy(good)
row = report['actor_aggregations'][0]
if attack == 'at': row['at'][0] += 1
elif attack == 'displaced_by': row['displaced_by'] = []
elif attack == 'count': row['text'] = '+'
elif attack == 'hidden': report['actor_aggregations'] = []
else: row['anchor'][0] += 1
with self.subTest(attack=attack), self.assertRaises(ValueError):
check.validate_observation_labels(report)
check.compare_queues(state, report)
check.compare_actor_geometry(state, report)
def test_generic_order_and_edge_contact(self):
for kinds in [('business', 'aggregation'), ('supply', 'landing'),
('business', 'supply', 'landing', 'aggregation')]:
report = dict(cabins=[], badges=[], landing_badges=[], actor_aggregations=[])
for kind in kinds:
key = 'badges' if kind in ('business', 'supply') else 'landing_badges' if kind == 'landing' else 'actor_aggregations'
report[key].append(dict(kind=kind, at=[10,20], text='abc'))
result = self.resolved(report)
check.validate_observation_labels(result)
positions = [fields['at'] for _, _, fields in check.observation_label_layout(result)]
self.assertEqual(positions, [[10+36*i,20] for i in range(len(kinds))])
report = dict(cabins=[dict(rect=[0,50,20,20], badge_at=[10,20], badge='abc')],
badges=[], landing_badges=[dict(at=[10,20], text='x')], actor_aggregations=[])
self.assertEqual(self.resolved(report)['landing_badges'][0]['at'], [46,20])
report['landing_badges'][0]['at'] = [46,20]
self.assertEqual(self.resolved(report)['landing_badges'][0]['displaced_by'], [])
def test_future_review_requires_indexed_judgments(self):
manifest = dict(rows=[dict(row_index=7, category='queue', eligibility='visible', mechanical_pass=True)], mechanical_pass=True)
verdict = dict(manifest_sha256='digest', reviewer='independent-opus-direct-image',
state_bits='1', position_bits='1', category_bits='1')
with tempfile.TemporaryDirectory() as folder:
path = Path(folder)/'verdict.json'
path.write_text(json.dumps(verdict))
with self.assertRaisesRegex(ValueError, 'indexed judgments'):
check.reviewed_pixel_categories(manifest, 'digest', path, require_judgments=True)
verdict['judgments'] = [dict(row_index=7, category=True, state=True, position=True)]
path.write_text(json.dumps(verdict))
self.assertEqual(check.reviewed_pixel_categories(manifest, 'digest', path, require_judgments=True), {'queue'})
if __name__ == '__main__':
unittest.main()