Applies the gen2 desktop-PTT review summary
(`gen2/chanora-desktop-ptt-review-summary-v0.9.2.md`) to our doc set
with the owner rulings PTT-OPEN-001 through PTT-OPEN-006 resolved as
accepted decisions DEC-023 through DEC-028:
* DEC-023 Windows Global PTT P0 / MVP
* DEC-024 macOS Global PTT P0 / MVP with permission UX
* DEC-025 Linux officially-tested env: GNOME on Wayland only
* DEC-026 Mouse side buttons supported (Win + macOS; Linux portal)
* DEC-027 PTT diagnostics: capability + availability only, no
raw key codes ever
* DEC-028 Missed-key-up watchdog: P0
Requirements (SysRS / SRS) and architecture (SysDes / SAD / SDD)
gain the desktop-PTT ID set the gen2 summary describes:
SysRS-296..302 -> SysDes-142..148
-> SRS-195..203
-> SAD-071..079
-> SDD-081..092
ID totals advance from 295 / 141 / 194 / 70 / 80 to 302 / 148 / 203
/ 79 / 92. The strict layered sourcing rule (`SRS -> SysDes` only,
`SAD -> SRS` only, `SDD -> SAD` only) is preserved; the
`tools/validate_docs.py` validator reports zero undefined refs and
zero direct-layer-rule violations.
New document:
* `docs/architecture/desktop-ptt-architecture.md` — capability
ladder (L0Focused, L1GlobalShortcut, L2GlobalHoldToTalk,
L3GlobalWithMouseButtons, L4DeviceAware reserved), Windows /
macOS / Linux strategies, privacy rule, audio-gate rule,
missed-key-up watchdog, release-readiness evidence requirement,
traceability summary.
Doc addenda (Baseline Candidate 0.9.3):
* `privacy/privacy-policy.md` — no raw key history, capability-
dependent Global PTT, UI reflects actual runtime capability
* `security/threat-model.md` — THREAT-PTT-001..006
* `security/diagnostic-redaction-audit-report.md` —
REDACT-PTT-001..006 banned field list enforced by `PttSanitizer`
* `release/platform-release-policy.md` — per-platform evidence
fields, no over-claim on untested Linux compositors
* `release/release-readiness-go-nogo-record.md` — RR-PTT-001..008
release-readiness items
* `verification/swe4-unit-verification-plan.md` —
SWE4-UV-035..039
* `verification/swe5-software-integration-verification-plan.md` —
SWE5-IV-015
* `verification/swe6-software-verification-plan.md` — SWE6-SV-017
* `verification/sys4-system-integration-verification-plan.md` —
SYS4-SIV-016
* `governance/traceability-matrix.md` — full PTT trace rows +
verification map
* `governance/decision-impact-assessment.md` — DEC-023..028
impact matrix
* `governance/product-decision-register.md` v0.9.9 entry
recording DEC-023..028 in the decision table and the status
table at §7
* `governance/document-index.md` — adds
`desktop-ptt-architecture.md` to the controlled set
* `architecture/proof-of-concept-plan.md` —
PoC-PTT-001..005 platform items
* `references/external-references.md` — Windows Raw Input,
macOS event-tap, Linux GlobalShortcuts portal references
* Both validation reports
(`baseline-candidate-validation-report.md`,
`repo-format-validation-report.md`) bumped to v0.9.3 with the
new ID totals (302 / 148 / 203 / 79 / 92).
README §"Desktop Push-to-Talk" added between Architecture Overview
and Repository Layout: capability levels, per-platform strategy,
privacy posture, missed-key-up watchdog.
Tooling:
* `tools/validate_docs.py` copied from the gen2 zip into the
repo tree (was previously available only inside the zip).
Reports zero undefined refs, zero direct-layer-rule violations,
English-only CJK check passes. The 35 "old package-style
filename" hits are pre-existing and identical to the gen2
baseline (they live in `path-migration-map.md` and config-ID
headers of governance docs and are intentional per the path
migration policy).
* `.gitignore` adds `/gen2/` so the externally-provided review
package does not enter the repo.
No code changes in this commit; B (the implementation split into
`transmit_active` / `capture_active`, `PttCapabilityLevel`
reporting, `PttSanitizer` diagnostics rule, and the UI capability
badge) follows in a separate commit.
91 lines
2.8 KiB
Python
Executable File
91 lines
2.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DOCS = ROOT / "docs"
|
|
|
|
ID_FILES = {
|
|
"SysRS": DOCS / "requirements" / "sysrs.md",
|
|
"SysDes": DOCS / "architecture" / "sysdes.md",
|
|
"SRS": DOCS / "requirements" / "srs.md",
|
|
"SAD": DOCS / "architecture" / "sad.md",
|
|
"SDD": DOCS / "architecture" / "sdd.md",
|
|
}
|
|
|
|
def read(path: Path) -> str:
|
|
if not path.exists():
|
|
return ""
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
def defined_ids(prefix: str, text: str) -> set[str]:
|
|
return set(re.findall(rf"\*\*({prefix}-\d{{3}})\*\*:", text))
|
|
|
|
def all_refs(prefix: str, text: str) -> set[str]:
|
|
return set(re.findall(rf"{prefix}-\d{{3}}", text))
|
|
|
|
def main() -> int:
|
|
if not DOCS.exists():
|
|
print("docs/ does not exist; skipping documentation validation.")
|
|
return 0
|
|
|
|
all_text = "\n".join(p.read_text(encoding="utf-8") for p in DOCS.rglob("*.md"))
|
|
|
|
defined = {
|
|
prefix: defined_ids(prefix, read(path))
|
|
for prefix, path in ID_FILES.items()
|
|
}
|
|
|
|
failed = False
|
|
|
|
for prefix, ids in defined.items():
|
|
refs = all_refs(prefix, all_text)
|
|
undefined = sorted(refs - ids)
|
|
if undefined:
|
|
failed = True
|
|
print(f"[FAIL] Undefined {prefix} references: {', '.join(undefined)}")
|
|
else:
|
|
print(f"[OK] {prefix}: {len(ids)} defined, 0 undefined references")
|
|
|
|
srs = read(DOCS / "requirements" / "srs.md")
|
|
sad = read(DOCS / "architecture" / "sad.md")
|
|
sdd = read(DOCS / "architecture" / "sdd.md")
|
|
|
|
checks = [
|
|
("SRS direct SysRS references", re.findall(r"SysRS-\d{3}", srs)),
|
|
("SAD direct SysRS references", re.findall(r"SysRS-\d{3}", sad)),
|
|
("SAD direct SysDes references", re.findall(r"SysDes-\d{3}", sad)),
|
|
("SDD direct SysRS references", re.findall(r"SysRS-\d{3}", sdd)),
|
|
("SDD direct SysDes references", re.findall(r"SysDes-\d{3}", sdd)),
|
|
("SDD direct SRS references", re.findall(r"SRS-\d{3}", sdd)),
|
|
]
|
|
|
|
for name, matches in checks:
|
|
if matches:
|
|
failed = True
|
|
print(f"[FAIL] {name}: {len(matches)}")
|
|
else:
|
|
print(f"[OK] {name}: 0")
|
|
|
|
old_names = re.findall(r"CHANORA_[A-Za-z0-9_]+_v\d+\.\d+(?:\.\d+)?\.md", all_text)
|
|
if old_names:
|
|
failed = True
|
|
print(f"[FAIL] Old package-style filenames found: {len(set(old_names))}")
|
|
else:
|
|
print("[OK] No old package-style filenames found")
|
|
|
|
cjk = re.findall(r"[\u4e00-\u9fff]", all_text)
|
|
if cjk:
|
|
failed = True
|
|
print(f"[FAIL] CJK characters found: {len(cjk)}")
|
|
else:
|
|
print("[OK] English-only CJK check passed")
|
|
|
|
return 1 if failed else 0
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|