Add Phase 9 validation and advisory review

Record the finite dependency search, Wi-Fi maintenance blocker, and
pinned
icon provenance. Add bounded host orchestration and fixture coverage,
and
update release documentation with current evidence.
This commit is contained in:
2026-09-16 16:26:46 +02:00
parent 51f835c22f
commit cdc4d4a8df
31 changed files with 1748 additions and 45 deletions
+19 -2
View File
@@ -36,8 +36,25 @@ with tempfile.TemporaryDirectory(prefix="admin-console-boundary-") as directory:
"-o", str(path / "certificate")], check=True, timeout=30)
subprocess.run([str(path / "certificate")], check=True, timeout=10)
ssh = (ROOT / "src/ssh_transport.c").read_text()
adapter = ssh[ssh.index("static admin_ssh_console_token_t admin_console_token("):
ssh.index("static void *ssh_malloc(")]
# Bound the adapter by its own functions, not the allocator helpers now
# owned by ssh_memory.c. Fail closed if the reviewed source layout changes.
adapter_markers = (
"static admin_ssh_console_token_t admin_console_token(",
"static void publish_slot(",
"static size_t admin_console_snapshot_index_locked(",
"static bool admin_console_is_current(",
"static bool admin_console_drained(",
"static esp_err_t admin_console_perform(",
"static const admin_console_owner_t s_admin_console_owner = {",
"static bool consume_external_close(",
)
positions = []
for marker in adapter_markers:
assert ssh.count(marker) == 1, f"SSH adapter marker missing/ambiguous: {marker}"
positions.append(ssh.index(marker))
assert positions == sorted(positions), "SSH adapter source order changed"
adapter_end = ssh.index("\n}", positions[-1]) + len("\n}")
adapter = ssh[positions[0]:adapter_end]
unit = ((ROOT / "tests/admin_console_boundary/fakes.h").read_text()
+ strip_includes(header) + "\n" + strip_includes(source)
+ (ROOT / "tests/admin_console_boundary/adapter.c").read_text()
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""Offline orchestrator fixtures only; never invokes pio or existing suites."""
import contextlib
import importlib.util
import io
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import time
import unittest
from unittest.mock import patch
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[2]
spec = importlib.util.spec_from_file_location('phase9', ROOT / 'tools/validate_phase9.py')
runner = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = runner
spec.loader.exec_module(runner)
class ValidationTests(unittest.TestCase):
def options(self, *args):
return runner.parser().parse_args(args)
def command(self, code, name='fixture', timeout=3, required=()):
return runner.Command(name, (sys.executable, '-c', code), timeout, required)
def execute(self, commands, **kwargs):
output = io.StringIO()
with contextlib.redirect_stdout(output):
code = runner.execute(commands, ROOT, **kwargs)
return code, output.getvalue()
def test_default_exact_scope_and_real_paths(self):
commands = runner.plan(self.options())
self.assertEqual(len(commands), 23)
self.assertEqual({c.name for c in commands}, {
'security_build_policy', 'ssh_auth_policy', 'ssh_auth_transport', 'hidden_input',
'ssh_memory', 'sdk_security_overrides', 'wolfssh_auth_contract', 'ssh_protocol_policy',
'wolf_crypto_policy', 'wolfssh_parser_contract', 'wolfssh_order_contract', 'release_notices',
'ssh_management', 'admin_console_boundary', 'admin_ssh_policy', 'web_admin_transport',
'web_admin_tickets', 'web_httpd_idle', 'web_cookie_auth:base', 'web_cookie_auth:admin',
'web_cookie_auth:accounts', 'web_cookie_auth:ssh', 'web_cookie_auth:lifecycle'})
for command in commands:
self.assertTrue((ROOT / command.argv[2]).is_file())
self.assertEqual(command.argv[:2], (sys.executable, '-B'))
self.assertEqual(command.timeout, 180)
self.assertFalse({'--interop', '--host-only', '--candidate', '--target-contracts',
'--pio-adapter', 'pio', '--admission'} & set(command.argv))
by_name = {c.name: c for c in commands}
database = str(runner.BUILD / 'compile_commands.json')
for name in ('wolfssh_auth_contract', 'ssh_protocol_policy', 'wolf_crypto_policy'):
self.assertEqual(by_name[name].argv[3:], ('--compile-commands', database))
self.assertEqual(by_name['sdk_security_overrides'].argv[3:], ('--build-dir', str(runner.BUILD)))
self.assertEqual(by_name['security_build_policy'].argv[3:],
('--sdkconfig-header', str(runner.BUILD / 'config/sdkconfig.h')))
def test_opt_ins_independent_and_timeout(self):
commands = runner.plan(self.options('--build', '--timeout', '4', '--build-timeout', '5'))
self.assertEqual(commands[0], runner.Command('build', ('pio', 'run'), 5))
self.assertTrue(all(c.timeout == 4 for c in commands[1:]))
self.assertFalse(any('--interop' in c.argv for c in commands))
commands = runner.plan(self.options('--interop'))
self.assertNotIn('build', [c.name for c in commands])
self.assertEqual([c.name for c in commands if '--interop' in c.argv], ['wolfssh_order_contract'])
def test_bad_timeout(self):
for value in ('0', '-1', 'nan', 'inf', '3601', 'junk'):
with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
self.options('--timeout', value)
def test_dry_run_never_launches_or_checks_prerequisites(self):
with patch.object(runner, 'run_command', side_effect=AssertionError('executed')):
code, output = self.execute(runner.plan(self.options('--build', '--interop')), dry_run=True)
self.assertEqual(code, 0)
self.assertIn('PLAN build', output)
self.assertNotIn('PASS ', output)
with patch.object(runner, 'run_command', side_effect=AssertionError('executed')):
with contextlib.redirect_stdout(io.StringIO()) as output:
self.assertEqual(runner.main(['--dry-run']), 0)
self.assertIn('SKIP optional build', output.getvalue())
self.assertIn('SKIP optional interop', output.getvalue())
def test_collect_failure_and_failfast_skip(self):
commands = [self.command('raise SystemExit(7)', 'bad'), self.command('pass', 'good')]
code, output = self.execute(commands)
self.assertEqual(code, 1)
self.assertIn('FAIL bad: exit 7', output)
self.assertIn('PASS good', output)
code, output = self.execute(commands, fail_fast=True)
self.assertEqual(code, 1)
self.assertIn('SKIP good', output)
self.assertNotIn('PASS good', output)
def test_failed_build_blocks_stale_artifact_checks(self):
code, output = self.execute([self.command('raise SystemExit(1)', 'build'), self.command('pass', 'host')])
self.assertEqual(code, 1)
self.assertIn('SKIP host', output)
def test_prerequisites(self):
with tempfile.TemporaryDirectory() as tmp:
missing = Path(tmp) / 'missing'
commands = [runner.Command('missing-executable', (str(missing),), 1),
self.command('raise AssertionError()', 'missing-input', required=(missing,))]
code, output = self.execute(commands)
self.assertEqual(code, 1)
self.assertIn('PREREQ missing-executable', output)
self.assertIn('PREREQ missing-input', output)
def test_environment_and_literal_argv(self):
with tempfile.TemporaryDirectory(prefix='phase9 fixture ') as tmp:
script = Path(tmp) / 'fixture ; literal.py'
script.write_text('import os, sys\nassert os.environ["CCACHE_DISABLE"] == "1"\n'
'assert os.environ["PHASE9_FIXTURE"] == "preserved"\n'
'assert sys.argv[1] == "a ; $(not-a-command)"\n')
command = runner.Command('literal', (sys.executable, str(script), 'a ; $(not-a-command)'), 3)
with patch.dict(os.environ, {'PHASE9_FIXTURE': 'preserved', 'CCACHE_DISABLE': '0'}):
self.assertEqual(self.execute([command])[0], 0)
self.assertEqual(os.environ['CCACHE_DISABLE'], '0')
def test_timeout_kills_descendant_and_collects(self):
with tempfile.TemporaryDirectory() as tmp:
marker = Path(tmp) / 'should-not-exist'
child = f'import time; from pathlib import Path; time.sleep(1); Path({str(marker)!r}).touch()'
parent = f'import subprocess, sys, time; subprocess.Popen([sys.executable, "-c", {child!r}]); time.sleep(10)'
started = time.monotonic()
code, output = self.execute([self.command(parent, timeout=0.25), self.command('pass', 'next')])
self.assertLess(time.monotonic() - started, 4)
self.assertEqual(code, 1)
self.assertIn('TIMEOUT fixture', output)
self.assertIn('PASS next', output)
time.sleep(1.1)
self.assertFalse(marker.exists())
def test_streams_not_captured_and_stdin_closed(self):
with patch.object(runner.subprocess, 'Popen') as popen, patch.object(runner.os, 'killpg'):
popen.return_value.wait.return_value = 0
code, _ = self.execute([self.command('pass')])
self.assertEqual(code, 0)
kwargs = popen.call_args.kwargs
self.assertNotIn('stdout', kwargs)
self.assertNotIn('stderr', kwargs)
self.assertNotIn('shell', kwargs)
self.assertEqual(kwargs['stdin'], subprocess.DEVNULL)
self.assertTrue(kwargs['start_new_session'])
if __name__ == '__main__':
unittest.main()
+22 -5
View File
@@ -1,7 +1,7 @@
# Release notice tool tests
Run `python3 tests/release_notices/run.py` from the repository root. Uses Python's
standard library and isolated temporary fixtures only; no managed package,
standard library, isolated temporary fixtures and checked-in icon evidence; no managed package,
SDK, toolchain, PlatformIO, network, or device is required. Linux/POSIX path and
descriptor semantics match the notice tool.
@@ -10,8 +10,25 @@ across moved roots/changed mtimes; missing, empty, changed and oversized inputs;
source body drift outside excerpts; bounds/schema; traversal and symlinks in
input/output ancestry; FIFOs/directories; existing user-data preservation;
explicit output requirement; unlisted secret/config/build exclusion; incomplete
write behavior; and success/failure CLI exits.
write behavior; and success/failure CLI exits. Additional offline icon tests check
exact upstream SHA-256/Git blob identities, release and author metadata, absence
of NOTICE paths in the complete pinned tree, actual retained SVG coordinates,
USB mockup transforms, manual firmware row bytes, and catalog inclusion. A
changed coordinate or added transform is rejected; differing mockup Wi-Fi and
unproven manual rasterization are not described as exact upstream matches.
Real installed-input assembly and recipient delivery are separate checks; see
`docs/release_packaging.md`. Passing these tests is not license clearance or
proof of corresponding-source compliance.
Real installed-input assembly is an explicit, separate offline check:
```sh
python3 tests/release_notices/actual_bundle.py \
--sdk-root /home/mscholz/.platformio/packages/framework-espidf \
--toolchain-root /home/mscholz/.platformio/packages/toolchain-xtensa-esp-elf
```
Use literal paths for the reviewed installed snapshot. This invokes the real
CLI twice with fresh temporary outputs outside input roots, verifies catalog
identity, every manifest entry and payload hash/size, absence of extra files,
and identical relative names/bytes across outputs. Temporary outputs are removed
after the check. No fetch, dependency installation, build or device access.
Recipient delivery remains separate; see `docs/release_packaging.md`. Passing
these tests is not license clearance or corresponding-source compliance.
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-only
"""Assemble two real bundles in temporary directories and verify every byte."""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import tempfile
PROJECT = Path(__file__).absolute().parents[2]
def contents(root):
return {p.relative_to(root).as_posix(): p.read_bytes()
for p in root.rglob("*") if p.is_file()}
def validate(files, catalog_data):
manifest = json.loads(files["manifest.json"])
catalog = json.loads(catalog_data)
assert manifest["catalog_sha256"] == hashlib.sha256(catalog_data).hexdigest()
assert manifest["snapshot"] == catalog["snapshot"]
assert len(manifest["inputs"]) == len(catalog["inputs"])
expected_entries = {(x["root"], x["path"]): x for x in catalog["inputs"]}
expected_files = {"manifest.json"}
seen = set()
for entry in manifest["inputs"]:
key = (entry["root"], entry["path"])
assert key not in seen
seen.add(key)
original = expected_entries[key]
assert {k: entry[k] for k in original} == original
path = "inputs/" + entry["root"] + "/" + entry["path"]
if entry["range"] is not None:
path += ".notice.txt"
assert entry["output"] == path
expected_files.add(path)
data = files[path]
assert len(data) == entry["output_size"]
assert hashlib.sha256(data).hexdigest() == entry["output_sha256"]
assert seen == set(expected_entries)
assert len(manifest["generated"]) == 1
assert manifest["generated"][0]["path"] == "README.txt"
for entry in manifest["generated"]:
expected_files.add(entry["path"])
data = files[entry["path"]]
assert len(data) == entry["size"]
assert hashlib.sha256(data).hexdigest() == entry["sha256"]
assert set(files) == expected_files, "unexpected or missing bundle files"
return len(manifest["inputs"])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--sdk-root", type=Path, required=True)
parser.add_argument("--toolchain-root", type=Path, required=True)
args = parser.parse_args()
catalog = (PROJECT / "third_party/release-notices/inputs.json").read_bytes()
with tempfile.TemporaryDirectory(prefix="actual-release-notices-") as temporary:
base = Path(temporary)
outputs = []
for name in ("first", "second"):
output = base / name
subprocess.run([
sys.executable, str(PROJECT / "tools/release_notices.py"),
"--sdk-root", str(args.sdk_root),
"--toolchain-root", str(args.toolchain_root), "--output", str(output),
], check=True, timeout=60)
files = contents(output)
count = validate(files, catalog)
outputs.append(files)
assert outputs[0] == outputs[1], "bundle names/bytes differ"
files = outputs[0]
print(f"PASS: two actual bundles; {count} inputs; {len(files)} files; "
f"{sum(map(len, files.values()))} bytes; all payloads verified and deterministic")
print("manifest SHA-256:", hashlib.sha256(files["manifest.json"]).hexdigest())
return 0
if __name__ == "__main__":
sys.exit(main())
+173
View File
@@ -0,0 +1,173 @@
# SPDX-License-Identifier: GPL-3.0-only
"""Offline checks of actual project icon derivatives, not a raster generator."""
import copy
import hashlib
import json
from pathlib import Path
import re
import unittest
import xml.etree.ElementTree as ET
PROJECT = Path(__file__).absolute().parents[2]
ICONS = PROJECT / "third_party/material-design-icons"
UPSTREAM = ICONS / "upstream-7.4.47"
COMMIT = "9e04201d4557e729822fb57f62a316c3dea1d4a8"
TAG = "5edde266e281d26a03dcfa89fb651183cbab0f2e"
SVG = "{http://www.w3.org/2000/svg}"
PINS = {
"usb.svg": "c9918e9a983fbd788378ca4c524e7a07a0d5eedcaeff73d19814e6f6ae221f22",
"wifi-strength-4.svg": "89d14daf863076b0f73c76d913212875f2e9bcddaaf49c9e5d1825e0b2dc2d5f",
"LICENSE": "f3bc8715bad84b26396bb42d2abd11f919cf58163be158eab79ecbaabf84cdf2",
"package.json": "ffeb0e4cc17b4cb124408bd0068667b9a35e985971792978e40d4cd69c7ca8b7",
"README.md": "db5acca5eb4e2c9113c3408ed1ae7d17c76f653340a5d4e3f8a58d2bcdf50b7d",
"meta.json": "ed9d7c7a5ca3ce7dee023fb6c1d62715dbec5b27503d268cbc10b9c8894ef3e8",
"tag.json": "f02dc7fc1e862c344c66877828b1914543f0656db8544c05a1ed3b7d990b6d75",
"tree.json": "b75710c2b9ee4d0ad3f79016e9767a85103f16df6cbbe5f0629efc3b470d1569",
"Apache-2.0.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
}
def load(name):
return json.loads((UPSTREAM / name).read_bytes())
def geometry(element):
"""Strict comparison: no coordinate normalization or hidden transforms."""
if set(element.attrib) != {"id", "viewBox"}:
raise ValueError("unexpected root/symbol geometry attributes")
if element.attrib["viewBox"] != "0 0 24 24":
raise ValueError("unexpected viewBox")
if len(element) != 1 or element[0].tag != SVG + "path":
raise ValueError("expected exactly one path")
if set(element[0].attrib) != {"d"}:
raise ValueError("unexpected path geometry attributes")
return element[0].attrib["d"]
class IconProvenanceTests(unittest.TestCase):
def test_download_pins_and_git_blobs(self):
records = load("downloads.json")
self.assertEqual({r["path"] for r in records}, set(PINS))
self.assertEqual(len(records), len(PINS))
tree = {x["path"]: x for x in load("tree.json")["tree"]}
for record in records:
name = record["path"]
with self.subTest(name=name):
data = (UPSTREAM / name).read_bytes()
self.assertEqual(len(data), record["size"])
self.assertEqual(hashlib.sha256(data).hexdigest(), PINS[name])
self.assertEqual(record["sha256"], PINS[name])
if name not in ("tag.json", "tree.json", "Apache-2.0.txt"):
path = "svg/" + name if name.endswith(".svg") else name
self.assertEqual(record["url"],
"https://raw.githubusercontent.com/Templarian/"
f"MaterialDesign-SVG/{COMMIT}/{path}")
self.assertEqual(tree[path]["size"], len(data))
blob = b"blob " + str(len(data)).encode() + b"\0" + data
self.assertEqual(hashlib.sha1(blob).hexdigest(), tree[path]["sha"])
for name in ("usb.svg", "wifi-strength-4.svg", "LICENSE"):
self.assertEqual((ICONS / name).read_bytes(), (UPSTREAM / name).read_bytes())
def test_release_license_authors_and_notice_scope(self):
tag = load("tag.json")
self.assertEqual(tag["sha"], TAG)
self.assertEqual(tag["tag"], "v7.4.47")
self.assertEqual(tag["object"]["sha"], COMMIT)
self.assertEqual(tag["object"]["type"], "commit")
self.assertFalse(tag["verification"]["verified"])
package = load("package.json")
self.assertEqual((package["name"], package["version"], package["license"]),
("@mdi/svg", "7.4.47", "Apache-2.0"))
expected = {
"usb": ("Google", "25033E0B-3AD4-414D-9972-559F2690FC1D", "1.5.54"),
"wifi-strength-4": ("Simran", "41B86B22-7245-4A97-9BAA-3E9EBD44CEB0", "2.3.50"),
}
selected = [x for x in load("meta.json") if x["name"] in expected]
self.assertEqual(len(selected), 2)
for icon in selected:
self.assertEqual((icon["author"], icon["id"], icon["version"]), expected[icon["name"]])
self.assertNotIn("license", icon)
tree = load("tree.json")
self.assertFalse(tree["truncated"])
self.assertFalse([x["path"] for x in tree["tree"] if "notice" in x["path"].lower()])
def test_exact_retained_geometry_and_negative_changes(self):
for name in ("usb.svg", "wifi-strength-4.svg"):
original = ET.parse(UPSTREAM / name).getroot()
local = ET.parse(ICONS / name).getroot()
self.assertEqual(geometry(local), geometry(original))
changed = copy.deepcopy(local)
changed[0].set("d", changed[0].get("d").replace("M", "M0,0L", 1))
self.assertNotEqual(geometry(changed), geometry(original))
for node in (0, None):
changed = copy.deepcopy(local)
(changed if node is None else changed[node]).set("transform", "translate(1 0)")
with self.assertRaises(ValueError):
geometry(changed)
def test_actual_mockup_paths_and_transforms(self):
root = ET.parse(PROJECT / "docs/phase7c_icon_mockup.svg").getroot()
parents = {child: parent for parent in root.iter() for child in parent}
symbols = {x.get("id"): x for x in root.iter(SVG + "symbol")}
self.assertEqual(geometry(symbols["usb"]), geometry(ET.parse(UPSTREAM / "usb.svg").getroot()))
self.assertNotEqual(geometry(symbols["wifi"]),
geometry(ET.parse(UPSTREAM / "wifi-strength-4.svg").getroot()))
uses = [x for x in root.iter(SVG + "use") if x.get("href") == "#usb"]
self.assertEqual([x.attrib for x in uses], [
{"href": "#usb", "x": "150", "y": "4", "width": "30", "height": "30"},
{"href": "#usb", "x": "8", "y": "72", "width": "30", "height": "30"},
])
def ancestry(node):
result = []
while node in parents:
node = parents[node]
if "transform" in node.attrib:
result.append(node.get("transform"))
# Nested SVG viewports would require additional mappings.
if node is not root:
self.assertNotEqual(node.tag, SVG + "svg")
return result
self.assertEqual(ancestry(uses[0]), [])
self.assertEqual(parents[parents[uses[0]]].get("id"), "status")
self.assertEqual(ancestry(uses[1]), ["translate(62 438)"])
status_uses = [x for x in root.iter(SVG + "use") if x.get("href") == "#status"]
self.assertEqual([x.attrib for x in status_uses], [{"href": "#status"}] * 4)
self.assertEqual([ancestry(x) for x in status_uses], [
["translate(62 98)"], ["translate(592 98)"],
["translate(62 438)"], ["translate(592 438)"],
])
def test_actual_manual_masks_not_inferred_rasterization(self):
source = (PROJECT / "src/local_status_ui.c").read_text()
self.assertIn("hand-rasterized 8x8 derivative", source)
self.assertIn('adaptations of "wifi-strength-4"', source)
masks = {
"usb": "18 3c 18 5a 3e 18 3c 18",
"wifi_full": "7e 81 3c 42 18 24 00 18",
"wifi_three": "00 00 3c 42 18 24 00 18",
"wifi_two": "00 00 00 00 18 24 00 18",
"wifi_one": "00 00 00 00 00 00 00 18",
}
for name, expected in masks.items():
match = re.search(r"s_icon_" + name + r"\[LOCAL_STATUS_UI_ICON_SIZE\]\s*=\s*\{([^}]+)\}", source)
self.assertIsNotNone(match)
actual = re.findall(r"0x([0-9a-fA-F]{2})U", match[1])
self.assertEqual(bytes.fromhex(" ".join(actual)), bytes.fromhex(expected))
def test_all_icon_evidence_is_pinned_in_catalog(self):
catalog = json.loads((PROJECT / "third_party/release-notices/inputs.json").read_bytes())
entries = {(x["root"], x["path"]): x for x in catalog["inputs"]}
paths = [UPSTREAM / name for name in (*PINS, "downloads.json")]
paths += [ICONS / name for name in ("LICENSE", "usb.svg", "wifi-strength-4.svg")]
paths += [PROJECT / "docs/icon_provenance.md"]
for path in paths:
with self.subTest(path=path.name):
entry = entries[("project", path.relative_to(PROJECT).as_posix())]
data = path.read_bytes()
self.assertIsNone(entry["range"])
self.assertEqual(entry["size"], len(data))
self.assertEqual(entry["sha256"], hashlib.sha256(data).hexdigest())
self.assertEqual(entry["output_sha256"], entry["sha256"])
+2
View File
@@ -14,6 +14,8 @@ import unittest
from unittest import mock
sys.dont_write_bytecode = True
from icon_provenance import IconProvenanceTests
PROJECT = Path(__file__).absolute().parents[2]
SPEC = importlib.util.spec_from_file_location("release_notices", PROJECT / "tools/release_notices.py")
notices = importlib.util.module_from_spec(SPEC)