devlyn-cli 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +6 -1
  2. package/bin/devlyn.js +69 -23
  3. package/config/skills/_shared/adapters/claude.md +3 -2
  4. package/config/skills/_shared/adapters/codex.md +1 -1
  5. package/config/skills/_shared/archive_run.py +5 -3
  6. package/config/skills/_shared/codex-config.md +13 -8
  7. package/config/skills/_shared/codex-monitored.sh +12 -19
  8. package/config/skills/_shared/collect-codex-findings.py +1 -0
  9. package/config/skills/_shared/engine-preflight.md +2 -0
  10. package/config/skills/_shared/finish-gate.py +4 -1
  11. package/config/skills/_shared/invocation-receipt.py +194 -5
  12. package/config/skills/_shared/judge-role-evidence.py +33 -6
  13. package/config/skills/_shared/phase-prompt-render.py +2 -0
  14. package/config/skills/_shared/platform-support.py +370 -0
  15. package/config/skills/_shared/process-evidence.py +20 -3
  16. package/config/skills/_shared/resolve-bootstrap.py +131 -63
  17. package/config/skills/_shared/resolve-stop-hook.py +3 -1
  18. package/config/skills/_shared/role-config.py +7 -5
  19. package/config/skills/_shared/run-bounded.py +35 -21
  20. package/config/skills/_shared/spec-verify-check.py +393 -167
  21. package/config/skills/_shared/state-phase-write.py +26 -9
  22. package/config/skills/_shared/task-complete.py +337 -97
  23. package/config/skills/_shared/verify-merge-findings.py +9 -8
  24. package/config/skills/devlyn:ideate/references/spec-template.md +1 -1
  25. package/config/skills/devlyn:resolve/SKILL.md +10 -7
  26. package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
  27. package/config/skills/devlyn:resolve/references/phases/build-gate.md +1 -1
  28. package/config/skills/devlyn:resolve/references/phases/verify.md +14 -10
  29. package/config/skills/devlyn:resolve/references/state-schema.md +1 -1
  30. package/config/skills/devlyn:resolve/references/task-completion.md +28 -1
  31. package/package.json +1 -1
  32. package/scripts/lint-skills.sh +6 -5
@@ -89,6 +89,7 @@ Exit codes:
89
89
 
90
90
  from __future__ import annotations
91
91
 
92
+ import runpy
92
93
  import importlib.util
93
94
  import json
94
95
  import hashlib
@@ -843,10 +844,21 @@ def validate_expected_shape(data) -> str | None:
843
844
  return None
844
845
 
845
846
 
846
- def validate_expected_against_sibling_spec(expected_path: Path, data: object) -> str | None:
847
+ def validate_inline_shape(data: object) -> str | None:
848
+ if isinstance(data, dict):
849
+ unknown = sorted(set(data) - {"verification_commands"})
850
+ if unknown:
851
+ return (
852
+ f"unsupported inline key(s): {', '.join(unknown)}; inline carriers "
853
+ "support only verification_commands. Encode these checks as commands, "
854
+ "or use a real spec with sibling spec.expected.json."
855
+ )
856
+ return validate_shape(data) or validate_expected_shape(data)
857
+
858
+
859
+ def validate_expected_against_sibling_spec(spec_path: Path, data: object) -> str | None:
847
860
  if not isinstance(data, dict):
848
861
  return None
849
- spec_path = expected_path.with_name("spec.md")
850
862
  if not spec_path.is_file():
851
863
  return None
852
864
  try:
@@ -880,8 +892,7 @@ def validate_expected_against_sibling_spec(expected_path: Path, data: object) ->
880
892
  )
881
893
 
882
894
 
883
- def validate_sibling_spec_complexity(expected_path: Path) -> str | None:
884
- spec_path = expected_path.with_name("spec.md")
895
+ def validate_sibling_spec_complexity(spec_path: Path) -> str | None:
885
896
  if not spec_path.is_file():
886
897
  return None
887
898
  try:
@@ -1012,7 +1023,7 @@ def resolve_required_risk_probe_requirements(
1012
1023
  return ([], err)
1013
1024
  reqs = (data or {}).get("required_risk_probe_requirements", [])
1014
1025
  else:
1015
- _section_found, block = extract_verification_block(source_md.read_text())
1026
+ _section_found, block = extract_verification_block(source_md.read_text(encoding="utf-8"))
1016
1027
  if block is None:
1017
1028
  return ([], None)
1018
1029
  try:
@@ -1022,7 +1033,7 @@ def resolve_required_risk_probe_requirements(
1022
1033
  reqs = parsed.get("required_risk_probe_requirements", []) if isinstance(parsed, dict) else []
1023
1034
  if not isinstance(reqs, list):
1024
1035
  return ([], "required_risk_probe_requirements must be a list")
1025
- verification_text = extract_verification_text(source_md.read_text())
1036
+ verification_text = extract_verification_text(source_md.read_text(encoding="utf-8"))
1026
1037
  for i, req in enumerate(reqs):
1027
1038
  err = validate_required_risk_probe_requirement(req, i, verification_text)
1028
1039
  if err:
@@ -1044,12 +1055,12 @@ def load_risk_probes(
1044
1055
  if source_md is None or not source_md.is_file():
1045
1056
  return ([], "risk-probes.jsonl exists but source markdown is unavailable")
1046
1057
 
1047
- verification_text = extract_verification_text(source_md.read_text())
1058
+ verification_text = extract_verification_text(source_md.read_text(encoding="utf-8"))
1048
1059
  if not verification_text:
1049
1060
  return ([], "risk-probes.jsonl exists but source has no <!-- devlyn:verification --> section")
1050
1061
 
1051
1062
  probes: list[dict] = []
1052
- for index, line in enumerate(probes_path.read_text().splitlines()):
1063
+ for index, line in enumerate(probes_path.read_text(encoding="utf-8").splitlines()):
1053
1064
  if not line.strip():
1054
1065
  continue
1055
1066
  try:
@@ -1099,7 +1110,7 @@ def read_source(work: Path, devlyn_dir: Path) -> tuple[str | None, Path | None]:
1099
1110
  if not state_path.is_file():
1100
1111
  return (None, None)
1101
1112
  try:
1102
- state = loads_strict_json(state_path.read_text())
1113
+ state = loads_strict_json(state_path.read_text(encoding="utf-8"))
1103
1114
  except (ValueError, OSError):
1104
1115
  return (None, None)
1105
1116
  src = state.get("source") or {}
@@ -1123,7 +1134,7 @@ def read_state(devlyn_dir: Path) -> dict:
1123
1134
  if not state_path.is_file():
1124
1135
  return {}
1125
1136
  try:
1126
- data = loads_strict_json(state_path.read_text())
1137
+ data = loads_strict_json(state_path.read_text(encoding="utf-8"))
1127
1138
  except (ValueError, OSError):
1128
1139
  return {}
1129
1140
  return data if isinstance(data, dict) else {}
@@ -1176,7 +1187,7 @@ def source_integrity_error(src_type: str | None, state: dict, source_md: Path |
1176
1187
 
1177
1188
  def load_expected_contract(expected_path: Path) -> tuple[dict | None, str | None]:
1178
1189
  try:
1179
- data = loads_strict_json(expected_path.read_text())
1190
+ data = loads_strict_json(expected_path.read_text(encoding="utf-8"))
1180
1191
  except ValueError as e:
1181
1192
  return (None, f"{expected_path} has invalid JSON: {e}")
1182
1193
  except OSError as e:
@@ -1198,7 +1209,7 @@ def stage_from_source(md: Path, devlyn_dir: Path) -> tuple[bool, str | None]:
1198
1209
  error=None → the sentinel is absent entirely (handwritten spec or
1199
1210
  generated source missing the contract).
1200
1211
  """
1201
- section_found, block = extract_verification_block(md.read_text())
1212
+ section_found, block = extract_verification_block(md.read_text(encoding="utf-8"))
1202
1213
  if not section_found:
1203
1214
  return (False, None)
1204
1215
  if block is None:
@@ -1207,12 +1218,12 @@ def stage_from_source(md: Path, devlyn_dir: Path) -> tuple[bool, str | None]:
1207
1218
  data = loads_strict_json(block)
1208
1219
  except ValueError as e:
1209
1220
  return (False, f"`<!-- devlyn:verification -->` ```json``` block in {md} has invalid JSON: {e}")
1210
- err = validate_shape(data)
1221
+ err = validate_inline_shape(data)
1211
1222
  if err:
1212
1223
  return (False, f"`<!-- devlyn:verification -->` ```json``` block in {md}: {err}")
1213
1224
  normalized = {"verification_commands": data["verification_commands"]}
1214
1225
  devlyn_dir.mkdir(parents=True, exist_ok=True)
1215
- (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n")
1226
+ (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n", encoding="utf-8")
1216
1227
  return (True, None)
1217
1228
 
1218
1229
 
@@ -1235,6 +1246,9 @@ def stage_from_expected(
1235
1246
  if err:
1236
1247
  return (True, False, err, expected_path, None)
1237
1248
  assert data is not None
1249
+ err = validate_sibling_spec_complexity(md) or validate_expected_against_sibling_spec(md, data)
1250
+ if err:
1251
+ return (True, False, f"{expected_path}: {err}", expected_path, None)
1238
1252
  commands = data.get("verification_commands")
1239
1253
  if not commands:
1240
1254
  spec_path = devlyn_dir / "spec-verify.json"
@@ -1243,7 +1257,7 @@ def stage_from_expected(
1243
1257
  return (True, False, None, expected_path, data)
1244
1258
  normalized = {"verification_commands": commands}
1245
1259
  devlyn_dir.mkdir(parents=True, exist_ok=True)
1246
- (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n")
1260
+ (devlyn_dir / "spec-verify.json").write_text(json.dumps(normalized, indent=2) + "\n", encoding="utf-8")
1247
1261
  return (True, True, None, expected_path, data)
1248
1262
 
1249
1263
 
@@ -1278,7 +1292,7 @@ def write_malformed_finding(
1278
1292
  "blocking": True,
1279
1293
  "status": "open",
1280
1294
  }
1281
- with findings_path.open("w") as fh:
1295
+ with findings_path.open("w", encoding="utf-8") as fh:
1282
1296
  fh.write(json.dumps(finding) + "\n")
1283
1297
 
1284
1298
 
@@ -1300,7 +1314,7 @@ def write_risk_probe_integrity_finding(devlyn_dir: Path, error: str) -> None:
1300
1314
  "blocking": True,
1301
1315
  "status": "open",
1302
1316
  }
1303
- with findings_path.open("w") as fh:
1317
+ with findings_path.open("w", encoding="utf-8") as fh:
1304
1318
  fh.write(json.dumps(finding) + "\n")
1305
1319
 
1306
1320
 
@@ -1321,14 +1335,14 @@ def diff_text_for_expected(work: Path, devlyn_dir: Path, state: dict) -> tuple[s
1321
1335
  external_diff = devlyn_dir / "external-diff.patch"
1322
1336
  if external_diff.is_file():
1323
1337
  try:
1324
- return (external_diff.read_text(), None)
1338
+ return (external_diff.read_text(encoding="utf-8"), None)
1325
1339
  except OSError as e:
1326
1340
  return ("", f"cannot read {external_diff}: {e}")
1327
1341
  base_sha = ((state.get("base_ref") or {}).get("sha") or "").strip()
1328
1342
  cmd = ["git", "diff"]
1329
1343
  if base_sha:
1330
1344
  cmd.append(base_sha)
1331
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1345
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1332
1346
  if proc.returncode != 0:
1333
1347
  return ("", (proc.stderr or proc.stdout or "git diff failed").strip())
1334
1348
  return (proc.stdout or "", None)
@@ -1340,7 +1354,7 @@ def count_deps_added(work: Path, state: dict) -> int:
1340
1354
  if base_sha:
1341
1355
  cmd.append(base_sha)
1342
1356
  cmd.extend(["--", "package.json"])
1343
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1357
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1344
1358
  if proc.returncode != 0:
1345
1359
  return 0
1346
1360
  in_deps = False
@@ -1365,7 +1379,7 @@ def changed_files(work: Path, state: dict, devlyn_dir: Path) -> list[str]:
1365
1379
  if external_diff.is_file():
1366
1380
  names: list[str] = []
1367
1381
  try:
1368
- external_text = external_diff.read_text()
1382
+ external_text = external_diff.read_text(encoding="utf-8")
1369
1383
  except OSError:
1370
1384
  return []
1371
1385
  for line in external_text.splitlines():
@@ -1378,7 +1392,7 @@ def changed_files(work: Path, state: dict, devlyn_dir: Path) -> list[str]:
1378
1392
  cmd = ["git", "diff", "--name-only"]
1379
1393
  if base_sha:
1380
1394
  cmd.append(base_sha)
1381
- proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True)
1395
+ proc = subprocess.run(cmd, cwd=str(work), capture_output=True, text=True, encoding="utf-8")
1382
1396
  if proc.returncode != 0:
1383
1397
  return []
1384
1398
  return [line.strip() for line in (proc.stdout or "").splitlines() if line.strip()]
@@ -1856,7 +1870,7 @@ def run_check_mode(md_path: Path) -> int:
1856
1870
  if not md_path.is_file():
1857
1871
  print(f"[spec-verify --check] error: {md_path} not found", file=sys.stderr)
1858
1872
  return 2
1859
- text = md_path.read_text()
1873
+ text = md_path.read_text(encoding="utf-8")
1860
1874
  frontmatter_err = validate_present_spec_complexity(text)
1861
1875
  if frontmatter_err:
1862
1876
  print(f"[spec-verify --check] {md_path}: {frontmatter_err}", file=sys.stderr)
@@ -1891,7 +1905,7 @@ def run_check_mode(md_path: Path) -> int:
1891
1905
  file=sys.stderr,
1892
1906
  )
1893
1907
  return 2
1894
- err = validate_shape(data)
1908
+ err = validate_inline_shape(data)
1895
1909
  if err:
1896
1910
  print(f"[spec-verify --check] {md_path}: shape error: {err}", file=sys.stderr)
1897
1911
  return 2
@@ -1914,11 +1928,11 @@ def run_check_expected_mode(expected_path: Path) -> int:
1914
1928
  if err:
1915
1929
  print(f"[spec-verify --check-expected] {expected_path}: shape error: {err}", file=sys.stderr)
1916
1930
  return 2
1917
- complexity_err = validate_sibling_spec_complexity(expected_path)
1931
+ complexity_err = validate_sibling_spec_complexity(expected_path.with_name("spec.md"))
1918
1932
  if complexity_err:
1919
1933
  print(f"[spec-verify --check-expected] {expected_path}: shape error: {complexity_err}", file=sys.stderr)
1920
1934
  return 2
1921
- sibling_err = validate_expected_against_sibling_spec(expected_path, _data)
1935
+ sibling_err = validate_expected_against_sibling_spec(expected_path.with_name("spec.md"), _data)
1922
1936
  if sibling_err:
1923
1937
  print(f"[spec-verify --check-expected] {expected_path}: shape error: {sibling_err}", file=sys.stderr)
1924
1938
  return 2
@@ -1947,6 +1961,7 @@ def run_self_test() -> int:
1947
1961
  cwd=help_tmp,
1948
1962
  capture_output=True,
1949
1963
  text=True,
1964
+ encoding="utf-8",
1950
1965
  )
1951
1966
  if help_result.returncode != 0 or "usage:" not in help_result.stdout:
1952
1967
  print(f"{help_flag} did not return usage successfully", file=sys.stderr)
@@ -1959,6 +1974,7 @@ def run_self_test() -> int:
1959
1974
  cwd=help_tmp,
1960
1975
  capture_output=True,
1961
1976
  text=True,
1977
+ encoding="utf-8",
1962
1978
  )
1963
1979
  if unknown_result.returncode != 2 or "unknown argument" not in unknown_result.stderr:
1964
1980
  print("unknown option did not fail closed", file=sys.stderr)
@@ -1988,15 +2004,15 @@ def run_self_test() -> int:
1988
2004
  else:
1989
2005
  raise AssertionError(f"malformed brace glob accepted: {entry}")
1990
2006
  spec_md = work / "spec.md"
1991
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
2007
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
1992
2008
  (devlyn / "pipeline.state.json").write_text(json.dumps({
1993
2009
  "source": {"type": "spec", "spec_path": str(spec_md)}
1994
- }))
2010
+ }), encoding="utf-8")
1995
2011
  (devlyn / "spec-verify.json").write_text(json.dumps({
1996
2012
  "verification_commands": [
1997
2013
  {"cmd": "printf ok", "exit_code": 0, "stdout_contains": ["ok"]}
1998
2014
  ]
1999
- }) + "\n")
2015
+ }) + "\n", encoding="utf-8")
2000
2016
  probes_dir = devlyn / "probes"
2001
2017
  probes_dir.mkdir()
2002
2018
  probe_script = probes_dir / "P1.py"
@@ -2018,7 +2034,7 @@ def run_self_test() -> int:
2018
2034
  ],
2019
2035
  },
2020
2036
  }
2021
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n")
2037
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n", encoding="utf-8")
2022
2038
  loaded_probes, loaded_probe_error = load_risk_probes(
2023
2039
  devlyn, spec_md, require_present=True
2024
2040
  )
@@ -2068,7 +2084,7 @@ def run_self_test() -> int:
2068
2084
  inline_timeout_spec, inline_timeout_devlyn
2069
2085
  )
2070
2086
  inline_staged = loads_strict_json(
2071
- (inline_timeout_devlyn / "spec-verify.json").read_text()
2087
+ (inline_timeout_devlyn / "spec-verify.json").read_text(encoding="utf-8")
2072
2088
  )
2073
2089
  if (
2074
2090
  not staged
@@ -2089,7 +2105,7 @@ def run_self_test() -> int:
2089
2105
  "verification_commands": [
2090
2106
  {"cmd": "printf ok", "timeout_sec": invalid_timeout}
2091
2107
  ]
2092
- }) + "\n")
2108
+ }) + "\n", encoding="utf-8")
2093
2109
  found, staged, sibling_error, _path, _data = stage_from_expected(
2094
2110
  sibling_timeout_spec, sibling_timeout_devlyn
2095
2111
  )
@@ -2104,12 +2120,12 @@ def run_self_test() -> int:
2104
2120
  "verification_commands": [
2105
2121
  {"cmd": "printf ok", "timeout_sec": 5}
2106
2122
  ]
2107
- }) + "\n")
2123
+ }) + "\n", encoding="utf-8")
2108
2124
  found, staged, sibling_error, _path, _data = stage_from_expected(
2109
2125
  sibling_timeout_spec, sibling_timeout_devlyn
2110
2126
  )
2111
2127
  sibling_staged = loads_strict_json(
2112
- (sibling_timeout_devlyn / "spec-verify.json").read_text()
2128
+ (sibling_timeout_devlyn / "spec-verify.json").read_text(encoding="utf-8")
2113
2129
  )
2114
2130
  if (
2115
2131
  not found
@@ -2149,23 +2165,24 @@ def run_self_test() -> int:
2149
2165
  "run_id": "rs-timeout-run",
2150
2166
  "source": {"type": "spec", "spec_path": str(timeout_run_spec)},
2151
2167
  "phases": {"build_gate": {"round": 2}},
2152
- }))
2168
+ }), encoding="utf-8")
2153
2169
  timeout_run = subprocess.run(
2154
2170
  [sys.executable, script_path],
2155
2171
  cwd=timeout_run_root,
2156
2172
  capture_output=True,
2157
2173
  text=True,
2174
+ encoding="utf-8",
2158
2175
  )
2159
2176
  if timeout_run.returncode == 0:
2160
2177
  print("declared one-second timeout did not fail", file=sys.stderr)
2161
2178
  return 1
2162
2179
  timeout_document = loads_strict_json(
2163
- (timeout_run_devlyn / "spec-verify.results.json").read_text()
2180
+ (timeout_run_devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2164
2181
  )
2165
2182
  timeout_results = timeout_document["commands"]
2166
2183
  timeout_findings = [
2167
2184
  loads_strict_json(line)
2168
- for line in (timeout_run_devlyn / output_findings_name()).read_text().splitlines()
2185
+ for line in (timeout_run_devlyn / output_findings_name()).read_text(encoding="utf-8").splitlines()
2169
2186
  if line.strip()
2170
2187
  ]
2171
2188
  if (
@@ -2185,7 +2202,7 @@ def run_self_test() -> int:
2185
2202
  ".devlyn/process-evidence/rs-timeout-run/build_gate/round-2/manifest.json"
2186
2203
  )
2187
2204
  timeout_manifest = loads_strict_json(
2188
- (timeout_run_root / timeout_manifest_path).read_text()
2205
+ (timeout_run_root / timeout_manifest_path).read_text(encoding="utf-8")
2189
2206
  )
2190
2207
  if (
2191
2208
  timeout_carrier.get("manifest", {}).get("path") != timeout_manifest_path
@@ -2249,7 +2266,7 @@ def run_self_test() -> int:
2249
2266
  "run_id": "rs-runner-env",
2250
2267
  "source": {"type": "spec", "spec_path": str(runner_env_spec)},
2251
2268
  "phases": {"verify": {"round": 3}},
2252
- }))
2269
+ }), encoding="utf-8")
2253
2270
  runner_env = os.environ.copy()
2254
2271
  runner_env.update({
2255
2272
  "SPEC_VERIFY_PHASE": "verify_mechanical",
@@ -2262,9 +2279,10 @@ def run_self_test() -> int:
2262
2279
  env=runner_env,
2263
2280
  capture_output=True,
2264
2281
  text=True,
2282
+ encoding="utf-8",
2265
2283
  )
2266
2284
  runner_env_document = loads_strict_json(
2267
- (runner_env_devlyn / "spec-verify.results.json").read_text()
2285
+ (runner_env_devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2268
2286
  )
2269
2287
  runner_env_results = runner_env_document["commands"]
2270
2288
  if runner_env_run.returncode != 0 or not runner_env_results[0].get("pass"):
@@ -2285,6 +2303,7 @@ def run_self_test() -> int:
2285
2303
  env=env,
2286
2304
  capture_output=True,
2287
2305
  text=True,
2306
+ encoding="utf-8",
2288
2307
  )
2289
2308
  if validate_without_digest.returncode != 0:
2290
2309
  print("--validate-risk-probes rejected valid probes without digest", file=sys.stderr)
@@ -2296,6 +2315,7 @@ def run_self_test() -> int:
2296
2315
  env=env,
2297
2316
  capture_output=True,
2298
2317
  text=True,
2318
+ encoding="utf-8",
2299
2319
  )
2300
2320
  if digest_run.returncode != 0:
2301
2321
  print("--print-risk-probes-digest rejected valid probes", file=sys.stderr)
@@ -2312,23 +2332,24 @@ def run_self_test() -> int:
2312
2332
  "risk_profile": {"risk_probes_enabled": True},
2313
2333
  "risk_probes_digest": risk_digest,
2314
2334
  "phases": {"build_gate": {"round": 4}},
2315
- }))
2335
+ }), encoding="utf-8")
2316
2336
  good = subprocess.run(
2317
2337
  [sys.executable, script_path, "--include-risk-probes"],
2318
2338
  cwd=work,
2319
2339
  env=env,
2320
2340
  capture_output=True,
2321
2341
  text=True,
2342
+ encoding="utf-8",
2322
2343
  )
2323
2344
  if good.returncode != 0:
2324
2345
  print(good.stderr, file=sys.stderr)
2325
2346
  return 1
2326
2347
  good_document = loads_strict_json(
2327
- (devlyn / "spec-verify.results.json").read_text()
2348
+ (devlyn / "spec-verify.results.json").read_text(encoding="utf-8")
2328
2349
  )
2329
2350
  good_manifest_path = good_document.get("process_evidence", {}).get("manifest", {}).get("path")
2330
2351
  good_manifest = (
2331
- loads_strict_json((work / good_manifest_path).read_text())
2352
+ loads_strict_json((work / good_manifest_path).read_text(encoding="utf-8"))
2332
2353
  if isinstance(good_manifest_path, str)
2333
2354
  else {}
2334
2355
  )
@@ -2349,6 +2370,7 @@ def run_self_test() -> int:
2349
2370
  env=env,
2350
2371
  capture_output=True,
2351
2372
  text=True,
2373
+ encoding="utf-8",
2352
2374
  )
2353
2375
  if mutated_script.returncode == 0:
2354
2376
  print("--include-risk-probes accepted mutated probe script bytes", file=sys.stderr)
@@ -2362,13 +2384,14 @@ def run_self_test() -> int:
2362
2384
 
2363
2385
  mutated_payload = dict(risk_probe_payload)
2364
2386
  mutated_payload["id"] = "P1-mutated"
2365
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(mutated_payload) + "\n")
2387
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(mutated_payload) + "\n", encoding="utf-8")
2366
2388
  mutated_jsonl = subprocess.run(
2367
2389
  [sys.executable, script_path, "--include-risk-probes"],
2368
2390
  cwd=work,
2369
2391
  env=env,
2370
2392
  capture_output=True,
2371
2393
  text=True,
2394
+ encoding="utf-8",
2372
2395
  )
2373
2396
  if mutated_jsonl.returncode == 0:
2374
2397
  print("--include-risk-probes accepted mutated risk-probes.jsonl bytes", file=sys.stderr)
@@ -2378,18 +2401,19 @@ def run_self_test() -> int:
2378
2401
  print("mutated risk-probes.jsonl did not emit correctness.risk-probe-integrity", file=sys.stderr)
2379
2402
  print(integrity_findings, file=sys.stderr)
2380
2403
  return 1
2381
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n")
2404
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(risk_probe_payload) + "\n", encoding="utf-8")
2382
2405
 
2383
2406
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2384
2407
  "source": {"type": "spec", "spec_path": str(spec_md)},
2385
2408
  "risk_profile": {"risk_probes_enabled": True},
2386
- }))
2409
+ }), encoding="utf-8")
2387
2410
  missing_digest = subprocess.run(
2388
2411
  [sys.executable, script_path, "--include-risk-probes"],
2389
2412
  cwd=work,
2390
2413
  env=env,
2391
2414
  capture_output=True,
2392
2415
  text=True,
2416
+ encoding="utf-8",
2393
2417
  )
2394
2418
  if missing_digest.returncode == 0:
2395
2419
  print("--include-risk-probes accepted enabled risk probes with missing digest", file=sys.stderr)
@@ -2407,6 +2431,7 @@ def run_self_test() -> int:
2407
2431
  env=env,
2408
2432
  capture_output=True,
2409
2433
  text=True,
2434
+ encoding="utf-8",
2410
2435
  )
2411
2436
  if missing_jsonl_digest.returncode != 2:
2412
2437
  print("--print-risk-probes-digest accepted missing risk-probes.jsonl", file=sys.stderr)
@@ -2420,13 +2445,14 @@ def run_self_test() -> int:
2420
2445
  "source": {"type": "spec", "spec_path": str(spec_md)},
2421
2446
  "risk_profile": {"risk_probes_enabled": True},
2422
2447
  "risk_probes_digest": risk_digest,
2423
- }))
2448
+ }), encoding="utf-8")
2424
2449
  missing_required_probe = subprocess.run(
2425
2450
  [sys.executable, script_path, "--include-risk-probes"],
2426
2451
  cwd=work,
2427
2452
  env=env,
2428
2453
  capture_output=True,
2429
2454
  text=True,
2455
+ encoding="utf-8",
2430
2456
  )
2431
2457
  if missing_required_probe.returncode == 0:
2432
2458
  print("--include-risk-probes accepted missing required risk-probes.jsonl", file=sys.stderr)
@@ -2439,13 +2465,14 @@ def run_self_test() -> int:
2439
2465
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2440
2466
  "source": {"type": "spec", "spec_path": str(spec_md)},
2441
2467
  "risk_profile": {"risk_probes_enabled": False},
2442
- }))
2468
+ }), encoding="utf-8")
2443
2469
  missing_optional_probe = subprocess.run(
2444
2470
  [sys.executable, script_path, "--include-risk-probes"],
2445
2471
  cwd=work,
2446
2472
  env=env,
2447
2473
  capture_output=True,
2448
2474
  text=True,
2475
+ encoding="utf-8",
2449
2476
  )
2450
2477
  if missing_optional_probe.returncode != 0:
2451
2478
  print("--include-risk-probes rejected optional missing risk-probes.jsonl", file=sys.stderr)
@@ -2455,13 +2482,14 @@ def run_self_test() -> int:
2455
2482
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2456
2483
  "source": {"type": "spec", "spec_path": str(spec_md)},
2457
2484
  "risk_profile": {"risk_probes_enabled": "true"},
2458
- }))
2485
+ }), encoding="utf-8")
2459
2486
  malformed_risk_probe_state = subprocess.run(
2460
2487
  [sys.executable, script_path, "--include-risk-probes"],
2461
2488
  cwd=work,
2462
2489
  env=env,
2463
2490
  capture_output=True,
2464
2491
  text=True,
2492
+ encoding="utf-8",
2465
2493
  )
2466
2494
  if malformed_risk_probe_state.returncode == 0:
2467
2495
  print("--include-risk-probes accepted non-boolean risk_probes_enabled", file=sys.stderr)
@@ -2474,13 +2502,14 @@ def run_self_test() -> int:
2474
2502
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2475
2503
  "source": {"type": "spec", "spec_path": str(spec_md)},
2476
2504
  "risk_profile": "enabled",
2477
- }))
2505
+ }), encoding="utf-8")
2478
2506
  malformed_risk_profile = subprocess.run(
2479
2507
  [sys.executable, script_path, "--include-risk-probes"],
2480
2508
  cwd=work,
2481
2509
  env=env,
2482
2510
  capture_output=True,
2483
2511
  text=True,
2512
+ encoding="utf-8",
2484
2513
  )
2485
2514
  if malformed_risk_profile.returncode == 0:
2486
2515
  print("--include-risk-probes accepted non-object risk_profile", file=sys.stderr)
@@ -2492,7 +2521,7 @@ def run_self_test() -> int:
2492
2521
 
2493
2522
  (devlyn / "pipeline.state.json").write_text(json.dumps({
2494
2523
  "source": {"type": "spec", "spec_path": str(spec_md)}
2495
- }))
2524
+ }), encoding="utf-8")
2496
2525
  (devlyn / "risk-probes.jsonl").write_text(json.dumps({
2497
2526
  "id": "P1",
2498
2527
  "derived_from": "probe must pass visible marker.",
@@ -2508,7 +2537,7 @@ def run_self_test() -> int:
2508
2537
  "asserts_no_unexpected_output_keys",
2509
2538
  ],
2510
2539
  },
2511
- }) + "\n")
2540
+ }) + "\n", encoding="utf-8")
2512
2541
 
2513
2542
  good_complexity = work / "good-complexity.md"
2514
2543
  good_complexity.write_text(
@@ -2520,6 +2549,7 @@ def run_self_test() -> int:
2520
2549
  cwd=work,
2521
2550
  capture_output=True,
2522
2551
  text=True,
2552
+ encoding="utf-8",
2523
2553
  )
2524
2554
  if good_complexity_check.returncode != 0:
2525
2555
  print(good_complexity_check.stderr, file=sys.stderr)
@@ -2535,6 +2565,7 @@ def run_self_test() -> int:
2535
2565
  cwd=work,
2536
2566
  capture_output=True,
2537
2567
  text=True,
2568
+ encoding="utf-8",
2538
2569
  )
2539
2570
  if bad_complexity_check.returncode == 0:
2540
2571
  print("unsupported spec complexity was accepted", file=sys.stderr)
@@ -2556,6 +2587,7 @@ def run_self_test() -> int:
2556
2587
  cwd=work,
2557
2588
  capture_output=True,
2558
2589
  text=True,
2590
+ encoding="utf-8",
2559
2591
  )
2560
2592
  if weak_solo_check.returncode == 0:
2561
2593
  print("weak solo-headroom hypothesis was accepted by --check", file=sys.stderr)
@@ -2576,6 +2608,7 @@ def run_self_test() -> int:
2576
2608
  cwd=work,
2577
2609
  capture_output=True,
2578
2610
  text=True,
2611
+ encoding="utf-8",
2579
2612
  )
2580
2613
  if weak_descriptive_check.returncode == 0:
2581
2614
  print("descriptive backtick solo-headroom hypothesis was accepted by --check", file=sys.stderr)
@@ -2592,6 +2625,7 @@ def run_self_test() -> int:
2592
2625
  cwd=work,
2593
2626
  capture_output=True,
2594
2627
  text=True,
2628
+ encoding="utf-8",
2595
2629
  )
2596
2630
  if strong_solo_check.returncode != 0:
2597
2631
  print("actionable solo-headroom hypothesis was rejected by --check", file=sys.stderr)
@@ -2610,6 +2644,7 @@ def run_self_test() -> int:
2610
2644
  cwd=work,
2611
2645
  capture_output=True,
2612
2646
  text=True,
2647
+ encoding="utf-8",
2613
2648
  )
2614
2649
  if docs_style_solo_check.returncode != 0:
2615
2650
  print("docs-style solo-headroom hypothesis was rejected by --check", file=sys.stderr)
@@ -2627,6 +2662,7 @@ def run_self_test() -> int:
2627
2662
  cwd=work,
2628
2663
  capture_output=True,
2629
2664
  text=True,
2665
+ encoding="utf-8",
2630
2666
  )
2631
2667
  if weak_solo_ceiling_check.returncode == 0:
2632
2668
  print("weak solo ceiling avoidance was accepted by --check", file=sys.stderr)
@@ -2648,6 +2684,7 @@ def run_self_test() -> int:
2648
2684
  cwd=work,
2649
2685
  capture_output=True,
2650
2686
  text=True,
2687
+ encoding="utf-8",
2651
2688
  )
2652
2689
  if strong_solo_ceiling_check.returncode != 0:
2653
2690
  print("actionable solo ceiling avoidance was rejected by --check", file=sys.stderr)
@@ -2669,6 +2706,7 @@ def run_self_test() -> int:
2669
2706
  cwd=work,
2670
2707
  capture_output=True,
2671
2708
  text=True,
2709
+ encoding="utf-8",
2672
2710
  )
2673
2711
  if inline_mismatched_check.returncode == 0:
2674
2712
  print("mismatched inline solo-headroom command was accepted by --check", file=sys.stderr)
@@ -2693,6 +2731,7 @@ def run_self_test() -> int:
2693
2731
  cwd=work,
2694
2732
  capture_output=True,
2695
2733
  text=True,
2734
+ encoding="utf-8",
2696
2735
  )
2697
2736
  if inline_matched_check.returncode != 0:
2698
2737
  print("matched inline solo-headroom command was rejected by --check", file=sys.stderr)
@@ -2704,13 +2743,14 @@ def run_self_test() -> int:
2704
2743
  "derived_from": "probe must pass visible marker.",
2705
2744
  "cmd": "node $BENCH_FIXTURE_DIR/verifiers/hidden.js",
2706
2745
  "exit_code": 0,
2707
- }) + "\n")
2746
+ }) + "\n", encoding="utf-8")
2708
2747
  bad = subprocess.run(
2709
2748
  [sys.executable, script_path, "--validate-risk-probes"],
2710
2749
  cwd=work,
2711
2750
  env=env,
2712
2751
  capture_output=True,
2713
2752
  text=True,
2753
+ encoding="utf-8",
2714
2754
  )
2715
2755
  if bad.returncode == 0:
2716
2756
  print("hidden verifier path was accepted", file=sys.stderr)
@@ -2725,13 +2765,14 @@ def run_self_test() -> int:
2725
2765
  "cmd": "python3 .devlyn/probes/Pscript.py",
2726
2766
  "stdout_contains": ["script-ok"],
2727
2767
  }
2728
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(script_probe_payload) + "\n")
2768
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(script_probe_payload) + "\n", encoding="utf-8")
2729
2769
  good_script_probe = subprocess.run(
2730
2770
  [sys.executable, script_path, "--validate-risk-probes"],
2731
2771
  cwd=work,
2732
2772
  env=env,
2733
2773
  capture_output=True,
2734
2774
  text=True,
2775
+ encoding="utf-8",
2735
2776
  )
2736
2777
  if good_script_probe.returncode != 0:
2737
2778
  print("risk probe script file was rejected", file=sys.stderr)
@@ -2743,13 +2784,14 @@ def run_self_test() -> int:
2743
2784
  "id": "Pmixed",
2744
2785
  "cmd": "python3 .devlyn/probes/Pscript.py && python3 ./.devlyn/probes/P1.py",
2745
2786
  }
2746
- (devlyn / "risk-probes.jsonl").write_text(json.dumps(mixed_script_payload) + "\n")
2787
+ (devlyn / "risk-probes.jsonl").write_text(json.dumps(mixed_script_payload) + "\n", encoding="utf-8")
2747
2788
  mixed_script_probe = subprocess.run(
2748
2789
  [sys.executable, script_path, "--validate-risk-probes"],
2749
2790
  cwd=work,
2750
2791
  env=env,
2751
2792
  capture_output=True,
2752
2793
  text=True,
2794
+ encoding="utf-8",
2753
2795
  )
2754
2796
  if mixed_script_probe.returncode != 0:
2755
2797
  print("canonical and ./-alias probe script references were rejected", file=sys.stderr)
@@ -2780,13 +2822,14 @@ def run_self_test() -> int:
2780
2822
  "derived_from": "probe must pass visible marker.",
2781
2823
  "cmd": "python3 ./.devlyn/probes/missing.py",
2782
2824
  "exit_code": 0,
2783
- }) + "\n")
2825
+ }) + "\n", encoding="utf-8")
2784
2826
  missing_script_probe = subprocess.run(
2785
2827
  [sys.executable, script_path, "--validate-risk-probes"],
2786
2828
  cwd=work,
2787
2829
  env=env,
2788
2830
  capture_output=True,
2789
2831
  text=True,
2832
+ encoding="utf-8",
2790
2833
  )
2791
2834
  if missing_script_probe.returncode == 0:
2792
2835
  print("risk probe missing script file was accepted", file=sys.stderr)
@@ -2801,6 +2844,7 @@ def run_self_test() -> int:
2801
2844
  env=env,
2802
2845
  capture_output=True,
2803
2846
  text=True,
2847
+ encoding="utf-8",
2804
2848
  )
2805
2849
  if missing_script_digest.returncode != 2:
2806
2850
  print("--print-risk-probes-digest accepted a missing referenced script", file=sys.stderr)
@@ -2822,13 +2866,14 @@ def run_self_test() -> int:
2822
2866
  "derived_from": "probe must pass visible marker.",
2823
2867
  "cmd": f"python3 {bad_form}",
2824
2868
  "exit_code": 0,
2825
- }) + "\n")
2869
+ }) + "\n", encoding="utf-8")
2826
2870
  bad_ref_probe = subprocess.run(
2827
2871
  [sys.executable, script_path, "--validate-risk-probes"],
2828
2872
  cwd=work,
2829
2873
  env=env,
2830
2874
  capture_output=True,
2831
2875
  text=True,
2876
+ encoding="utf-8",
2832
2877
  )
2833
2878
  bad_ref_digest = subprocess.run(
2834
2879
  [sys.executable, script_path, "--print-risk-probes-digest"],
@@ -2836,6 +2881,7 @@ def run_self_test() -> int:
2836
2881
  env=env,
2837
2882
  capture_output=True,
2838
2883
  text=True,
2884
+ encoding="utf-8",
2839
2885
  )
2840
2886
  unrecognized = unrecognized_risk_probe_reference(f"python3 {bad_form}")
2841
2887
  expected_error = f"risk-probes[0].cmd has {unrecognized}"
@@ -2856,13 +2902,14 @@ def run_self_test() -> int:
2856
2902
  "derived_from": "probe must pass visible marker.",
2857
2903
  "cmd": "python3 .devlyn/probes/Phidden.py",
2858
2904
  "exit_code": 0,
2859
- }) + "\n")
2905
+ }) + "\n", encoding="utf-8")
2860
2906
  hidden_script_probe = subprocess.run(
2861
2907
  [sys.executable, script_path, "--validate-risk-probes"],
2862
2908
  cwd=work,
2863
2909
  env=env,
2864
2910
  capture_output=True,
2865
2911
  text=True,
2912
+ encoding="utf-8",
2866
2913
  )
2867
2914
  if hidden_script_probe.returncode == 0:
2868
2915
  print("risk probe script containing a hidden fixture path was accepted", file=sys.stderr)
@@ -2878,13 +2925,14 @@ def run_self_test() -> int:
2878
2925
  "derived_from": "probe must pass visible marker.",
2879
2926
  "cmd": "python3 .devlyn/probes/Pexternal.py",
2880
2927
  "exit_code": 0,
2881
- }) + "\n")
2928
+ }) + "\n", encoding="utf-8")
2882
2929
  external_script_probe = subprocess.run(
2883
2930
  [sys.executable, script_path, "--validate-risk-probes"],
2884
2931
  cwd=work,
2885
2932
  env=env,
2886
2933
  capture_output=True,
2887
2934
  text=True,
2935
+ encoding="utf-8",
2888
2936
  )
2889
2937
  if external_script_probe.returncode == 0:
2890
2938
  print("risk probe script containing an external URL was accepted", file=sys.stderr)
@@ -2894,13 +2942,14 @@ def run_self_test() -> int:
2894
2942
  print(external_script_probe.stderr, file=sys.stderr)
2895
2943
  return 1
2896
2944
 
2897
- (devlyn / "risk-probes.jsonl").write_text('{"id":NaN}\n')
2945
+ (devlyn / "risk-probes.jsonl").write_text('{"id":NaN}\n', encoding="utf-8")
2898
2946
  bad_probe_nan = subprocess.run(
2899
2947
  [sys.executable, script_path, "--validate-risk-probes"],
2900
2948
  cwd=work,
2901
2949
  env=env,
2902
2950
  capture_output=True,
2903
2951
  text=True,
2952
+ encoding="utf-8",
2904
2953
  )
2905
2954
  if bad_probe_nan.returncode == 0:
2906
2955
  print("NaN risk-probes JSONL was accepted", file=sys.stderr)
@@ -2917,13 +2966,14 @@ def run_self_test() -> int:
2917
2966
  "exit_code": 0,
2918
2967
  "tags": ["error_contract"],
2919
2968
  "tag_evidence": {"error_contract": []},
2920
- }) + "\n")
2969
+ }) + "\n", encoding="utf-8")
2921
2970
  bad_error_ref = subprocess.run(
2922
2971
  [sys.executable, script_path, "--validate-risk-probes"],
2923
2972
  cwd=work,
2924
2973
  env=env,
2925
2974
  capture_output=True,
2926
2975
  text=True,
2976
+ encoding="utf-8",
2927
2977
  )
2928
2978
  if bad_error_ref.returncode == 0:
2929
2979
  print("error_contract with unrelated derived_from was accepted", file=sys.stderr)
@@ -2951,13 +3001,14 @@ def run_self_test() -> int:
2951
3001
  "asserts_no_unexpected_output_keys",
2952
3002
  ],
2953
3003
  },
2954
- }) + "\n")
3004
+ }) + "\n", encoding="utf-8")
2955
3005
  bad_solo_headroom_probe = subprocess.run(
2956
3006
  [sys.executable, script_path, "--validate-risk-probes"],
2957
3007
  cwd=work,
2958
3008
  env=env,
2959
3009
  capture_output=True,
2960
3010
  text=True,
3011
+ encoding="utf-8",
2961
3012
  )
2962
3013
  if bad_solo_headroom_probe.returncode == 0:
2963
3014
  print("risk probe missing solo-headroom command coverage was accepted", file=sys.stderr)
@@ -2982,13 +3033,14 @@ def run_self_test() -> int:
2982
3033
  "asserts_no_unexpected_output_keys",
2983
3034
  ],
2984
3035
  },
2985
- }) + "\n")
3036
+ }) + "\n", encoding="utf-8")
2986
3037
  bad_solo_headroom_derived_from = subprocess.run(
2987
3038
  [sys.executable, script_path, "--validate-risk-probes"],
2988
3039
  cwd=work,
2989
3040
  env=env,
2990
3041
  capture_output=True,
2991
3042
  text=True,
3043
+ encoding="utf-8",
2992
3044
  )
2993
3045
  if bad_solo_headroom_derived_from.returncode == 0:
2994
3046
  print("risk probe with unrelated solo-headroom derived_from was accepted", file=sys.stderr)
@@ -3029,7 +3081,8 @@ def run_self_test() -> int:
3029
3081
  "asserts_no_unexpected_output_keys",
3030
3082
  ],
3031
3083
  },
3032
- }) + "\n"
3084
+ }) + "\n",
3085
+ encoding="utf-8",
3033
3086
  )
3034
3087
  late_solo_headroom_probe = subprocess.run(
3035
3088
  [sys.executable, script_path, "--validate-risk-probes"],
@@ -3037,6 +3090,7 @@ def run_self_test() -> int:
3037
3090
  env=env,
3038
3091
  capture_output=True,
3039
3092
  text=True,
3093
+ encoding="utf-8",
3040
3094
  )
3041
3095
  if late_solo_headroom_probe.returncode == 0:
3042
3096
  print("solo-headroom command in a later risk probe was accepted", file=sys.stderr)
@@ -3057,13 +3111,14 @@ def run_self_test() -> int:
3057
3111
  "asserts_no_unexpected_output_keys",
3058
3112
  ],
3059
3113
  },
3060
- }) + "\n")
3114
+ }) + "\n", encoding="utf-8")
3061
3115
  prefix_solo_headroom_probe = subprocess.run(
3062
3116
  [sys.executable, script_path, "--validate-risk-probes"],
3063
3117
  cwd=work,
3064
3118
  env=env,
3065
3119
  capture_output=True,
3066
3120
  text=True,
3121
+ encoding="utf-8",
3067
3122
  )
3068
3123
  if prefix_solo_headroom_probe.returncode == 0:
3069
3124
  print("solo-headroom command prefix match was accepted", file=sys.stderr)
@@ -3084,13 +3139,14 @@ def run_self_test() -> int:
3084
3139
  "asserts_no_unexpected_output_keys",
3085
3140
  ],
3086
3141
  },
3087
- }) + "\n")
3142
+ }) + "\n", encoding="utf-8")
3088
3143
  good_solo_headroom_probe = subprocess.run(
3089
3144
  [sys.executable, script_path, "--validate-risk-probes"],
3090
3145
  cwd=work,
3091
3146
  env=env,
3092
3147
  capture_output=True,
3093
3148
  text=True,
3149
+ encoding="utf-8",
3094
3150
  )
3095
3151
  if good_solo_headroom_probe.returncode != 0:
3096
3152
  print("risk probe covering solo-headroom command was rejected", file=sys.stderr)
@@ -3112,17 +3168,19 @@ def run_self_test() -> int:
3112
3168
  "required_files": ["bin/cli.js"],
3113
3169
  "forbidden_files": [],
3114
3170
  "max_deps_added": 0,
3115
- }) + "\n")
3171
+ }) + "\n", encoding="utf-8")
3116
3172
  spec_md.write_text(
3117
3173
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
3118
3174
  "- solo-headroom hypothesis: solo_claude should miss duplicate handling.\n"
3119
- "- Observable command: `node check.js` exposes behavior.\n"
3175
+ "- Observable command: `node check.js` exposes behavior.\n",
3176
+ encoding="utf-8",
3120
3177
  )
3121
3178
  weak_sibling_solo = subprocess.run(
3122
3179
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3123
3180
  cwd=work,
3124
3181
  capture_output=True,
3125
3182
  text=True,
3183
+ encoding="utf-8",
3126
3184
  )
3127
3185
  if weak_sibling_solo.returncode == 0:
3128
3186
  print("weak sibling solo-headroom hypothesis was accepted by --check-expected", file=sys.stderr)
@@ -3143,6 +3201,7 @@ def run_self_test() -> int:
3143
3201
  cwd=work,
3144
3202
  capture_output=True,
3145
3203
  text=True,
3204
+ encoding="utf-8",
3146
3205
  )
3147
3206
  if mismatched_sibling_solo.returncode == 0:
3148
3207
  print("mismatched sibling solo-headroom command was accepted by --check-expected", file=sys.stderr)
@@ -3163,6 +3222,7 @@ def run_self_test() -> int:
3163
3222
  cwd=work,
3164
3223
  capture_output=True,
3165
3224
  text=True,
3225
+ encoding="utf-8",
3166
3226
  )
3167
3227
  if matched_sibling_solo.returncode != 0:
3168
3228
  print("matched sibling solo-headroom command was rejected by --check-expected", file=sys.stderr)
@@ -3180,6 +3240,7 @@ def run_self_test() -> int:
3180
3240
  cwd=work,
3181
3241
  capture_output=True,
3182
3242
  text=True,
3243
+ encoding="utf-8",
3183
3244
  )
3184
3245
  if docs_style_sibling_solo.returncode != 0:
3185
3246
  print("docs-style sibling solo-headroom command was rejected by --check-expected", file=sys.stderr)
@@ -3196,6 +3257,7 @@ def run_self_test() -> int:
3196
3257
  cwd=work,
3197
3258
  capture_output=True,
3198
3259
  text=True,
3260
+ encoding="utf-8",
3199
3261
  )
3200
3262
  if weak_sibling_solo_ceiling.returncode == 0:
3201
3263
  print("weak sibling solo ceiling avoidance was accepted by --check-expected", file=sys.stderr)
@@ -3216,18 +3278,20 @@ def run_self_test() -> int:
3216
3278
  cwd=work,
3217
3279
  capture_output=True,
3218
3280
  text=True,
3281
+ encoding="utf-8",
3219
3282
  )
3220
3283
  if strong_sibling_solo_ceiling.returncode != 0:
3221
3284
  print("actionable sibling solo ceiling avoidance was rejected by --check-expected", file=sys.stderr)
3222
3285
  print(strong_sibling_solo_ceiling.stderr, file=sys.stderr)
3223
3286
  return 1
3224
3287
 
3225
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
3288
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
3226
3289
  expected_good = subprocess.run(
3227
3290
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3228
3291
  cwd=work,
3229
3292
  capture_output=True,
3230
3293
  text=True,
3294
+ encoding="utf-8",
3231
3295
  )
3232
3296
  if expected_good.returncode != 0:
3233
3297
  print(expected_good.stderr, file=sys.stderr)
@@ -3242,6 +3306,7 @@ def run_self_test() -> int:
3242
3306
  cwd=work,
3243
3307
  capture_output=True,
3244
3308
  text=True,
3309
+ encoding="utf-8",
3245
3310
  )
3246
3311
  if expected_bad_sibling_complexity.returncode == 0:
3247
3312
  print("unsupported sibling spec complexity was accepted by --check-expected", file=sys.stderr)
@@ -3250,14 +3315,15 @@ def run_self_test() -> int:
3250
3315
  print("--check-expected did not report unsupported sibling spec complexity", file=sys.stderr)
3251
3316
  print(expected_bad_sibling_complexity.stderr, file=sys.stderr)
3252
3317
  return 1
3253
- spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n")
3318
+ spec_md.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- probe must pass visible marker.\n", encoding="utf-8")
3254
3319
 
3255
- expected_json.write_text(json.dumps({"verification_commands": []}) + "\n")
3320
+ expected_json.write_text(json.dumps({"verification_commands": []}) + "\n", encoding="utf-8")
3256
3321
  expected_empty_runtime = subprocess.run(
3257
3322
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3258
3323
  cwd=work,
3259
3324
  capture_output=True,
3260
3325
  text=True,
3326
+ encoding="utf-8",
3261
3327
  )
3262
3328
  if expected_empty_runtime.returncode == 0:
3263
3329
  print("empty verification_commands should fail for runtime specs", file=sys.stderr)
@@ -3280,6 +3346,7 @@ def run_self_test() -> int:
3280
3346
  cwd=work,
3281
3347
  capture_output=True,
3282
3348
  text=True,
3349
+ encoding="utf-8",
3283
3350
  )
3284
3351
  if expected_empty_design.returncode != 0:
3285
3352
  print("empty verification_commands should be valid for pure-design specs", file=sys.stderr)
@@ -3298,6 +3365,7 @@ def run_self_test() -> int:
3298
3365
  cwd=work,
3299
3366
  capture_output=True,
3300
3367
  text=True,
3368
+ encoding="utf-8",
3301
3369
  )
3302
3370
  if pure_design_contradiction.returncode == 0:
3303
3371
  print("pure_design: true with non-empty verification_commands was accepted", file=sys.stderr)
@@ -3316,6 +3384,7 @@ def run_self_test() -> int:
3316
3384
  cwd=work,
3317
3385
  capture_output=True,
3318
3386
  text=True,
3387
+ encoding="utf-8",
3319
3388
  )
3320
3389
  if pure_design_not_boolean.returncode == 0:
3321
3390
  print("non-boolean pure_design was accepted", file=sys.stderr)
@@ -3325,12 +3394,13 @@ def run_self_test() -> int:
3325
3394
  print(pure_design_not_boolean.stderr, file=sys.stderr)
3326
3395
  return 1
3327
3396
 
3328
- expected_json.write_text(json.dumps({"unknown": True}) + "\n")
3397
+ expected_json.write_text(json.dumps({"unknown": True}) + "\n", encoding="utf-8")
3329
3398
  expected_bad = subprocess.run(
3330
3399
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3331
3400
  cwd=work,
3332
3401
  capture_output=True,
3333
3402
  text=True,
3403
+ encoding="utf-8",
3334
3404
  )
3335
3405
  if expected_bad.returncode == 0:
3336
3406
  print("spec.expected.json with unknown key was accepted", file=sys.stderr)
@@ -3338,23 +3408,25 @@ def run_self_test() -> int:
3338
3408
 
3339
3409
  expected_json.write_text(json.dumps({
3340
3410
  "verification_commands": [{"cmd": "printf ok", "stdout_contians": ["ok"]}]
3341
- }) + "\n")
3411
+ }) + "\n", encoding="utf-8")
3342
3412
  expected_bad_command = subprocess.run(
3343
3413
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3344
3414
  cwd=work,
3345
3415
  capture_output=True,
3346
3416
  text=True,
3417
+ encoding="utf-8",
3347
3418
  )
3348
3419
  if expected_bad_command.returncode == 0:
3349
3420
  print("spec.expected.json command with unknown key was accepted", file=sys.stderr)
3350
3421
  return 1
3351
3422
 
3352
- expected_json.write_text("[1]\n")
3423
+ expected_json.write_text("[1]\n", encoding="utf-8")
3353
3424
  expected_non_object = subprocess.run(
3354
3425
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3355
3426
  cwd=work,
3356
3427
  capture_output=True,
3357
3428
  text=True,
3429
+ encoding="utf-8",
3358
3430
  )
3359
3431
  if expected_non_object.returncode == 0:
3360
3432
  print("spec.expected.json top-level array was accepted", file=sys.stderr)
@@ -3367,12 +3439,13 @@ def run_self_test() -> int:
3367
3439
  print("spec.expected.json top-level array produced a traceback", file=sys.stderr)
3368
3440
  return 1
3369
3441
 
3370
- expected_json.write_text("{broken\n")
3442
+ expected_json.write_text("{broken\n", encoding="utf-8")
3371
3443
  expected_invalid_json = subprocess.run(
3372
3444
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3373
3445
  cwd=work,
3374
3446
  capture_output=True,
3375
3447
  text=True,
3448
+ encoding="utf-8",
3376
3449
  )
3377
3450
  if expected_invalid_json.returncode == 0:
3378
3451
  print("invalid spec.expected.json was accepted", file=sys.stderr)
@@ -3385,12 +3458,13 @@ def run_self_test() -> int:
3385
3458
  print("invalid spec.expected.json produced a traceback", file=sys.stderr)
3386
3459
  return 1
3387
3460
 
3388
- expected_json.write_text('{"verification_commands": NaN}\n')
3461
+ expected_json.write_text('{"verification_commands": NaN}\n', encoding="utf-8")
3389
3462
  expected_nan_json = subprocess.run(
3390
3463
  [sys.executable, script_path, "--check-expected", str(expected_json)],
3391
3464
  cwd=work,
3392
3465
  capture_output=True,
3393
3466
  text=True,
3467
+ encoding="utf-8",
3394
3468
  )
3395
3469
  if expected_nan_json.returncode == 0:
3396
3470
  print("NaN spec.expected.json was accepted", file=sys.stderr)
@@ -3426,6 +3500,7 @@ def run_self_test() -> int:
3426
3500
  )
3427
3501
  external_diff_base_sha = subprocess.check_output(
3428
3502
  ["git", "rev-parse", "HEAD"], cwd=external_diff_root, text=True,
3503
+ encoding="utf-8",
3429
3504
  ).strip()
3430
3505
  (external_diff_root / "outside.txt").write_text("worktree-only\n", encoding="utf-8")
3431
3506
  (external_diff_devlyn / "external-diff.patch").write_text(
@@ -3449,26 +3524,28 @@ def run_self_test() -> int:
3449
3524
  "base_ref": {"sha": external_diff_base_sha},
3450
3525
  }
3451
3526
  external_diff_state_path = external_diff_devlyn / "pipeline.state.json"
3452
- external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n")
3527
+ external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n", encoding="utf-8")
3453
3528
  external_diff_free_form = subprocess.run(
3454
3529
  [sys.executable, script_path],
3455
3530
  cwd=external_diff_root,
3456
3531
  capture_output=True,
3457
3532
  text=True,
3533
+ encoding="utf-8",
3458
3534
  )
3459
3535
  external_diff_findings_path = external_diff_devlyn / output_findings_name()
3460
3536
  external_diff_free_form_findings = (
3461
- external_diff_findings_path.read_text()
3537
+ external_diff_findings_path.read_text(encoding="utf-8")
3462
3538
  if external_diff_findings_path.is_file()
3463
3539
  else ""
3464
3540
  )
3465
3541
  external_diff_state["mode"] = "verify-only"
3466
- external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n")
3542
+ external_diff_state_path.write_text(json.dumps(external_diff_state) + "\n", encoding="utf-8")
3467
3543
  external_diff_verify_only = subprocess.run(
3468
3544
  [sys.executable, script_path],
3469
3545
  cwd=external_diff_root,
3470
3546
  capture_output=True,
3471
3547
  text=True,
3548
+ encoding="utf-8",
3472
3549
  )
3473
3550
  if external_diff_free_form.returncode != 1:
3474
3551
  print("non-verify-only mode accepted .devlyn/external-diff.patch", file=sys.stderr)
@@ -3514,12 +3591,13 @@ def run_self_test() -> int:
3514
3591
  "spec_path": str(integrity_spec),
3515
3592
  "spec_sha256": "0" * 64,
3516
3593
  }
3517
- }))
3594
+ }), encoding="utf-8")
3518
3595
  spec_bad_hash_run = subprocess.run(
3519
3596
  [sys.executable, script_path],
3520
3597
  cwd=spec_integrity,
3521
3598
  capture_output=True,
3522
3599
  text=True,
3600
+ encoding="utf-8",
3523
3601
  )
3524
3602
  if spec_bad_hash_run.returncode == 0:
3525
3603
  print("spec source with mismatched source.spec_sha256 was accepted", file=sys.stderr)
@@ -3536,17 +3614,18 @@ def run_self_test() -> int:
3536
3614
  "spec_path": str(integrity_spec),
3537
3615
  "spec_sha256": spec_hash,
3538
3616
  }
3539
- }))
3617
+ }), encoding="utf-8")
3540
3618
  spec_hash_run = subprocess.run(
3541
3619
  [sys.executable, script_path],
3542
3620
  cwd=spec_integrity,
3543
3621
  capture_output=True,
3544
3622
  text=True,
3623
+ encoding="utf-8",
3545
3624
  )
3546
3625
  if spec_hash_run.returncode != 0:
3547
3626
  print(spec_hash_run.stderr, file=sys.stderr)
3548
3627
  return 1
3549
- staged_spec_hash = loads_strict_json((spec_integrity_devlyn / "spec-verify.json").read_text())
3628
+ staged_spec_hash = loads_strict_json((spec_integrity_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3550
3629
  if staged_spec_hash.get("verification_commands", [{}])[0].get("cmd") != "printf spec-hash-ok":
3551
3630
  print("spec source with matching source.spec_sha256 was not staged", file=sys.stderr)
3552
3631
  return 1
@@ -3564,12 +3643,13 @@ def run_self_test() -> int:
3564
3643
  )
3565
3644
  (generated_devlyn / "pipeline.state.json").write_text(json.dumps({
3566
3645
  "source": {"type": "generated", "criteria_path": str(generated_criteria)}
3567
- }))
3646
+ }), encoding="utf-8")
3568
3647
  generated_missing_hash_run = subprocess.run(
3569
3648
  [sys.executable, script_path],
3570
3649
  cwd=generated_user,
3571
3650
  capture_output=True,
3572
3651
  text=True,
3652
+ encoding="utf-8",
3573
3653
  )
3574
3654
  if generated_missing_hash_run.returncode == 0:
3575
3655
  print("generated criteria without source.criteria_sha256 was accepted", file=sys.stderr)
@@ -3585,12 +3665,13 @@ def run_self_test() -> int:
3585
3665
  "criteria_path": str(generated_criteria),
3586
3666
  "criteria_sha256": "0" * 64,
3587
3667
  }
3588
- }))
3668
+ }), encoding="utf-8")
3589
3669
  generated_bad_hash_run = subprocess.run(
3590
3670
  [sys.executable, script_path],
3591
3671
  cwd=generated_user,
3592
3672
  capture_output=True,
3593
3673
  text=True,
3674
+ encoding="utf-8",
3594
3675
  )
3595
3676
  if generated_bad_hash_run.returncode == 0:
3596
3677
  print("generated criteria with mismatched source.criteria_sha256 was accepted", file=sys.stderr)
@@ -3607,21 +3688,70 @@ def run_self_test() -> int:
3607
3688
  "criteria_path": str(generated_criteria),
3608
3689
  "criteria_sha256": generated_hash,
3609
3690
  }
3610
- }))
3691
+ }), encoding="utf-8")
3611
3692
  generated_run = subprocess.run(
3612
3693
  [sys.executable, script_path],
3613
3694
  cwd=generated_user,
3614
3695
  capture_output=True,
3615
3696
  text=True,
3697
+ encoding="utf-8",
3616
3698
  )
3617
3699
  if generated_run.returncode != 0:
3618
3700
  print(generated_run.stderr, file=sys.stderr)
3619
3701
  return 1
3620
- staged_generated = loads_strict_json((generated_devlyn / "spec-verify.json").read_text())
3702
+ staged_generated = loads_strict_json((generated_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3621
3703
  if staged_generated.get("verification_commands", [{}])[0].get("cmd") != "printf generated-ok":
3622
3704
  print("generated criteria carrier was not staged into .devlyn/spec-verify.json", file=sys.stderr)
3623
3705
  return 1
3624
3706
 
3707
+ inline_marker = generated_user / "inline-command-ran"
3708
+ inline_command = "printf bad > inline-command-ran; printf bad"
3709
+ for source_type in ("generated", "spec"):
3710
+ for contract, diagnostic in (
3711
+ ({"verification_commands": [{"cmd": inline_command}],
3712
+ "required_files": ["missing.txt"]}, "unsupported inline key(s): required_files"),
3713
+ ({"verification_commands": [{"cmd": inline_command,
3714
+ "stdout_not_contians": ["bad"]}]}, "unknown key(s): stdout_not_contians"),
3715
+ ({"verification_commands": [{"cmd": inline_command,
3716
+ "stdout_not_contains": ["bad"]}]}, None),
3717
+ ):
3718
+ inline_marker.unlink(missing_ok=True)
3719
+ (generated_devlyn / output_findings_name()).unlink(missing_ok=True)
3720
+ generated_criteria.write_text(
3721
+ "# Inline constraints\n\n<!-- devlyn:verification -->\n## Verification\n\n```json\n"
3722
+ + json.dumps(contract) + "\n```\n", encoding="utf-8",
3723
+ )
3724
+ prefix = "criteria" if source_type == "generated" else "spec"
3725
+ (generated_devlyn / "pipeline.state.json").write_text(json.dumps({
3726
+ "source": {
3727
+ "type": source_type,
3728
+ f"{prefix}_path": str(generated_criteria),
3729
+ f"{prefix}_sha256": hashlib.sha256(generated_criteria.read_bytes()).hexdigest(),
3730
+ }
3731
+ }), encoding="utf-8")
3732
+ # A stale valid carrier must not bypass malformed source validation.
3733
+ (generated_devlyn / "spec-verify.json").write_text(json.dumps({
3734
+ "verification_commands": [{"cmd": inline_command}]
3735
+ }), encoding="utf-8")
3736
+ inline_check = subprocess.run(
3737
+ [sys.executable, script_path, "--check", str(generated_criteria)],
3738
+ cwd=generated_user, capture_output=True, text=True, encoding="utf-8",
3739
+ )
3740
+ inline_run = subprocess.run(
3741
+ [sys.executable, script_path], cwd=generated_user,
3742
+ capture_output=True, text=True, encoding="utf-8",
3743
+ )
3744
+ assert inline_check.returncode == (2 if diagnostic else 0)
3745
+ assert inline_run.returncode == 1
3746
+ if diagnostic:
3747
+ assert diagnostic in inline_check.stderr and diagnostic in inline_run.stderr
3748
+ assert not inline_marker.exists(), "malformed inline constraint executed a command"
3749
+ else:
3750
+ assert inline_marker.exists(), "valid inline output guard was not executed"
3751
+ assert "correctness.spec-literal-mismatch" in (
3752
+ generated_devlyn / output_findings_name()
3753
+ ).read_text(encoding="utf-8")
3754
+
3625
3755
  generated_criteria.write_text(
3626
3756
  "# Criteria\n\n## Verification\n\n- generated criteria omitted its machine-readable carrier.\n",
3627
3757
  encoding="utf-8",
@@ -3633,12 +3763,13 @@ def run_self_test() -> int:
3633
3763
  "criteria_path": str(generated_criteria),
3634
3764
  "criteria_sha256": malformed_generated_hash,
3635
3765
  }
3636
- }))
3766
+ }), encoding="utf-8")
3637
3767
  malformed_generated_run = subprocess.run(
3638
3768
  [sys.executable, script_path],
3639
3769
  cwd=generated_user,
3640
3770
  capture_output=True,
3641
3771
  text=True,
3772
+ encoding="utf-8",
3642
3773
  )
3643
3774
  if malformed_generated_run.returncode == 0:
3644
3775
  print("generated criteria without a JSON carrier was accepted", file=sys.stderr)
@@ -3654,30 +3785,80 @@ def run_self_test() -> int:
3654
3785
  real_devlyn.mkdir()
3655
3786
  real_spec = real_user / "spec.md"
3656
3787
  real_spec.write_text(
3657
- "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- sibling command must print sibling-ok.\n"
3788
+ "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- sibling command must print sibling-ok.\n",
3789
+ encoding="utf-8",
3658
3790
  )
3659
3791
  (real_user / "spec.expected.json").write_text(json.dumps({
3660
3792
  "verification_commands": [
3661
3793
  {"cmd": "printf sibling-ok", "stdout_contains": ["sibling-ok"]}
3662
3794
  ]
3663
- }) + "\n")
3795
+ }) + "\n", encoding="utf-8")
3664
3796
  (real_devlyn / "pipeline.state.json").write_text(json.dumps({
3665
3797
  "source": {"type": "spec", "spec_path": str(real_spec)}
3666
- }))
3798
+ }), encoding="utf-8")
3667
3799
  sibling_run = subprocess.run(
3668
3800
  [sys.executable, script_path],
3669
3801
  cwd=real_user,
3670
3802
  capture_output=True,
3671
3803
  text=True,
3804
+ encoding="utf-8",
3672
3805
  )
3673
3806
  if sibling_run.returncode != 0:
3674
3807
  print(sibling_run.stderr, file=sys.stderr)
3675
3808
  return 1
3676
- staged = loads_strict_json((real_devlyn / "spec-verify.json").read_text())
3809
+ staged = loads_strict_json((real_devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3677
3810
  if staged.get("verification_commands", [{}])[0].get("cmd") != "printf sibling-ok":
3678
3811
  print("sibling spec.expected.json was not staged into .devlyn/spec-verify.json", file=sys.stderr)
3679
3812
  return 1
3680
3813
 
3814
+ pure_prose = "# Design\n\n<!-- devlyn:verification -->\n## Verification\n\n- No runtime verification commands.\n"
3815
+ stale_command = {"verification_commands": [{"cmd": "printf unexpected-command"}]}
3816
+ pure_inline = pure_prose + "\n```json\n" + json.dumps(stale_command) + "\n```\n"
3817
+ for name, filename, source, contract, expected_rule in (
3818
+ ("pure-prose", "spec.md", pure_prose, {"pure_design": True}, None),
3819
+ ("pure-inline", "design-notes.md", pure_inline, {"pure_design": True, "verification_commands": []}, None),
3820
+ ("empty-runtime", "design-notes.md", pure_inline, {"verification_commands": []}, "correctness.spec-verify-malformed"),
3821
+ ("contradictory-pure", "spec.md", pure_inline, {"pure_design": True, **stale_command}, "correctness.spec-verify-malformed"),
3822
+ ("pure-required-file", "spec.md", pure_inline, {"pure_design": True, "required_files": ["missing.md"]}, "correctness.required-file-missing"),
3823
+ ):
3824
+ case_root = work / name
3825
+ case_root.mkdir()
3826
+ case_devlyn = case_root / ".devlyn"
3827
+ case_devlyn.mkdir()
3828
+ case_spec = case_root / filename
3829
+ case_spec.write_text(source, encoding="utf-8")
3830
+ (case_root / "spec.expected.json").write_text(json.dumps(contract), encoding="utf-8")
3831
+ (case_devlyn / "spec-verify.json").write_text(json.dumps(stale_command), encoding="utf-8")
3832
+ (case_devlyn / "pipeline.state.json").write_text(json.dumps({
3833
+ "source": {"type": "spec", "spec_path": str(case_spec)},
3834
+ }), encoding="utf-8")
3835
+ case_run = subprocess.run(
3836
+ [sys.executable, script_path], cwd=case_root,
3837
+ capture_output=True, text=True, encoding="utf-8",
3838
+ )
3839
+ if case_run.returncode != (1 if expected_rule else 0):
3840
+ print(f"{name}: sibling execution returned {case_run.returncode}: {case_run.stderr}", file=sys.stderr)
3841
+ return 1
3842
+ case_findings = [
3843
+ loads_strict_json(line) for line in
3844
+ (case_devlyn / output_findings_name()).read_text(encoding="utf-8").splitlines()
3845
+ ]
3846
+ if [finding["rule_id"] for finding in case_findings] != ([expected_rule] if expected_rule else []):
3847
+ print(f"{name}: unexpected sibling findings: {case_findings}", file=sys.stderr)
3848
+ return 1
3849
+ case_results = case_devlyn / "spec-verify.results.json"
3850
+ if expected_rule == "correctness.spec-verify-malformed":
3851
+ if case_results.exists():
3852
+ print(f"{name}: malformed sibling reached command execution", file=sys.stderr)
3853
+ return 1
3854
+ elif (
3855
+ loads_strict_json(case_results.read_text(encoding="utf-8"))
3856
+ != {"commands": [], "process_evidence": None}
3857
+ or (case_devlyn / "spec-verify.json").exists()
3858
+ ):
3859
+ print(f"{name}: pure-design sibling ran or retained a stale command", file=sys.stderr)
3860
+ return 1
3861
+
3681
3862
  malformed = work / "malformed-sibling"
3682
3863
  malformed.mkdir()
3683
3864
  malformed_devlyn = malformed / ".devlyn"
@@ -3686,48 +3867,51 @@ def run_self_test() -> int:
3686
3867
  malformed_spec.write_text(
3687
3868
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n```json\n"
3688
3869
  "{\"verification_commands\":[{\"cmd\":\"printf inline-ok\"}]}\n"
3689
- "```\n"
3870
+ "```\n",
3871
+ encoding="utf-8",
3690
3872
  )
3691
- (malformed / "spec.expected.json").write_text(json.dumps({"unknown": True}) + "\n")
3873
+ (malformed / "spec.expected.json").write_text(json.dumps({"unknown": True}) + "\n", encoding="utf-8")
3692
3874
  (malformed_devlyn / "pipeline.state.json").write_text(json.dumps({
3693
3875
  "source": {"type": "spec", "spec_path": str(malformed_spec)}
3694
- }))
3876
+ }), encoding="utf-8")
3695
3877
  malformed_run = subprocess.run(
3696
3878
  [sys.executable, script_path],
3697
3879
  cwd=malformed,
3698
3880
  capture_output=True,
3699
3881
  text=True,
3882
+ encoding="utf-8",
3700
3883
  )
3701
3884
  if malformed_run.returncode == 0:
3702
3885
  print("malformed sibling spec.expected.json fell back to inline carrier", file=sys.stderr)
3703
3886
  return 1
3704
3887
 
3705
3888
  bench_spec = work / "bench-spec.md"
3706
- bench_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- benchmark pre-staged wins.\n")
3889
+ bench_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- benchmark pre-staged wins.\n", encoding="utf-8")
3707
3890
  (work / "spec.expected.json").write_text(json.dumps({
3708
3891
  "verification_commands": [
3709
3892
  {"cmd": "printf wrong", "stdout_contains": ["wrong"]}
3710
3893
  ]
3711
- }) + "\n")
3894
+ }) + "\n", encoding="utf-8")
3712
3895
  (devlyn / "pipeline.state.json").write_text(json.dumps({
3713
3896
  "source": {"type": "spec", "spec_path": str(bench_spec)}
3714
- }))
3897
+ }), encoding="utf-8")
3715
3898
  (devlyn / "spec-verify.json").write_text(json.dumps({
3716
3899
  "verification_commands": [
3717
3900
  {"cmd": "printf bench-staged", "stdout_contains": ["bench-staged"]}
3718
3901
  ]
3719
- }) + "\n")
3902
+ }) + "\n", encoding="utf-8")
3720
3903
  bench_pre_staged = subprocess.run(
3721
3904
  [sys.executable, script_path],
3722
3905
  cwd=work,
3723
3906
  env=env,
3724
3907
  capture_output=True,
3725
3908
  text=True,
3909
+ encoding="utf-8",
3726
3910
  )
3727
3911
  if bench_pre_staged.returncode != 0:
3728
3912
  print(bench_pre_staged.stderr, file=sys.stderr)
3729
3913
  return 1
3730
- staged_bench = loads_strict_json((devlyn / "spec-verify.json").read_text())
3914
+ staged_bench = loads_strict_json((devlyn / "spec-verify.json").read_text(encoding="utf-8"))
3731
3915
  if staged_bench.get("verification_commands", [{}])[0].get("cmd") != "printf bench-staged":
3732
3916
  print("benchmark pre-staged contract was overwritten", file=sys.stderr)
3733
3917
  return 1
@@ -3737,15 +3921,15 @@ def run_self_test() -> int:
3737
3921
  verify_devlyn = verify_output / ".devlyn"
3738
3922
  verify_devlyn.mkdir()
3739
3923
  verify_spec = verify_output / "spec.md"
3740
- verify_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- verify mechanical output.\n")
3924
+ verify_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- verify mechanical output.\n", encoding="utf-8")
3741
3925
  (verify_output / "spec.expected.json").write_text(json.dumps({
3742
3926
  "verification_commands": [
3743
3927
  {"cmd": "printf wrong", "stdout_contains": ["expected"]}
3744
3928
  ]
3745
- }) + "\n")
3929
+ }) + "\n", encoding="utf-8")
3746
3930
  (verify_devlyn / "pipeline.state.json").write_text(json.dumps({
3747
3931
  "source": {"type": "spec", "spec_path": str(verify_spec)}
3748
- }))
3932
+ }), encoding="utf-8")
3749
3933
  verify_env = os.environ.copy()
3750
3934
  verify_env.update({
3751
3935
  "SPEC_VERIFY_PHASE": "verify_mechanical",
@@ -3758,11 +3942,12 @@ def run_self_test() -> int:
3758
3942
  env=verify_env,
3759
3943
  capture_output=True,
3760
3944
  text=True,
3945
+ encoding="utf-8",
3761
3946
  )
3762
3947
  if verify_output_run.returncode == 0:
3763
3948
  print("VERIFY output-mode failing command was accepted", file=sys.stderr)
3764
3949
  return 1
3765
- verify_findings = (verify_devlyn / "verify-mechanical.findings.jsonl").read_text()
3950
+ verify_findings = (verify_devlyn / "verify-mechanical.findings.jsonl").read_text(encoding="utf-8")
3766
3951
  if '"phase": "verify_mechanical"' not in verify_findings or "VERIFY-MECH-" not in verify_findings:
3767
3952
  print("VERIFY output-mode did not route findings to verify-mechanical", file=sys.stderr)
3768
3953
  return 1
@@ -3772,7 +3957,8 @@ def run_self_test() -> int:
3772
3957
  contract_devlyn = contract_root / ".devlyn"
3773
3958
  contract_devlyn.mkdir()
3774
3959
  (contract_root / "package.json").write_text(
3775
- '{\n "dependencies": {},\n "devDependencies": {}\n}\n'
3960
+ '{\n "dependencies": {},\n "devDependencies": {}\n}\n',
3961
+ encoding="utf-8",
3776
3962
  )
3777
3963
  subprocess.run(["git", "init", "-q"], cwd=contract_root, check=True)
3778
3964
  subprocess.run(["git", "add", "-A"], cwd=contract_root, check=True)
@@ -3785,14 +3971,16 @@ def run_self_test() -> int:
3785
3971
  ["git", "rev-parse", "HEAD"],
3786
3972
  cwd=contract_root,
3787
3973
  text=True,
3974
+ encoding="utf-8",
3788
3975
  ).strip()
3789
3976
  contract_spec = contract_root / "spec.md"
3790
- contract_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- expected contract checks.\n")
3791
- (contract_root / "app.js").write_text("try { work(); } catch { return null; }\n")
3792
- (contract_root / "forbidden.txt").write_text("forbidden\n")
3977
+ contract_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- expected contract checks.\n", encoding="utf-8")
3978
+ (contract_root / "app.js").write_text("try { work(); } catch { return null; }\n", encoding="utf-8")
3979
+ (contract_root / "forbidden.txt").write_text("forbidden\n", encoding="utf-8")
3793
3980
  (contract_root / "package.json").write_text(
3794
3981
  '{\n "dependencies": {\n "left-pad": "1.3.0"\n },\n'
3795
- ' "devDependencies": {}\n}\n'
3982
+ ' "devDependencies": {}\n}\n',
3983
+ encoding="utf-8",
3796
3984
  )
3797
3985
  (contract_root / "spec.expected.json").write_text(json.dumps({
3798
3986
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
@@ -3804,22 +3992,23 @@ def run_self_test() -> int:
3804
3992
  "required_files": ["required.txt"],
3805
3993
  "forbidden_files": ["forbidden.txt"],
3806
3994
  "max_deps_added": 0,
3807
- }) + "\n")
3995
+ }) + "\n", encoding="utf-8")
3808
3996
  subprocess.run(["git", "add", "-A"], cwd=contract_root, check=True)
3809
3997
  (contract_devlyn / "pipeline.state.json").write_text(json.dumps({
3810
3998
  "source": {"type": "spec", "spec_path": str(contract_spec)},
3811
3999
  "base_ref": {"sha": base_sha},
3812
- }))
4000
+ }), encoding="utf-8")
3813
4001
  contract_run = subprocess.run(
3814
4002
  [sys.executable, script_path],
3815
4003
  cwd=contract_root,
3816
4004
  capture_output=True,
3817
4005
  text=True,
4006
+ encoding="utf-8",
3818
4007
  )
3819
4008
  if contract_run.returncode == 0:
3820
4009
  print("expected contract violations were accepted", file=sys.stderr)
3821
4010
  return 1
3822
- findings_text = (contract_devlyn / output_findings_name()).read_text()
4011
+ findings_text = (contract_devlyn / output_findings_name()).read_text(encoding="utf-8")
3823
4012
  for rule_id in (
3824
4013
  "correctness.forbidden-pattern",
3825
4014
  "correctness.required-file-missing",
@@ -3837,13 +4026,14 @@ def run_self_test() -> int:
3837
4026
  "exit_code": 0,
3838
4027
  "tags": ["boundary_overlap"],
3839
4028
  "tag_evidence": {"boundary_overlap": ["one_minute_overlap"]},
3840
- }) + "\n")
4029
+ }) + "\n", encoding="utf-8")
3841
4030
  weak = subprocess.run(
3842
4031
  [sys.executable, script_path, "--validate-risk-probes"],
3843
4032
  cwd=work,
3844
4033
  env=env,
3845
4034
  capture_output=True,
3846
4035
  text=True,
4036
+ encoding="utf-8",
3847
4037
  )
3848
4038
  if weak.returncode == 0:
3849
4039
  print("incomplete boundary_overlap evidence was accepted", file=sys.stderr)
@@ -3857,11 +4047,12 @@ def run_self_test() -> int:
3857
4047
  error_spec.write_text(
3858
4048
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
3859
4049
  "- Invalid input must print JSON error object `{ \"error\": \"bad_input\" }` to stderr and exit 2.\n"
3860
- "- Malformed input must exit 2 and print stderr JSON with keys `code` and `detail`; values are implementation-defined.\n"
4050
+ "- Malformed input must exit 2 and print stderr JSON with keys `code` and `detail`; values are implementation-defined.\n",
4051
+ encoding="utf-8",
3861
4052
  )
3862
4053
  (error_devlyn / "pipeline.state.json").write_text(json.dumps({
3863
4054
  "source": {"type": "spec", "spec_path": str(error_spec)}
3864
- }))
4055
+ }), encoding="utf-8")
3865
4056
  (error_devlyn / "risk-probes.jsonl").write_text(json.dumps({
3866
4057
  "id": "P6",
3867
4058
  "derived_from": "Invalid input must print a JSON error object to stderr and exit 2.",
@@ -3872,12 +4063,13 @@ def run_self_test() -> int:
3872
4063
  "stdout_stderr_contract": ["asserts_named_stream_output"],
3873
4064
  "error_contract": ["asserts_error_payload_or_stderr"],
3874
4065
  },
3875
- }) + "\n")
4066
+ }) + "\n", encoding="utf-8")
3876
4067
  weak_error_contract = subprocess.run(
3877
4068
  [sys.executable, script_path, "--validate-risk-probes"],
3878
4069
  cwd=error_root,
3879
4070
  capture_output=True,
3880
4071
  text=True,
4072
+ encoding="utf-8",
3881
4073
  )
3882
4074
  if weak_error_contract.returncode == 0:
3883
4075
  print("error_contract without exit-code evidence was accepted", file=sys.stderr)
@@ -3896,12 +4088,13 @@ def run_self_test() -> int:
3896
4088
  "asserts_nonzero_or_exit_2",
3897
4089
  ],
3898
4090
  },
3899
- }) + "\n")
4091
+ }) + "\n", encoding="utf-8")
3900
4092
  weak_stdio_contract = subprocess.run(
3901
4093
  [sys.executable, script_path, "--validate-risk-probes"],
3902
4094
  cwd=error_root,
3903
4095
  capture_output=True,
3904
4096
  text=True,
4097
+ encoding="utf-8",
3905
4098
  )
3906
4099
  if weak_stdio_contract.returncode == 0:
3907
4100
  print("stdout_stderr_contract without stream evidence was accepted", file=sys.stderr)
@@ -3926,12 +4119,13 @@ def run_self_test() -> int:
3926
4119
  "visible_text_names_exact_json_error_object",
3927
4120
  ],
3928
4121
  },
3929
- }) + "\n")
4122
+ }) + "\n", encoding="utf-8")
3930
4123
  missing_exact_error_object = subprocess.run(
3931
4124
  [sys.executable, script_path, "--validate-risk-probes"],
3932
4125
  cwd=error_root,
3933
4126
  capture_output=True,
3934
4127
  text=True,
4128
+ encoding="utf-8",
3935
4129
  )
3936
4130
  if missing_exact_error_object.returncode == 0:
3937
4131
  print(
@@ -3961,12 +4155,13 @@ def run_self_test() -> int:
3961
4155
  "asserts_exact_error_object",
3962
4156
  ],
3963
4157
  },
3964
- }) + "\n")
4158
+ }) + "\n", encoding="utf-8")
3965
4159
  strong_json_error_shape_contract = subprocess.run(
3966
4160
  [sys.executable, script_path, "--validate-risk-probes"],
3967
4161
  cwd=error_root,
3968
4162
  capture_output=True,
3969
4163
  text=True,
4164
+ encoding="utf-8",
3970
4165
  )
3971
4166
  if strong_json_error_shape_contract.returncode != 0:
3972
4167
  print("JSON error object shape_contract with exact object evidence was rejected", file=sys.stderr)
@@ -3993,12 +4188,13 @@ def run_self_test() -> int:
3993
4188
  "asserts_no_unexpected_output_keys",
3994
4189
  ],
3995
4190
  },
3996
- }) + "\n")
4191
+ }) + "\n", encoding="utf-8")
3997
4192
  error_exit_shape_contract = subprocess.run(
3998
4193
  [sys.executable, script_path, "--validate-risk-probes"],
3999
4194
  cwd=error_root,
4000
4195
  capture_output=True,
4001
4196
  text=True,
4197
+ encoding="utf-8",
4002
4198
  )
4003
4199
  if error_exit_shape_contract.returncode != 0:
4004
4200
  print("error-exit shape_contract without visible exact error object was rejected", file=sys.stderr)
@@ -4012,11 +4208,12 @@ def run_self_test() -> int:
4012
4208
  http_error_spec = http_error_root / "spec.md"
4013
4209
  http_error_spec.write_text(
4014
4210
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4015
- "- An invalid query returns HTTP 400 with JSON error body `{ \"error\": \"invalid_query\", \"field\": \"per_page\" }`.\n"
4211
+ "- An invalid query returns HTTP 400 with JSON error body `{ \"error\": \"invalid_query\", \"field\": \"per_page\" }`.\n",
4212
+ encoding="utf-8",
4016
4213
  )
4017
4214
  (http_error_devlyn / "pipeline.state.json").write_text(json.dumps({
4018
4215
  "source": {"type": "spec", "spec_path": str(http_error_spec)}
4019
- }))
4216
+ }), encoding="utf-8")
4020
4217
  (http_error_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4021
4218
  "id": "P8b",
4022
4219
  "derived_from": (
@@ -4029,12 +4226,13 @@ def run_self_test() -> int:
4029
4226
  "tag_evidence": {
4030
4227
  "http_error_contract": ["asserts_http_error_status"],
4031
4228
  },
4032
- }) + "\n")
4229
+ }) + "\n", encoding="utf-8")
4033
4230
  incomplete_http_error_contract = subprocess.run(
4034
4231
  [sys.executable, script_path, "--validate-risk-probes"],
4035
4232
  cwd=http_error_root,
4036
4233
  capture_output=True,
4037
4234
  text=True,
4235
+ encoding="utf-8",
4038
4236
  )
4039
4237
  if incomplete_http_error_contract.returncode == 0:
4040
4238
  print("http_error_contract without payload evidence was accepted", file=sys.stderr)
@@ -4061,12 +4259,13 @@ def run_self_test() -> int:
4061
4259
  "visible_text_names_exact_json_error_object",
4062
4260
  ],
4063
4261
  },
4064
- }) + "\n")
4262
+ }) + "\n", encoding="utf-8")
4065
4263
  weak_exact_error_shape_contract = subprocess.run(
4066
4264
  [sys.executable, script_path, "--validate-risk-probes"],
4067
4265
  cwd=http_error_root,
4068
4266
  capture_output=True,
4069
4267
  text=True,
4268
+ encoding="utf-8",
4070
4269
  )
4071
4270
  if weak_exact_error_shape_contract.returncode == 0:
4072
4271
  print("exact error body shape_contract without exact object evidence was accepted", file=sys.stderr)
@@ -4094,12 +4293,13 @@ def run_self_test() -> int:
4094
4293
  "asserts_exact_error_object",
4095
4294
  ],
4096
4295
  },
4097
- }) + "\n")
4296
+ }) + "\n", encoding="utf-8")
4098
4297
  strong_exact_error_shape_contract = subprocess.run(
4099
4298
  [sys.executable, script_path, "--validate-risk-probes"],
4100
4299
  cwd=http_error_root,
4101
4300
  capture_output=True,
4102
4301
  text=True,
4302
+ encoding="utf-8",
4103
4303
  )
4104
4304
  if strong_exact_error_shape_contract.returncode != 0:
4105
4305
  print("exact error body shape_contract with exact object evidence was rejected", file=sys.stderr)
@@ -4114,11 +4314,12 @@ def run_self_test() -> int:
4114
4314
  shape_spec.write_text(
4115
4315
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4116
4316
  "- On success, output is one JSON object with keys `applied`, `rejected`, and `accounts`; "
4117
- "`rejected` rows have keys `id` and `reason`.\n"
4317
+ "`rejected` rows have keys `id` and `reason`.\n",
4318
+ encoding="utf-8",
4118
4319
  )
4119
4320
  (shape_devlyn / "pipeline.state.json").write_text(json.dumps({
4120
4321
  "source": {"type": "spec", "spec_path": str(shape_spec)}
4121
- }))
4322
+ }), encoding="utf-8")
4122
4323
  (shape_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4123
4324
  "id": "P8e",
4124
4325
  "derived_from": (
@@ -4129,12 +4330,13 @@ def run_self_test() -> int:
4129
4330
  "exit_code": 0,
4130
4331
  "tags": ["shape_contract"],
4131
4332
  "tag_evidence": {},
4132
- }) + "\n")
4333
+ }) + "\n", encoding="utf-8")
4133
4334
  weak_shape_contract = subprocess.run(
4134
4335
  [sys.executable, script_path, "--validate-risk-probes"],
4135
4336
  cwd=shape_root,
4136
4337
  capture_output=True,
4137
4338
  text=True,
4339
+ encoding="utf-8",
4138
4340
  )
4139
4341
  if weak_shape_contract.returncode == 0:
4140
4342
  print("shape_contract without any evidence was accepted", file=sys.stderr)
@@ -4156,12 +4358,13 @@ def run_self_test() -> int:
4156
4358
  "asserts_no_unexpected_output_keys",
4157
4359
  ],
4158
4360
  },
4159
- }) + "\n")
4361
+ }) + "\n", encoding="utf-8")
4160
4362
  strong_shape_contract = subprocess.run(
4161
4363
  [sys.executable, script_path, "--validate-risk-probes"],
4162
4364
  cwd=shape_root,
4163
4365
  capture_output=True,
4164
4366
  text=True,
4367
+ encoding="utf-8",
4165
4368
  )
4166
4369
  if strong_shape_contract.returncode != 0:
4167
4370
  print("shape_contract with exact key evidence was rejected", file=sys.stderr)
@@ -4181,7 +4384,8 @@ def run_self_test() -> int:
4181
4384
  required_spec.write_text(
4182
4385
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4183
4386
  "- A failed all-or-nothing operation must roll back tentative state "
4184
- "so later orders can use the released stock.\n"
4387
+ "so later orders can use the released stock.\n",
4388
+ encoding="utf-8",
4185
4389
  )
4186
4390
  (required_root / "spec.expected.json").write_text(json.dumps({
4187
4391
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
@@ -4194,10 +4398,10 @@ def run_self_test() -> int:
4194
4398
  ),
4195
4399
  },
4196
4400
  ],
4197
- }) + "\n")
4401
+ }) + "\n", encoding="utf-8")
4198
4402
  (required_devlyn / "pipeline.state.json").write_text(json.dumps({
4199
4403
  "source": {"type": "spec", "spec_path": str(required_spec)}
4200
- }))
4404
+ }), encoding="utf-8")
4201
4405
  (required_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4202
4406
  "id": "P14",
4203
4407
  "derived_from": (
@@ -4213,12 +4417,13 @@ def run_self_test() -> int:
4213
4417
  "later_entity_fails_or_reroutes",
4214
4418
  ],
4215
4419
  },
4216
- }) + "\n")
4420
+ }) + "\n", encoding="utf-8")
4217
4421
  missing_declared_requirement = subprocess.run(
4218
4422
  [sys.executable, script_path, "--validate-risk-probes"],
4219
4423
  cwd=required_root,
4220
4424
  capture_output=True,
4221
4425
  text=True,
4426
+ encoding="utf-8",
4222
4427
  )
4223
4428
  if missing_declared_requirement.returncode == 0:
4224
4429
  print(
@@ -4250,12 +4455,13 @@ def run_self_test() -> int:
4250
4455
  "later_entity_uses_released_state",
4251
4456
  ],
4252
4457
  },
4253
- }) + "\n")
4458
+ }) + "\n", encoding="utf-8")
4254
4459
  covered_declared_requirement = subprocess.run(
4255
4460
  [sys.executable, script_path, "--validate-risk-probes"],
4256
4461
  cwd=required_root,
4257
4462
  capture_output=True,
4258
4463
  text=True,
4464
+ encoding="utf-8",
4259
4465
  )
4260
4466
  if covered_declared_requirement.returncode != 0:
4261
4467
  print(
@@ -4271,12 +4477,13 @@ def run_self_test() -> int:
4271
4477
  "required_risk_probe_requirements": [
4272
4478
  {"tag": "not-a-real-tag", "derived_from": "irrelevant"},
4273
4479
  ],
4274
- }) + "\n")
4480
+ }) + "\n", encoding="utf-8")
4275
4481
  malformed_requirement_tag = subprocess.run(
4276
4482
  [sys.executable, script_path, "--validate-risk-probes"],
4277
4483
  cwd=required_root,
4278
4484
  capture_output=True,
4279
4485
  text=True,
4486
+ encoding="utf-8",
4280
4487
  )
4281
4488
  if malformed_requirement_tag.returncode == 0:
4282
4489
  print("required_risk_probe_requirements with an unknown tag was accepted", file=sys.stderr)
@@ -4287,12 +4494,13 @@ def run_self_test() -> int:
4287
4494
  "required_risk_probe_requirements": [
4288
4495
  {"tag": "rollback_state", "derived_from": "text not present in the spec"},
4289
4496
  ],
4290
- }) + "\n")
4497
+ }) + "\n", encoding="utf-8")
4291
4498
  malformed_requirement_derived_from = subprocess.run(
4292
4499
  [sys.executable, script_path, "--validate-risk-probes"],
4293
4500
  cwd=required_root,
4294
4501
  capture_output=True,
4295
4502
  text=True,
4503
+ encoding="utf-8",
4296
4504
  )
4297
4505
  if malformed_requirement_derived_from.returncode == 0:
4298
4506
  print(
@@ -4310,11 +4518,12 @@ def run_self_test() -> int:
4310
4518
  atomic_batch_spec.write_text(
4311
4519
  "# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n"
4312
4520
  "- A POST with one valid + one invalid item returns `400`, AND a subsequent GET returns the same list as before the import.\n"
4313
- "- A POST with all-valid items returns `201`, and the items appear in GET output in order with distinct ids.\n"
4521
+ "- A POST with all-valid items returns `201`, and the items appear in GET output in order with distinct ids.\n",
4522
+ encoding="utf-8",
4314
4523
  )
4315
4524
  (atomic_batch_devlyn / "pipeline.state.json").write_text(json.dumps({
4316
4525
  "source": {"type": "spec", "spec_path": str(atomic_batch_spec)}
4317
- }))
4526
+ }), encoding="utf-8")
4318
4527
  (atomic_batch_devlyn / "risk-probes.jsonl").write_text(json.dumps({
4319
4528
  "id": "P13b",
4320
4529
  "derived_from": (
@@ -4330,12 +4539,13 @@ def run_self_test() -> int:
4330
4539
  "asserts_store_unchanged_after_failure",
4331
4540
  ],
4332
4541
  },
4333
- }) + "\n")
4542
+ }) + "\n", encoding="utf-8")
4334
4543
  incomplete_atomic_batch_probe = subprocess.run(
4335
4544
  [sys.executable, script_path, "--validate-risk-probes"],
4336
4545
  cwd=atomic_batch_root,
4337
4546
  capture_output=True,
4338
4547
  text=True,
4548
+ encoding="utf-8",
4339
4549
  )
4340
4550
  if incomplete_atomic_batch_probe.returncode == 0:
4341
4551
  print("atomic_batch_state without success-order evidence was accepted", file=sys.stderr)
@@ -4350,15 +4560,15 @@ def run_self_test() -> int:
4350
4560
  (scope_root / "lib").mkdir()
4351
4561
  (scope_root / "lib2").mkdir()
4352
4562
  (scope_root / "data").mkdir()
4353
- (scope_root / "bin" / "cli.js").write_text("module.exports = {};\n")
4354
- (scope_root / "lib" / "keep.js").write_text("module.exports = {};\n")
4355
- (scope_root / "lib2" / "keep.js").write_text("module.exports = {};\n")
4356
- (scope_root / "data" / "usage-stats.json").write_text("{}\n")
4563
+ (scope_root / "bin" / "cli.js").write_text("module.exports = {};\n", encoding="utf-8")
4564
+ (scope_root / "lib" / "keep.js").write_text("module.exports = {};\n", encoding="utf-8")
4565
+ (scope_root / "lib2" / "keep.js").write_text("module.exports = {};\n", encoding="utf-8")
4566
+ (scope_root / "data" / "usage-stats.json").write_text("{}\n", encoding="utf-8")
4357
4567
  scope_spec = scope_root / "spec.md"
4358
- scope_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- scope gate checks.\n")
4568
+ scope_spec.write_text("# Spec\n\n<!-- devlyn:verification -->\n## Verification\n\n- scope gate checks.\n", encoding="utf-8")
4359
4569
  (scope_root / "spec.expected.json").write_text(json.dumps({
4360
4570
  "verification_commands": [{"cmd": "printf ok", "stdout_contains": ["ok"]}],
4361
- }) + "\n")
4571
+ }) + "\n", encoding="utf-8")
4362
4572
  subprocess.run(["git", "init", "-q"], cwd=scope_root, check=True)
4363
4573
  subprocess.run(["git", "add", "-A"], cwd=scope_root, check=True)
4364
4574
  subprocess.run(
@@ -4368,11 +4578,12 @@ def run_self_test() -> int:
4368
4578
  )
4369
4579
  scope_base_sha = subprocess.check_output(
4370
4580
  ["git", "rev-parse", "HEAD"], cwd=scope_root, text=True,
4581
+ encoding="utf-8",
4371
4582
  ).strip()
4372
4583
  (scope_devlyn / "pipeline.state.json").write_text(json.dumps({
4373
4584
  "source": {"type": "spec", "spec_path": str(scope_spec)},
4374
4585
  "base_ref": {"sha": scope_base_sha},
4375
- }))
4586
+ }), encoding="utf-8")
4376
4587
  (scope_root / "preexisting.local").write_text("pre-existing untracked\n", encoding="utf-8")
4377
4588
  # Writer parity: --write-untracked-baseline must share the reader's
4378
4589
  # parser — untracked DIRECTORIES expand to per-file paths and
@@ -4384,6 +4595,7 @@ def run_self_test() -> int:
4384
4595
  write_baseline_run = subprocess.run(
4385
4596
  [sys.executable, script_path, "--write-untracked-baseline"],
4386
4597
  cwd=scope_root, capture_output=True, text=True,
4598
+ encoding="utf-8",
4387
4599
  )
4388
4600
  if write_baseline_run.returncode != 0:
4389
4601
  print("--write-untracked-baseline failed", file=sys.stderr)
@@ -4399,30 +4611,33 @@ def run_self_test() -> int:
4399
4611
  scope_build_gate_env["SPEC_VERIFY_PHASE"] = "build_gate"
4400
4612
 
4401
4613
  # Test 1: no .devlyn/plan.md at all -> fail-closed CRITICAL, not a no-op.
4402
- (scope_root / "bin" / "cli.js").write_text("module.exports = { ok: true };\n")
4614
+ (scope_root / "bin" / "cli.js").write_text("module.exports = { ok: true };\n", encoding="utf-8")
4403
4615
  missing_plan_run = subprocess.run(
4404
4616
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4405
4617
  capture_output=True, text=True,
4618
+ encoding="utf-8",
4406
4619
  )
4407
4620
  if missing_plan_run.returncode == 0:
4408
4621
  print("BUILD_GATE accepted a run with no plan.md", file=sys.stderr)
4409
4622
  return 1
4410
- if "scope.authorized-surface-malformed" not in scope_findings_path.read_text():
4623
+ if "scope.authorized-surface-malformed" not in scope_findings_path.read_text(encoding="utf-8"):
4411
4624
  print("missing plan.md did not emit scope.authorized-surface-malformed", file=sys.stderr)
4412
4625
  return 1
4413
4626
 
4414
4627
  # Test 2: plan.md present but no authorized_surface json block -> malformed.
4415
4628
  (scope_devlyn / "plan.md").write_text(
4416
- "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n- `bin/cli.js` (edit): ship the fix.\n"
4629
+ "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n- `bin/cli.js` (edit): ship the fix.\n",
4630
+ encoding="utf-8",
4417
4631
  )
4418
4632
  malformed_block_run = subprocess.run(
4419
4633
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4420
4634
  capture_output=True, text=True,
4635
+ encoding="utf-8",
4421
4636
  )
4422
4637
  if malformed_block_run.returncode == 0:
4423
4638
  print("BUILD_GATE accepted plan.md with no authorized_surface block", file=sys.stderr)
4424
4639
  return 1
4425
- if "scope.authorized-surface-malformed" not in scope_findings_path.read_text():
4640
+ if "scope.authorized-surface-malformed" not in scope_findings_path.read_text(encoding="utf-8"):
4426
4641
  print("missing authorized_surface block did not emit scope.authorized-surface-malformed", file=sys.stderr)
4427
4642
  return 1
4428
4643
  malformed_print_surface = subprocess.run(
@@ -4438,15 +4653,18 @@ def run_self_test() -> int:
4438
4653
  (scope_devlyn / "plan.md").write_text(
4439
4654
  "# PLAN\n\n<!-- devlyn:authorized-surface -->\n## 1. Files to touch\n\n"
4440
4655
  "```json\n"
4441
- + json.dumps({"authorized_surface": [entry]}) + "\n```\n"
4656
+ + json.dumps({"authorized_surface": [entry]}) + "\n```\n",
4657
+ encoding="utf-8",
4442
4658
  )
4443
4659
  brace_build_gate = subprocess.run(
4444
4660
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4445
4661
  capture_output=True, text=True,
4662
+ encoding="utf-8",
4446
4663
  )
4447
4664
  brace_print_surface = subprocess.run(
4448
4665
  [sys.executable, script_path, "--print-authorized-surface"], cwd=scope_root,
4449
4666
  capture_output=True, text=True,
4667
+ encoding="utf-8",
4450
4668
  )
4451
4669
  findings_text = scope_findings_path.read_text(encoding="utf-8")
4452
4670
  if (
@@ -4466,9 +4684,10 @@ def run_self_test() -> int:
4466
4684
  "- `bin/cli.js` (edit): ship the fix.\n\n"
4467
4685
  "```json\n"
4468
4686
  '{"authorized_surface": ["bin/cli.js", "lib/**", "authorized-but-uncreated.txt"]}\n'
4469
- "```\n"
4687
+ "```\n",
4688
+ encoding="utf-8",
4470
4689
  )
4471
- (scope_root / "lib" / "new.js").write_text("module.exports = { created: true };\n")
4690
+ (scope_root / "lib" / "new.js").write_text("module.exports = { created: true };\n", encoding="utf-8")
4472
4691
  print_surface = subprocess.run(
4473
4692
  [sys.executable, script_path, "--print-authorized-surface"],
4474
4693
  cwd=scope_root,
@@ -4490,16 +4709,18 @@ def run_self_test() -> int:
4490
4709
  missing_baseline_run = subprocess.run(
4491
4710
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4492
4711
  capture_output=True, text=True,
4712
+ encoding="utf-8",
4493
4713
  )
4494
4714
  if missing_baseline_run.returncode == 0:
4495
4715
  print("BUILD_GATE accepted missing .devlyn/untracked.baseline", file=sys.stderr)
4496
4716
  return 1
4497
- if "untracked.baseline" not in scope_findings_path.read_text():
4717
+ if "untracked.baseline" not in scope_findings_path.read_text(encoding="utf-8"):
4498
4718
  print("missing untracked baseline did not emit a scope finding", file=sys.stderr)
4499
4719
  return 1
4500
4720
  rewrite_baseline_run = subprocess.run(
4501
4721
  [sys.executable, script_path, "--write-untracked-baseline"],
4502
4722
  cwd=scope_root, capture_output=True, text=True,
4723
+ encoding="utf-8",
4503
4724
  )
4504
4725
  if rewrite_baseline_run.returncode != 0:
4505
4726
  print("--write-untracked-baseline re-run failed", file=sys.stderr)
@@ -4507,29 +4728,31 @@ def run_self_test() -> int:
4507
4728
  in_scope_run = subprocess.run(
4508
4729
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4509
4730
  capture_output=True, text=True,
4731
+ encoding="utf-8",
4510
4732
  )
4511
4733
  if in_scope_run.returncode != 0:
4512
4734
  print("in-scope-only diff was rejected", file=sys.stderr)
4513
4735
  print(in_scope_run.stderr, file=sys.stderr)
4514
4736
  return 1
4515
- if "scope." in scope_findings_path.read_text():
4737
+ if "scope." in scope_findings_path.read_text(encoding="utf-8"):
4516
4738
  print("in-scope-only diff produced a spurious scope finding", file=sys.stderr)
4517
- print(scope_findings_path.read_text(), file=sys.stderr)
4739
+ print(scope_findings_path.read_text(encoding="utf-8"), file=sys.stderr)
4518
4740
  return 1
4519
4741
 
4520
4742
  # Test 4: directory grant covers lib/**; an out-of-scope file must be
4521
4743
  # flagged, and the fix_hint must never suggest self-authorization.
4522
- (scope_root / "lib" / "keep.js").write_text("module.exports = { touched: true };\n")
4523
- (scope_root / "data" / "usage-stats.json").write_text('{"leaked": true}\n')
4524
- (scope_root / "data" / "scratch.json").write_text('{"untracked": true}\n')
4744
+ (scope_root / "lib" / "keep.js").write_text("module.exports = { touched: true };\n", encoding="utf-8")
4745
+ (scope_root / "data" / "usage-stats.json").write_text('{"leaked": true}\n', encoding="utf-8")
4746
+ (scope_root / "data" / "scratch.json").write_text('{"untracked": true}\n', encoding="utf-8")
4525
4747
  out_of_scope_run = subprocess.run(
4526
4748
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4527
4749
  capture_output=True, text=True,
4750
+ encoding="utf-8",
4528
4751
  )
4529
4752
  if out_of_scope_run.returncode == 0:
4530
4753
  print("out-of-scope file was accepted", file=sys.stderr)
4531
4754
  return 1
4532
- out_of_scope_lines = scope_findings_path.read_text().splitlines()
4755
+ out_of_scope_lines = scope_findings_path.read_text(encoding="utf-8").splitlines()
4533
4756
  out_of_scope_findings = [loads_strict_json(line) for line in out_of_scope_lines if line.strip()]
4534
4757
  flagged_files = {f["file"] for f in out_of_scope_findings if f.get("rule_id") == "scope.out-of-scope-file"}
4535
4758
  if "data/usage-stats.json" not in flagged_files:
@@ -4558,14 +4781,15 @@ def run_self_test() -> int:
4558
4781
 
4559
4782
  # Test 5: lib2/keep.js must NOT be covered by the lib/** grant
4560
4783
  # (directory-prefix boundary, not a bare string-prefix match).
4561
- (scope_root / "lib2" / "keep.js").write_text("module.exports = { touched: true };\n")
4784
+ (scope_root / "lib2" / "keep.js").write_text("module.exports = { touched: true };\n", encoding="utf-8")
4562
4785
  subprocess.run(
4563
4786
  [sys.executable, script_path], cwd=scope_root, env=scope_build_gate_env,
4564
4787
  capture_output=True, text=True,
4788
+ encoding="utf-8",
4565
4789
  )
4566
4790
  boundary_flagged = {
4567
4791
  loads_strict_json(line)["file"]
4568
- for line in scope_findings_path.read_text().splitlines() if line.strip()
4792
+ for line in scope_findings_path.read_text(encoding="utf-8").splitlines() if line.strip()
4569
4793
  }
4570
4794
  if "lib2/keep.js" not in boundary_flagged:
4571
4795
  print("lib/** incorrectly matched lib2/keep.js (directory-prefix boundary bug)", file=sys.stderr)
@@ -4585,8 +4809,9 @@ def run_self_test() -> int:
4585
4809
  subprocess.run(
4586
4810
  [sys.executable, script_path], cwd=scope_root, env=verify_mech_env,
4587
4811
  capture_output=True, text=True,
4812
+ encoding="utf-8",
4588
4813
  )
4589
- verify_mech_findings = (scope_devlyn / "verify-mechanical.findings.jsonl").read_text()
4814
+ verify_mech_findings = (scope_devlyn / "verify-mechanical.findings.jsonl").read_text(encoding="utf-8")
4590
4815
  if "scope." in verify_mech_findings:
4591
4816
  print("VERIFY MECHANICAL ran the BUILD_GATE-only authorized_surface gate", file=sys.stderr)
4592
4817
  print(verify_mech_findings, file=sys.stderr)
@@ -4799,8 +5024,8 @@ def main() -> int:
4799
5024
  print(f"[spec-verify] carrier malformed: {expected_error}", file=sys.stderr)
4800
5025
  write_malformed_finding(devlyn_dir, expected_error, expected_path)
4801
5026
  return 1
4802
- if expected_staged:
4803
- staged, error = (True, None)
5027
+ if expected_found:
5028
+ staged, error = (expected_staged, None)
4804
5029
  else:
4805
5030
  staged, error = stage_from_source(source_md, devlyn_dir)
4806
5031
  else:
@@ -4850,7 +5075,7 @@ def main() -> int:
4850
5075
  return 0
4851
5076
  else:
4852
5077
  try:
4853
- spec = loads_strict_json(spec_path.read_text())
5078
+ spec = loads_strict_json(spec_path.read_text(encoding="utf-8"))
4854
5079
  except (ValueError, OSError) as e:
4855
5080
  print(f"[spec-verify] error: cannot parse {spec_path}: {e}", file=sys.stderr)
4856
5081
  return 2
@@ -5145,12 +5370,12 @@ def main() -> int:
5145
5370
  results_path.write_text(json.dumps({
5146
5371
  "commands": results,
5147
5372
  "process_evidence": evidence_carrier,
5148
- }, indent=2) + "\n")
5373
+ }, indent=2) + "\n", encoding="utf-8")
5149
5374
 
5150
5375
  # Append findings (jsonl). BUILD_GATE merge step concatenates this onto
5151
5376
  # build_gate.findings.jsonl; never overwrite the orchestrator's own gate
5152
5377
  # findings. Truncate this file each run since it is a per-round artifact.
5153
- with findings_path.open("w") as fh:
5378
+ with findings_path.open("w", encoding="utf-8") as fh:
5154
5379
  for f in findings:
5155
5380
  fh.write(json.dumps(f) + "\n")
5156
5381
 
@@ -5181,4 +5406,5 @@ def main() -> int:
5181
5406
 
5182
5407
 
5183
5408
  if __name__ == "__main__":
5409
+ runpy.run_path(str(Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
5184
5410
  sys.exit(main())