tigrcorn-certification 0.3.16__py3-none-any.whl
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.
- tigrcorn_certification/__init__.py +55 -0
- tigrcorn_certification/aioquic_preflight.py +449 -0
- tigrcorn_certification/certification_env.py +423 -0
- tigrcorn_certification/conformance.py +42 -0
- tigrcorn_certification/explicit_surfaces.py +130 -0
- tigrcorn_certification/interop_runner.py +2017 -0
- tigrcorn_certification/perf_runner.py +725 -0
- tigrcorn_certification/py.typed +1 -0
- tigrcorn_certification/release_gates.py +1351 -0
- tigrcorn_certification-0.3.16.dist-info/METADATA +299 -0
- tigrcorn_certification-0.3.16.dist-info/RECORD +14 -0
- tigrcorn_certification-0.3.16.dist-info/WHEEL +5 -0
- tigrcorn_certification-0.3.16.dist-info/licenses/LICENSE +163 -0
- tigrcorn_certification-0.3.16.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
PACKAGE_BOUNDARY = "certification"
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"PACKAGE_BOUNDARY",
|
|
7
|
+
"evaluate_release_gates",
|
|
8
|
+
"assert_release_ready",
|
|
9
|
+
"ReleaseGateError",
|
|
10
|
+
"ReleaseGateReport",
|
|
11
|
+
"certification_explicit_surface_catalog",
|
|
12
|
+
"certification_explicit_surface_ids",
|
|
13
|
+
"validate_explicit_surface_manifest",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def __getattr__(name: str):
|
|
18
|
+
if name in {
|
|
19
|
+
"evaluate_release_gates",
|
|
20
|
+
"assert_release_ready",
|
|
21
|
+
"ReleaseGateError",
|
|
22
|
+
"ReleaseGateReport",
|
|
23
|
+
}:
|
|
24
|
+
from .release_gates import (
|
|
25
|
+
ReleaseGateError,
|
|
26
|
+
ReleaseGateReport,
|
|
27
|
+
assert_release_ready,
|
|
28
|
+
evaluate_release_gates,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
mapping = {
|
|
32
|
+
"evaluate_release_gates": evaluate_release_gates,
|
|
33
|
+
"assert_release_ready": assert_release_ready,
|
|
34
|
+
"ReleaseGateError": ReleaseGateError,
|
|
35
|
+
"ReleaseGateReport": ReleaseGateReport,
|
|
36
|
+
}
|
|
37
|
+
return mapping[name]
|
|
38
|
+
if name in {
|
|
39
|
+
"certification_explicit_surface_catalog",
|
|
40
|
+
"certification_explicit_surface_ids",
|
|
41
|
+
"validate_explicit_surface_manifest",
|
|
42
|
+
}:
|
|
43
|
+
from .explicit_surfaces import (
|
|
44
|
+
certification_explicit_surface_catalog,
|
|
45
|
+
certification_explicit_surface_ids,
|
|
46
|
+
validate_explicit_surface_manifest,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
mapping = {
|
|
50
|
+
"certification_explicit_surface_catalog": certification_explicit_surface_catalog,
|
|
51
|
+
"certification_explicit_surface_ids": certification_explicit_surface_ids,
|
|
52
|
+
"validate_explicit_surface_manifest": validate_explicit_surface_manifest,
|
|
53
|
+
}
|
|
54
|
+
return mapping[name]
|
|
55
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Iterable, Mapping, Sequence
|
|
11
|
+
|
|
12
|
+
from .interop_runner import run_external_matrix
|
|
13
|
+
from .release_gates import evaluate_promotion_target, evaluate_release_gates
|
|
14
|
+
|
|
15
|
+
DEFAULT_PRELIGHT_SCENARIOS: tuple[str, ...] = (
|
|
16
|
+
'http3-server-aioquic-client-post',
|
|
17
|
+
'websocket-http3-server-aioquic-client',
|
|
18
|
+
)
|
|
19
|
+
DEFAULT_BUNDLE_NAME = 'tigrcorn-aioquic-adapter-preflight-bundle'
|
|
20
|
+
DEFAULT_STATUS_DOC = 'docs/review/conformance/AIOQUIC_ADAPTER_PREFLIGHT.md'
|
|
21
|
+
DEFAULT_STATUS_JSON = 'docs/review/conformance/aioquic_adapter_preflight.current.json'
|
|
22
|
+
DEFAULT_DELIVERY_NOTES = 'docs/review/conformance/delivery/DELIVERY_NOTES_AIOQUIC_ADAPTER_PREFLIGHT.md'
|
|
23
|
+
DEFAULT_MATRIX_PATH = 'docs/review/conformance/external_matrix.release.json'
|
|
24
|
+
DEFAULT_RELEASE_ROOT = 'docs/review/conformance/releases/0.3.9/release-0.3.9'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AioquicAdapterPreflightError(RuntimeError):
|
|
28
|
+
"""Raised when the aioquic adapter preflight fails and strict pass mode is enabled."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _now() -> str:
|
|
32
|
+
return datetime.now(timezone.utc).isoformat()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_json(path: Path) -> dict[str, Any]:
|
|
36
|
+
return json.loads(path.read_text(encoding='utf-8'))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _dump_json(path: Path, payload: Any) -> None:
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + '\n', encoding='utf-8')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _module_version(name: str) -> str | None:
|
|
45
|
+
try:
|
|
46
|
+
return importlib.metadata.version(name)
|
|
47
|
+
except importlib.metadata.PackageNotFoundError:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _command_option(command: Sequence[str], option: str) -> str | None:
|
|
52
|
+
try:
|
|
53
|
+
index = list(command).index(option)
|
|
54
|
+
except ValueError:
|
|
55
|
+
return None
|
|
56
|
+
next_index = index + 1
|
|
57
|
+
if next_index >= len(command):
|
|
58
|
+
return None
|
|
59
|
+
return str(command[next_index])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _module_name(command: Sequence[str]) -> str | None:
|
|
63
|
+
try:
|
|
64
|
+
index = list(command).index('-m')
|
|
65
|
+
except ValueError:
|
|
66
|
+
return None
|
|
67
|
+
next_index = index + 1
|
|
68
|
+
if next_index >= len(command):
|
|
69
|
+
return None
|
|
70
|
+
return str(command[next_index])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _path_ready(entry: Mapping[str, Any] | None) -> bool:
|
|
74
|
+
if not isinstance(entry, Mapping):
|
|
75
|
+
return False
|
|
76
|
+
return bool(entry.get('exists')) and bool(entry.get('is_file'))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _default_certificate_inputs(repo_root: Path, peer_command: Sequence[str]) -> dict[str, Any]:
|
|
80
|
+
def _entry(option: str) -> dict[str, Any]:
|
|
81
|
+
value = _command_option(peer_command, option)
|
|
82
|
+
if value is None:
|
|
83
|
+
return {'path': None, 'exists': False, 'is_file': False}
|
|
84
|
+
candidate = repo_root / value
|
|
85
|
+
return {
|
|
86
|
+
'path': value,
|
|
87
|
+
'exists': candidate.exists(),
|
|
88
|
+
'is_file': candidate.is_file(),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ca = _entry('--cacert')
|
|
92
|
+
cert = _entry('--client-cert')
|
|
93
|
+
key = _entry('--client-key')
|
|
94
|
+
client_material_requested = bool(cert['path'] or key['path'])
|
|
95
|
+
client_material_ready = (not client_material_requested) or (bool(cert['exists']) and bool(key['exists']))
|
|
96
|
+
return {
|
|
97
|
+
'ca_cert': ca,
|
|
98
|
+
'client_cert': cert,
|
|
99
|
+
'client_key': key,
|
|
100
|
+
'client_material_requested': client_material_requested,
|
|
101
|
+
'client_material_ready': client_material_ready,
|
|
102
|
+
'ready': bool(ca['exists']) and client_material_ready,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _scenario_kind(scenario_id: str) -> str:
|
|
107
|
+
if 'websocket' in scenario_id:
|
|
108
|
+
return 'http3_websocket_adapter'
|
|
109
|
+
return 'http3_client_adapter'
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _extract_scenario_record(repo_root: Path, bundle_root: Path, scenario_id: str) -> dict[str, Any]:
|
|
113
|
+
scenario_root = bundle_root / scenario_id
|
|
114
|
+
result = _load_json(scenario_root / 'result.json')
|
|
115
|
+
commands = _load_json(scenario_root / 'command.json')
|
|
116
|
+
versions = _load_json(scenario_root / 'versions.json')
|
|
117
|
+
peer_command = [str(item) for item in commands['peer']['command']]
|
|
118
|
+
negotiation = dict((result.get('negotiation') or {}).get('peer') or {})
|
|
119
|
+
transcript = dict((result.get('transcript') or {}).get('peer') or {})
|
|
120
|
+
transcript_quic = dict(transcript.get('quic') or {})
|
|
121
|
+
certificate_inputs = dict(negotiation.get('certificate_inputs') or _default_certificate_inputs(repo_root, peer_command))
|
|
122
|
+
handshake_complete = bool(
|
|
123
|
+
negotiation.get('handshake_complete')
|
|
124
|
+
or transcript_quic.get('handshake_complete')
|
|
125
|
+
or (result.get('passed') and (result.get('peer') or {}).get('exit_code') == 0 and negotiation.get('protocol') == 'h3')
|
|
126
|
+
)
|
|
127
|
+
artifacts = result.get('artifacts') or {}
|
|
128
|
+
response = dict(transcript.get('response') or {})
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
'scenario_id': scenario_id,
|
|
132
|
+
'kind': _scenario_kind(scenario_id),
|
|
133
|
+
'passed': bool(result.get('passed')),
|
|
134
|
+
'peer_exit_code': int((result.get('peer') or {}).get('exit_code') or 0),
|
|
135
|
+
'peer_module': _module_name(peer_command),
|
|
136
|
+
'peer_command': peer_command,
|
|
137
|
+
'peer_version': (versions.get('peer') or {}).get('implementation_version'),
|
|
138
|
+
'protocol': negotiation.get('protocol'),
|
|
139
|
+
'tls_version': negotiation.get('tls_version'),
|
|
140
|
+
'server_name': negotiation.get('server_name'),
|
|
141
|
+
'handshake_complete': handshake_complete,
|
|
142
|
+
'retry_observed': bool(negotiation.get('retry_observed')),
|
|
143
|
+
'negotiation_metadata_emitted': bool((artifacts.get('peer_negotiation') or {}).get('exists')),
|
|
144
|
+
'transcript_emitted': bool((artifacts.get('peer_transcript') or {}).get('exists')),
|
|
145
|
+
'packet_trace_exists': bool((artifacts.get('packet_trace') or {}).get('exists')),
|
|
146
|
+
'qlog_exists': bool((artifacts.get('qlog') or {}).get('exists')),
|
|
147
|
+
'certificate_inputs': certificate_inputs,
|
|
148
|
+
'certificate_inputs_ready': bool(negotiation.get('certificate_inputs_ready', certificate_inputs.get('ready'))),
|
|
149
|
+
'ca_cert_path': (certificate_inputs.get('ca_cert') or {}).get('path'),
|
|
150
|
+
'ca_cert_exists': _path_ready(certificate_inputs.get('ca_cert') or {}),
|
|
151
|
+
'client_material_requested': bool(certificate_inputs.get('client_material_requested')),
|
|
152
|
+
'response_status': response.get('status'),
|
|
153
|
+
'websocket_connect_protocol_enabled': negotiation.get('connect_protocol_enabled'),
|
|
154
|
+
'websocket_negotiated_extensions': list(negotiation.get('negotiated_extensions') or []),
|
|
155
|
+
'artifact_dir': str(scenario_root.relative_to(bundle_root)),
|
|
156
|
+
'result_path': str((scenario_root / 'result.json').relative_to(bundle_root)),
|
|
157
|
+
'peer_negotiation_path': str((scenario_root / 'peer_negotiation.json').relative_to(bundle_root)),
|
|
158
|
+
'peer_transcript_path': str((scenario_root / 'peer_transcript.json').relative_to(bundle_root)),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _bundle_manifest(*, artifact_root: str, matrix_path: str, scenario_ids: Sequence[str]) -> dict[str, Any]:
|
|
163
|
+
return {
|
|
164
|
+
'bundle_kind': 'aioquic_adapter_preflight_bundle',
|
|
165
|
+
'generated_at': _now(),
|
|
166
|
+
'release_gate_eligible': False,
|
|
167
|
+
'artifact_root': artifact_root,
|
|
168
|
+
'matrix_path': matrix_path,
|
|
169
|
+
'scenario_ids': list(scenario_ids),
|
|
170
|
+
'note': 'This bundle proves the third-party aioquic HTTP/3 adapters can execute cleanly before strict-target checkpoint promotion work continues.',
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _bundle_index(*, artifact_root: str, matrix_path: str, scenario_records: Sequence[Mapping[str, Any]], environment: Mapping[str, Any], gate_status: Mapping[str, Any]) -> dict[str, Any]:
|
|
175
|
+
return {
|
|
176
|
+
'artifact_root': artifact_root,
|
|
177
|
+
'bundle_kind': 'aioquic_adapter_preflight_bundle',
|
|
178
|
+
'generated_at': _now(),
|
|
179
|
+
'matrix_path': matrix_path,
|
|
180
|
+
'scenario_count': len(scenario_records),
|
|
181
|
+
'scenario_ids': [str(item['scenario_id']) for item in scenario_records],
|
|
182
|
+
'all_adapters_passed': all(bool(item['passed']) for item in scenario_records),
|
|
183
|
+
'no_peer_exit_code_2': all(int(item['peer_exit_code']) != 2 for item in scenario_records),
|
|
184
|
+
'negotiation_metadata_emitted': all(bool(item['negotiation_metadata_emitted']) for item in scenario_records),
|
|
185
|
+
'transcript_metadata_emitted': all(bool(item['transcript_emitted']) for item in scenario_records),
|
|
186
|
+
'all_protocols_h3': all(item.get('protocol') == 'h3' for item in scenario_records),
|
|
187
|
+
'all_handshakes_complete': all(bool(item['handshake_complete']) for item in scenario_records),
|
|
188
|
+
'certificate_inputs_ready': all(bool(item['certificate_inputs_ready']) for item in scenario_records),
|
|
189
|
+
'packet_traces_emitted': all(bool(item['packet_trace_exists']) for item in scenario_records),
|
|
190
|
+
'qlogs_emitted': all(bool(item['qlog_exists']) for item in scenario_records),
|
|
191
|
+
'environment': dict(environment),
|
|
192
|
+
'gate_status_after_preflight': dict(gate_status),
|
|
193
|
+
'release_gate_eligible': False,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _bundle_summary(index: Mapping[str, Any]) -> dict[str, Any]:
|
|
198
|
+
return {
|
|
199
|
+
'artifact_root': index['artifact_root'],
|
|
200
|
+
'bundle_kind': index['bundle_kind'],
|
|
201
|
+
'generated_at': index['generated_at'],
|
|
202
|
+
'scenario_count': index['scenario_count'],
|
|
203
|
+
'all_adapters_passed': index['all_adapters_passed'],
|
|
204
|
+
'no_peer_exit_code_2': index['no_peer_exit_code_2'],
|
|
205
|
+
'all_protocols_h3': index['all_protocols_h3'],
|
|
206
|
+
'all_handshakes_complete': index['all_handshakes_complete'],
|
|
207
|
+
'certificate_inputs_ready': index['certificate_inputs_ready'],
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _bundle_readme(index: Mapping[str, Any], scenario_records: Sequence[Mapping[str, Any]]) -> str:
|
|
212
|
+
lines = [
|
|
213
|
+
'# aioquic adapter preflight bundle',
|
|
214
|
+
'',
|
|
215
|
+
'This bundle preserves the direct third-party aioquic HTTP/3 adapter preflight runs used before strict-target certification checkpoints.',
|
|
216
|
+
'',
|
|
217
|
+
'## Exit-criteria status',
|
|
218
|
+
'',
|
|
219
|
+
f"- all adapters passed: `{index['all_adapters_passed']}`",
|
|
220
|
+
f"- no peer exit code 2: `{index['no_peer_exit_code_2']}`",
|
|
221
|
+
f"- negotiation metadata emitted: `{index['negotiation_metadata_emitted']}`",
|
|
222
|
+
f"- transcript metadata emitted: `{index['transcript_metadata_emitted']}`",
|
|
223
|
+
f"- ALPN h3 observed for every run: `{index['all_protocols_h3']}`",
|
|
224
|
+
f"- QUIC handshakes complete: `{index['all_handshakes_complete']}`",
|
|
225
|
+
f"- certificate inputs ready: `{index['certificate_inputs_ready']}`",
|
|
226
|
+
'',
|
|
227
|
+
'## Scenarios',
|
|
228
|
+
'',
|
|
229
|
+
]
|
|
230
|
+
for item in scenario_records:
|
|
231
|
+
lines.extend([
|
|
232
|
+
f"- `{item['scenario_id']}` → passed=`{item['passed']}`, peer_exit=`{item['peer_exit_code']}`, protocol=`{item['protocol']}`, handshake_complete=`{item['handshake_complete']}`",
|
|
233
|
+
])
|
|
234
|
+
lines.append('')
|
|
235
|
+
return '\n'.join(lines) + '\n'
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _status_markdown(snapshot: Mapping[str, Any], *, release_root: str, bundle_root: str) -> str:
|
|
239
|
+
current = snapshot['current_state']
|
|
240
|
+
scenario_records = current['scenario_records']
|
|
241
|
+
lines = [
|
|
242
|
+
'# aioquic adapter preflight',
|
|
243
|
+
'',
|
|
244
|
+
'This checkpoint executes the third-party aioquic HTTP/3 adapters directly before any strict-target artifact-promotion work proceeds.',
|
|
245
|
+
'',
|
|
246
|
+
'## Exit criteria',
|
|
247
|
+
'',
|
|
248
|
+
f"- both adapters passed: `{current['all_adapters_passed']}`",
|
|
249
|
+
f"- no peer exit code 2: `{current['no_peer_exit_code_2']}`",
|
|
250
|
+
f"- negotiation metadata emitted: `{current['negotiation_metadata_emitted']}`",
|
|
251
|
+
f"- transcript metadata emitted: `{current['transcript_metadata_emitted']}`",
|
|
252
|
+
f"- ALPN h3 observed: `{current['all_protocols_h3']}`",
|
|
253
|
+
f"- QUIC handshakes complete: `{current['all_handshakes_complete']}`",
|
|
254
|
+
f"- certificate inputs ready: `{current['certificate_inputs_ready']}`",
|
|
255
|
+
'',
|
|
256
|
+
'## Environment snapshot',
|
|
257
|
+
'',
|
|
258
|
+
f"- python version: `{current['environment']['python_version']}`",
|
|
259
|
+
f"- python minor version: `{current['environment']['python_minor_version']}`",
|
|
260
|
+
f"- aioquic version: `{current['environment']['aioquic_version']}`",
|
|
261
|
+
f"- wsproto version: `{current['environment']['wsproto_version']}`",
|
|
262
|
+
f"- h2 version: `{current['environment']['h2_version']}`",
|
|
263
|
+
f"- websockets version: `{current['environment']['websockets_version']}`",
|
|
264
|
+
f"- release root: `{release_root}`",
|
|
265
|
+
f"- preflight bundle root: `{bundle_root}`",
|
|
266
|
+
'',
|
|
267
|
+
'## Scenario results',
|
|
268
|
+
'',
|
|
269
|
+
]
|
|
270
|
+
for item in scenario_records:
|
|
271
|
+
lines.extend([
|
|
272
|
+
f"### `{item['scenario_id']}`",
|
|
273
|
+
'',
|
|
274
|
+
f"- kind: `{item['kind']}`",
|
|
275
|
+
f"- adapter module: `{item['peer_module']}`",
|
|
276
|
+
f"- peer exit code: `{item['peer_exit_code']}`",
|
|
277
|
+
f"- protocol: `{item['protocol']}`",
|
|
278
|
+
f"- tls version: `{item['tls_version']}`",
|
|
279
|
+
f"- server name: `{item['server_name']}`",
|
|
280
|
+
f"- handshake complete: `{item['handshake_complete']}`",
|
|
281
|
+
f"- ca cert path: `{item['ca_cert_path']}`",
|
|
282
|
+
f"- ca cert exists: `{item['ca_cert_exists']}`",
|
|
283
|
+
f"- certificate inputs ready: `{item['certificate_inputs_ready']}`",
|
|
284
|
+
f"- packet trace emitted: `{item['packet_trace_exists']}`",
|
|
285
|
+
f"- qlog emitted: `{item['qlog_exists']}`",
|
|
286
|
+
f"- peer negotiation metadata: `{item['peer_negotiation_path']}`",
|
|
287
|
+
f"- peer transcript metadata: `{item['peer_transcript_path']}`",
|
|
288
|
+
'',
|
|
289
|
+
])
|
|
290
|
+
lines.extend([
|
|
291
|
+
'## Honest current repository state',
|
|
292
|
+
'',
|
|
293
|
+
f"- authoritative boundary after preflight: `{current['gate_status_after_preflight']['authoritative_boundary_passed']}`",
|
|
294
|
+
f"- strict target after preflight: `{current['gate_status_after_preflight']['strict_target_boundary_passed']}`",
|
|
295
|
+
f"- promotion target after preflight: `{current['gate_status_after_preflight']['promotion_target_passed']}`",
|
|
296
|
+
'',
|
|
297
|
+
'This preflight closes the adapter-execution ambiguity: the aioquic HTTP/3 client and aioquic RFC 9220 WebSocket client both ran successfully and emitted negotiation metadata. It does **not** by itself promote the remaining strict-target HTTP/3 scenario artifacts into the 0.3.9 release root, so the package may still remain non-green under the stricter target until those artifacts are regenerated and assembled.',
|
|
298
|
+
'',
|
|
299
|
+
])
|
|
300
|
+
return '\n'.join(lines)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _delivery_notes(snapshot: Mapping[str, Any], *, release_root: str, bundle_root: str) -> str:
|
|
304
|
+
current = snapshot['current_state']
|
|
305
|
+
return (
|
|
306
|
+
'# Delivery notes — aioquic adapter preflight\n\n'
|
|
307
|
+
'This checkpoint adds a direct aioquic adapter preflight on top of the existing Phase 9I release-assembly repository.\n\n'
|
|
308
|
+
'What changed:\n\n'
|
|
309
|
+
'- added a reusable aioquic preflight module at `src/tigrcorn/compat/aioquic_preflight.py`\n'
|
|
310
|
+
'- added a runnable checkpoint tool at `tools/preflight_aioquic_adapters.py`\n'
|
|
311
|
+
'- added a preserved preflight bundle under the 0.3.9 working release root\n'
|
|
312
|
+
'- updated the release workflow and local wrapper so aioquic adapter preflight is now mandatory before Phase 9 checkpoint scripts run\n'
|
|
313
|
+
'- updated current-state documentation\n\n'
|
|
314
|
+
'Current result:\n\n'
|
|
315
|
+
f"- preflight bundle root: `{bundle_root}`\n"
|
|
316
|
+
f"- all adapters passed: `{current['all_adapters_passed']}`\n"
|
|
317
|
+
f"- no peer exit code 2: `{current['no_peer_exit_code_2']}`\n"
|
|
318
|
+
f"- strict target after preflight: `{current['gate_status_after_preflight']['strict_target_boundary_passed']}`\n"
|
|
319
|
+
f"- promotion target after preflight: `{current['gate_status_after_preflight']['promotion_target_passed']}`\n\n"
|
|
320
|
+
'This checkpoint proves the third-party aioquic adapter execution path is healthy in the observed environment. It does not by itself claim that the package is already strict-target green or promotable.\n'
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def run_aioquic_adapter_preflight(
|
|
325
|
+
root: str | Path,
|
|
326
|
+
*,
|
|
327
|
+
release_root: str = DEFAULT_RELEASE_ROOT,
|
|
328
|
+
bundle_name: str = DEFAULT_BUNDLE_NAME,
|
|
329
|
+
matrix_path: str = DEFAULT_MATRIX_PATH,
|
|
330
|
+
scenario_ids: Sequence[str] = DEFAULT_PRELIGHT_SCENARIOS,
|
|
331
|
+
bundle_root: str | Path | None = None,
|
|
332
|
+
require_pass: bool = False,
|
|
333
|
+
) -> dict[str, Any]:
|
|
334
|
+
repo_root = Path(root)
|
|
335
|
+
resolved_release_root = repo_root / release_root
|
|
336
|
+
target_bundle_root = Path(bundle_root) if bundle_root is not None else resolved_release_root / bundle_name
|
|
337
|
+
if target_bundle_root.exists():
|
|
338
|
+
shutil.rmtree(target_bundle_root)
|
|
339
|
+
target_bundle_root.mkdir(parents=True, exist_ok=True)
|
|
340
|
+
|
|
341
|
+
with tempfile.TemporaryDirectory(prefix='aioquic-preflight-') as tmpdir:
|
|
342
|
+
summary = run_external_matrix(
|
|
343
|
+
repo_root / matrix_path,
|
|
344
|
+
artifact_root=tmpdir,
|
|
345
|
+
source_root=repo_root,
|
|
346
|
+
scenario_ids=list(scenario_ids),
|
|
347
|
+
strict=True,
|
|
348
|
+
)
|
|
349
|
+
generated_root = Path(summary.artifact_root)
|
|
350
|
+
for source_name, target_name in (
|
|
351
|
+
('manifest.json', 'generated_matrix_manifest.json'),
|
|
352
|
+
('index.json', 'generated_matrix_index.json'),
|
|
353
|
+
('summary.json', 'generated_matrix_summary.json'),
|
|
354
|
+
):
|
|
355
|
+
shutil.copy2(generated_root / source_name, target_bundle_root / target_name)
|
|
356
|
+
for scenario_id in scenario_ids:
|
|
357
|
+
shutil.copytree(generated_root / scenario_id, target_bundle_root / scenario_id)
|
|
358
|
+
|
|
359
|
+
environment = {
|
|
360
|
+
'python_version': sys.version,
|
|
361
|
+
'python_minor_version': f'{sys.version_info.major}.{sys.version_info.minor}',
|
|
362
|
+
'aioquic_version': _module_version('aioquic'),
|
|
363
|
+
'wsproto_version': _module_version('wsproto'),
|
|
364
|
+
'h2_version': _module_version('h2'),
|
|
365
|
+
'websockets_version': _module_version('websockets'),
|
|
366
|
+
}
|
|
367
|
+
gate_status = {
|
|
368
|
+
'authoritative_boundary_passed': evaluate_release_gates(repo_root).passed,
|
|
369
|
+
'strict_target_boundary_passed': evaluate_release_gates(repo_root, boundary_path='docs/review/conformance/certification_boundary.strict_target.json').passed,
|
|
370
|
+
'promotion_target_passed': evaluate_promotion_target(repo_root).passed,
|
|
371
|
+
}
|
|
372
|
+
scenario_records = [_extract_scenario_record(repo_root, target_bundle_root, scenario_id) for scenario_id in scenario_ids]
|
|
373
|
+
index = _bundle_index(
|
|
374
|
+
artifact_root=str(target_bundle_root.relative_to(repo_root)) if target_bundle_root.is_relative_to(repo_root) else str(target_bundle_root),
|
|
375
|
+
matrix_path=matrix_path,
|
|
376
|
+
scenario_records=scenario_records,
|
|
377
|
+
environment=environment,
|
|
378
|
+
gate_status=gate_status,
|
|
379
|
+
)
|
|
380
|
+
manifest = _bundle_manifest(
|
|
381
|
+
artifact_root=index['artifact_root'],
|
|
382
|
+
matrix_path=matrix_path,
|
|
383
|
+
scenario_ids=scenario_ids,
|
|
384
|
+
)
|
|
385
|
+
summary = _bundle_summary(index)
|
|
386
|
+
_dump_json(target_bundle_root / 'manifest.json', manifest)
|
|
387
|
+
_dump_json(target_bundle_root / 'index.json', index)
|
|
388
|
+
_dump_json(target_bundle_root / 'summary.json', summary)
|
|
389
|
+
_dump_json(target_bundle_root / 'preflight.json', {
|
|
390
|
+
'generated_at': _now(),
|
|
391
|
+
'environment': environment,
|
|
392
|
+
'gate_status_after_preflight': gate_status,
|
|
393
|
+
'scenario_records': scenario_records,
|
|
394
|
+
})
|
|
395
|
+
(target_bundle_root / 'README.md').write_text(_bundle_readme(index, scenario_records), encoding='utf-8')
|
|
396
|
+
|
|
397
|
+
snapshot = {
|
|
398
|
+
'checkpoint': 'aioquic_adapter_preflight',
|
|
399
|
+
'status': 'aioquic_adapter_preflight_passed' if summary['all_adapters_passed'] else 'aioquic_adapter_preflight_failed',
|
|
400
|
+
'current_state': {
|
|
401
|
+
'release_root': release_root,
|
|
402
|
+
'bundle_root': index['artifact_root'],
|
|
403
|
+
'matrix_path': matrix_path,
|
|
404
|
+
'scenario_ids': list(scenario_ids),
|
|
405
|
+
'scenario_records': scenario_records,
|
|
406
|
+
'environment': environment,
|
|
407
|
+
'all_adapters_passed': index['all_adapters_passed'],
|
|
408
|
+
'no_peer_exit_code_2': index['no_peer_exit_code_2'],
|
|
409
|
+
'negotiation_metadata_emitted': index['negotiation_metadata_emitted'],
|
|
410
|
+
'transcript_metadata_emitted': index['transcript_metadata_emitted'],
|
|
411
|
+
'all_protocols_h3': index['all_protocols_h3'],
|
|
412
|
+
'all_handshakes_complete': index['all_handshakes_complete'],
|
|
413
|
+
'certificate_inputs_ready': index['certificate_inputs_ready'],
|
|
414
|
+
'packet_traces_emitted': index['packet_traces_emitted'],
|
|
415
|
+
'qlogs_emitted': index['qlogs_emitted'],
|
|
416
|
+
'gate_status_after_preflight': gate_status,
|
|
417
|
+
},
|
|
418
|
+
'remaining_strict_target_blockers': [
|
|
419
|
+
'websocket-http3-server-aioquic-client-permessage-deflate',
|
|
420
|
+
'http3-connect-relay-aioquic-client',
|
|
421
|
+
'http3-trailer-fields-aioquic-client',
|
|
422
|
+
'http3-content-coding-aioquic-client',
|
|
423
|
+
],
|
|
424
|
+
}
|
|
425
|
+
if require_pass and not summary['all_adapters_passed']:
|
|
426
|
+
raise AioquicAdapterPreflightError('one or more aioquic adapter preflight scenarios failed')
|
|
427
|
+
return snapshot
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def write_status_documents(
|
|
431
|
+
root: str | Path,
|
|
432
|
+
snapshot: Mapping[str, Any],
|
|
433
|
+
*,
|
|
434
|
+
release_root: str = DEFAULT_RELEASE_ROOT,
|
|
435
|
+
bundle_root: str = DEFAULT_BUNDLE_NAME,
|
|
436
|
+
status_doc: str = DEFAULT_STATUS_DOC,
|
|
437
|
+
status_json: str = DEFAULT_STATUS_JSON,
|
|
438
|
+
delivery_notes: str = DEFAULT_DELIVERY_NOTES,
|
|
439
|
+
) -> None:
|
|
440
|
+
repo_root = Path(root)
|
|
441
|
+
_dump_json(repo_root / status_json, snapshot)
|
|
442
|
+
(repo_root / status_doc).write_text(
|
|
443
|
+
_status_markdown(snapshot, release_root=release_root, bundle_root=bundle_root),
|
|
444
|
+
encoding='utf-8',
|
|
445
|
+
)
|
|
446
|
+
(repo_root / delivery_notes).write_text(
|
|
447
|
+
_delivery_notes(snapshot, release_root=release_root, bundle_root=bundle_root),
|
|
448
|
+
encoding='utf-8',
|
|
449
|
+
)
|