docs(sync): record native candidate evidence

This commit is contained in:
fy59 2026-09-17 15:26:14 +02:00
parent 2c5a8be282
commit cc87f26b98
6 changed files with 89 additions and 19 deletions

View file

@ -2,6 +2,12 @@
## Unreleased — 0.1.2
- Closed the native candidate warning gate with checked path and HTTP response
bounds, strict GCC and Clang builds, full Clang ASan/UBSan validation, and a
stable concurrent object-I/O MTP double. Rebuilt the desktop, current Android
and non-installed schema-v17 bridge candidates with recorded digests; no
service, device, user database, signing secret or deployment was touched.
- Normalized the recent synchronization, generation-MTP, Android backup,
Web Dashboard serialization, tests, I/O doubles and packaging code for human
readability. Added scoped C formatting rules and review guidance without

View file

@ -8,6 +8,13 @@ non-installed schema-v17 bridge candidate is reproducibly built and tested.
Physical-device MTP, release signing, real user backup/restore, main integration
and deployment are not complete.
`TRAINLOG_NATIVE_BUILD_AND_CANDIDATE_V1` closes the native warning gate. Strict
GCC 16.2.1 and Clang 22.1.8 builds now pass the same 81-test inventory, and the
Clang ASan/UBSan build passes it with leak detection. Bounded path assembly and
the Web sync accepted response reject insufficient capacity without publishing
partial output. Fresh desktop, current-Android and schema-v17 bridge candidates
were rebuilt from commit `2c5a8be2827e31fc5be2accb65073bfeb79d6068`.
Snapshot date: **2026-09-17**.
`TRAINLOG_CODE_READABILITY_V1` normalizes the recent generation-MTP,

View file

@ -49,6 +49,18 @@ and non-destructive hardware smoke. Drive remains separate and untested here.
This is a non-executed rollout checklist. The development branch implements the
path; the daily installation still uses V3.
The latest software-only candidate set was rebuilt under the private root
`/home/fy59/.cache/trainlog/native-build-candidate-v1.VbFjE9` from source commit
`2c5a8be2827e31fc5be2accb65073bfeb79d6068`. Its desktop inventory records every
packaged file digest plus schemas, protocols, entry points and runtime
dependencies. The current debug APK SHA-256 is
`aca2f499ee9ff53ba5913a76a2a0117274e3e5545967ce361f6f126a6af497e1`; the
non-installed bridge APK SHA-256 is
`0a28d81cbb0818cdf2fee72d5024a4b706fcfda39f1182c682c12f000a026cb4`; and its
synthetic schema-v17 backup SHA-256 is
`06eb3999c6f3ce2fd8f551b38727a0a67fe6b48dde4c3244b3d50357afa7e6a3`.
These are unsigned development evidence, not deployment authorization.
## Compatible set
- desktop binary containing schema v21, the Web API and the generation worker;

View file

@ -1317,12 +1317,14 @@ behavioral assertions. Python syntax/orchestrator tests, Android generation and
backup tests, the I/O-boundary MTP browser proof, JSON/import validators and
the isolated harness cover the other normalized sources.
GCC 16.2.1 now compiles beyond the former `misleading-indentation` failures in
`json_writer.c`, `web_dashboard_json.c`, `web_dashboard.c`,
`dashboard_layout.c`, and `generation_mtp.c`. The separate strict build remains
blocked by distinct pre-existing `format-truncation` diagnostics in the Web
accepted-response buffer and bounded test path buffers. Warning policy remains
unchanged; GCC is not reported as green.
GCC 16.2.1 and Clang 22.1.8 both pass separate strict C17 builds and the full
**81/81** Meson inventory with `werror=true` and warning level 3. The former GCC
`format-truncation` findings are closed by exact checked path construction in
the generation-MTP tests/double and by a production accepted-response
serializer that reports its byte count. Boundary tests cover exact fit,
one-byte insufficiency and multibyte path components; failure leaves no partial
path or response available to a caller. Clang ASan/UBSan passes the same
**81/81** inventory with leak detection.
The readability closeout preserves the **81/81** normal Meson and **81/81**
ASan/UBSan inventories. The isolated Android run reports **220 tests: 215
@ -1330,3 +1332,14 @@ passed, five skipped, zero failures/errors**, and `assembleDebug` succeeds on
JDK 17. The real Firefox/object-I/O-boundary MTP conversation also passes after
formatting. A source comparison against the pre-readability commit confirms
that the scoped test names and assertion counts are unchanged.
The isolated JDK 17 harness reports **220 Android tests: 215 passed, five
skipped, zero failures/errors**. The skips are optional, externally supplied
copy gates: `BridgeBackupMigrationTest.verifiedSchemaSeventeenBridgeBackupMigratesThroughCurrentOwner`,
`RealAndroidV9BodyZonesMigrationTest.realVersionNineCopyMigratesWithoutChangingExistingTables`,
`RealAndroidV14FeedbackRevisionMigrationTest.freshRealCopyMigratesLosslesslyToVersionFifteen`,
and the version-thirteen and version-twelve methods in
`RealAndroidV12MachineExerciseMigrationTest`. They require respectively the
bridge backup input, a real v9 fixture, a real v14 fixture/output directory,
and real v13/v12 fixtures with explicit output directories. Synthetic bridge
and current-schema migration proofs run separately; no user database is read.

View file

@ -7,7 +7,9 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BUILD = ROOT / os.environ.get("TRAINLOG_WEB_E2E_BUILD", "build") / "tui"
GECKO = Path(
os.environ.get("TRAINLOG_GECKODRIVER", "/home/fy59/.cache/trainlog/web-e2e-tools/geckodriver")
os.environ.get(
"TRAINLOG_GECKODRIVER", "/home/fy59/.cache/trainlog/web-e2e-tools/geckodriver"
)
)
@ -40,7 +42,9 @@ class BrowserSyncTest(unittest.TestCase):
transport.mkdir()
owned = root / "owned"
owned.mkdir()
remote = (root / "device/Documents/Trainlog") if mode == "mtp" else transport
remote = (
(root / "device/Documents/Trainlog") if mode == "mtp" else transport
)
remote.mkdir(parents=True, exist_ok=True)
release = root / "release-android"
android_log = (root / "android.log").open("wb")
@ -140,11 +144,15 @@ class BrowserSyncTest(unittest.TestCase):
driver.get(f"http://127.0.0.1:{port}/")
wait = WebDriverWait(driver, 15)
button = wait.until(
lambda d: d.find_element(By.XPATH, "//button[normalize-space()='Synchroniser']")
lambda d: d.find_element(
By.XPATH, "//button[normalize-space()='Synchroniser']"
)
)
wait.until(lambda _: button.is_enabled())
button.click()
state_path = Path(str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json")
state_path = Path(
str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json"
)
observed = await_condition(
lambda: (
json.loads(state_path.read_text())
@ -164,7 +172,10 @@ class BrowserSyncTest(unittest.TestCase):
self.assertEqual(committed["result"], "running")
with sqlite3.connect(xdg / "trainlog" / "trainlog.db") as database:
self.assertEqual(
database.execute("SELECT COUNT(*) FROM execution_drafts").fetchone()[0], 1
database.execute(
"SELECT COUNT(*) FROM execution_drafts"
).fetchone()[0],
1,
)
release.touch()
final = await_condition(
@ -186,16 +197,21 @@ class BrowserSyncTest(unittest.TestCase):
)
summary.click()
wait.until(
lambda d: "Brouillons reçus" in d.page_source and "active" in d.page_source
lambda d: "Brouillons reçus" in d.page_source
and "active" in d.page_source
)
driver.refresh()
wait.until(lambda d: "Synchronisation confirmée" in d.page_source)
state = json.loads(
Path(str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json").read_text()
Path(
str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json"
).read_text()
)
self.assertEqual(state["phase"], "completed")
self.assertEqual(
android.wait(timeout=30), 0, (root / "android.log").read_text(errors="replace")
android.wait(timeout=30),
0,
(root / "android.log").read_text(errors="replace"),
)
finally:
if driver is not None:

View file

@ -139,7 +139,12 @@ def main() -> int:
if args.mtp_adapter is None:
raise RuntimeError("MTP adapter is required")
pump = lambda: run_adapter(
args.mtp_adapter, "pull", args.expected_peer, args.transport_root, deadline, True
args.mtp_adapter,
"pull",
args.expected_peer,
args.transport_root,
deadline,
True,
)
# Android publishes its durable identity only while participating. A
# missing expected advertisement is therefore a bounded wait state;
@ -162,7 +167,9 @@ def main() -> int:
request_path = args.transport_root / "request-v1.json"
publish_json(request_path, request)
if args.mode == "mtp":
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
run_adapter(
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
)
emit(
args.run_id,
"waiting_android_publication",
@ -178,7 +185,9 @@ def main() -> int:
ack_path = args.transport_root / "desktop-consumption-ack-v1.json"
publish_json(ack_path, ack)
if args.mode == "mtp":
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
run_adapter(
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
)
if ack["result"] != "consumed":
raise RuntimeError("desktop rejected Android generation")
emit(
@ -209,8 +218,15 @@ def main() -> int:
}
publish_json(args.transport_root / "desktop-generation-v1.json", ref)
if args.mode == "mtp":
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
emit(args.run_id, "published", outbound_generation_id=outgoing_id, manifest_sha256=digest)
run_adapter(
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
)
emit(
args.run_id,
"published",
outbound_generation_id=outgoing_id,
manifest_sha256=digest,
)
emit(args.run_id, "waiting_acknowledgement", outbound_generation_id=outgoing_id)
android_ack = args.transport_root / "android-consumption-ack-v1.json"
android_ack_value = wait_json(