phase 16 implementation

This commit is contained in:
root
2026-06-25 20:08:39 -04:00
parent 5d017f533a
commit c43620a005
248 changed files with 18458 additions and 90 deletions
+56
View File
@@ -0,0 +1,56 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_14_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([
'PHASE_14_PACKAGE_MANIFEST_v1.0.json',
'package-lock.json',
'tsconfig.tsbuildinfo',
]);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 14,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 14 manifest for ${entries.length} files.`);
+58
View File
@@ -0,0 +1,58 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_15_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
'__pycache__',
]);
const excludedFiles = new Set([
'PHASE_15_PACKAGE_MANIFEST_v1.0.json',
'package-lock.json',
'tsconfig.tsbuildinfo',
'docs/quality/phase15-command-logs/12-phase15-package-manifest.log',
]);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 15,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 15 manifest for ${entries.length} files.`);
+621
View File
@@ -0,0 +1,621 @@
#!/usr/bin/env python3
from __future__ import annotations
import csv
import json
import platform
import subprocess
from collections import Counter
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'docs' / 'phase15'
OUT.mkdir(parents=True, exist_ok=True)
DATE = '2026-06-25'
def write(name: str, text: str):
(OUT / name).write_text(text.rstrip() + '\n', encoding='utf-8')
def report(title: str, status: str, summary: str, executed: list[str], blocked: list[str], disposition: str, extra: str = '') -> str:
executed_text = '\n'.join(f'- {item}' for item in executed) if executed else '- None.'
blocked_text = '\n'.join(f'- {item}' for item in blocked) if blocked else '- None.'
return f'''# {title}
**Date:** {DATE}
**Status:** {status}
## Summary
{summary}
## Executed evidence
{executed_text}
## Unexecuted or blocked evidence
{blocked_text}
## Release disposition
{disposition}
{extra}
'''
def csv_write(name: str, fields: list[str], rows: list[dict[str, str]]):
with (OUT / name).open('w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
def read_json(path: str):
return json.loads((ROOT / path).read_text(encoding='utf-8'))
def get_version(command: list[str]) -> str:
try:
return subprocess.check_output(command, text=True, stderr=subprocess.STDOUT).strip()
except Exception as exc:
return f'unavailable: {exc}'
static_result = read_json('docs/quality/PHASE_15_STATIC_AUDIT_RESULTS_v1.0.json')
baseline_static = read_json('docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json')
phase14_result = read_json('docs/quality/PHASE_14_VALIDATION_RESULTS_v1.0.json')
with (ROOT / 'docs/IMPLEMENTATION_ISSUE_REGISTER_v1.5.csv').open(newline='', encoding='utf-8-sig') as f:
issues = list(csv.DictReader(f))
unresolved = [row for row in issues if 'resolved' not in row['Status'].lower()]
unresolved_counts = Counter(row['Severity'] for row in unresolved)
# Command matrix
command_fields = [
'ID','Command','Environment','Status','Exit_code','Duration_seconds','Test_count','Pass_count',
'Failure_count','Skipped_count','Warning_count','Evidence_path','Related_defects','Notes'
]
command_rows = [
{'ID':'C15-001','Command':'node scripts/validate-phase14-manifest.mjs','Environment':'Fresh extraction of supplied Phase 14 ZIP; Node 22.16.0','Status':'Passed','Exit_code':'0','Duration_seconds':'0.22','Test_count':'466 files','Pass_count':'466','Failure_count':'0','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/phase15-command-logs/02-phase14-manifest-baseline.log','Related_defects':'','Notes':'Verified the supplied archive before Phase 15 modifications.'},
{'ID':'C15-002','Command':'corepack prepare pnpm@11.9.0 --activate','Environment':'Debian 13; Node 22.16.0; Corepack 0.32.0; registry DNS unavailable','Status':'Blocked','Exit_code':'1','Duration_seconds':'0.17','Test_count':'1','Pass_count':'0','Failure_count':'1','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/PHASE_15_TOOLCHAIN_SETUP.log','Related_defects':'P14-009; P15-005','Notes':'Could not download the pinned package manager.'},
{'ID':'C15-003','Command':'pnpm install --frozen-lockfile','Environment':'Required Node 24.17.0 and pnpm 11.9.0','Status':'Not executed - prerequisite blocked','Exit_code':'','Duration_seconds':'','Test_count':'','Pass_count':'','Failure_count':'','Skipped_count':'','Warning_count':'','Evidence_path':'docs/quality/PHASE_15_TOOLCHAIN_SETUP.log','Related_defects':'P14-009; P15-005','Notes':'No pnpm binary and no registry DNS. No dependency update was attempted.'},
{'ID':'C15-004','Command':'node scripts/run-validations.mjs','Environment':'Current Phase 15 tree; Node 22.16.0; no installed packages required','Status':'Passed','Exit_code':'0','Duration_seconds':'1.52','Test_count':'16 validators','Pass_count':'16','Failure_count':'0','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/phase15-command-logs/10-final-validations.log','Related_defects':'P15-001; P15-002; P15-003; P15-004','Notes':'Repository-native dependency-free validators.'},
{'ID':'C15-005','Command':'python3 scripts/phase15-static-audit.py --root /mnt/data/phase14_baseline','Environment':'Fresh Phase 14 extraction; Python 3.13.5','Status':'Failed as expected - baseline findings','Exit_code':'1','Duration_seconds':'1.37','Test_count':str(baseline_static['metrics']['file_count'])+' files','Pass_count':'','Failure_count':'11 findings','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json','Related_defects':'P15-001; P15-002; P15-003; P15-004','Notes':'Found 4 High and 7 Medium defects before fixes.'},
{'ID':'C15-006','Command':'python3 scripts/phase15-static-audit.py','Environment':'Current Phase 15 tree; Python 3.13.5','Status':'Passed','Exit_code':'0','Duration_seconds':'1.38','Test_count':str(static_result['metrics']['file_count'])+' files','Pass_count':str(static_result['metrics']['file_count']),'Failure_count':'0','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/phase15-command-logs/13-final-static-audit.log','Related_defects':'P15-001; P15-002; P15-003; P15-004','Notes':'No findings under the same audit after remediation.'},
{'ID':'C15-007','Command':'node --experimental-strip-types media-type behavior assertions','Environment':'Node 22.16.0','Status':'Passed with runtime warnings','Exit_code':'0','Duration_seconds':'1.37','Test_count':'8 assertions','Pass_count':'8','Failure_count':'0','Skipped_count':'0','Warning_count':'2','Evidence_path':'docs/quality/phase15-command-logs/08-content-type-behavior.log','Related_defects':'P15-002','Notes':'Warnings concern experimental type stripping and do not affect the assertions.'},
{'ID':'C15-008','Command':'node --check scripts; node --experimental-strip-types --check TypeScript; python3 -m py_compile audit','Environment':'Node 22.16.0; Python 3.13.5','Status':'Passed','Exit_code':'0','Duration_seconds':'6.76','Test_count':'99 files','Pass_count':'99','Failure_count':'0','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/phase15-command-logs/09-syntax-check.log','Related_defects':'','Notes':'33 JavaScript modules, 64 TypeScript modules, and two Python scripts.'},
]
blocked_commands = [
('C15-009','pnpm format:check','Formatting validation'),
('C15-010','pnpm lint','ESLint'),
('C15-011','pnpm typecheck','Next type generation and strict TypeScript'),
('C15-012','pnpm test:unit','Unit tests and coverage'),
('C15-013','pnpm test:integration','Integration tests'),
('C15-014','pnpm test:browser','Functional browser tests'),
('C15-015','pnpm test:a11y','Automated accessibility tests'),
('C15-016','pnpm test:visual','Visual-regression tests'),
('C15-017','pnpm test:e2e','Aggregate Playwright tests'),
('C15-018','pnpm test:security','Package-script security validation; underlying static validator ran through C15-004'),
('C15-019','pnpm test:privacy','Package-script privacy validation; underlying static validator ran through C15-004'),
('C15-020','SITE_ORIGIN=https://approved.example PUBLIC_RELEASE_APPROVED=false pnpm build','Production build'),
('C15-021','pnpm audit --audit-level low','Registry-backed dependency audit'),
('C15-022','pnpm exec playwright install --with-deps chromium firefox webkit','Pinned browser installation'),
('C15-023','Performance audit commands','Lighthouse/Web Vitals and route timing'),
('C15-024','Production-like deployment and smoke commands','Deployment rehearsal'),
('C15-025','Rollback platform commands','Rollback rehearsal'),
]
for cid, cmd, note in blocked_commands:
command_rows.append({'ID':cid,'Command':cmd,'Environment':'Exact pinned toolchain and installed dependencies required','Status':'Not executed - clean install blocked','Exit_code':'','Duration_seconds':'','Test_count':'','Pass_count':'','Failure_count':'','Skipped_count':'','Warning_count':'','Evidence_path':'docs/quality/PHASE_15_TOOLCHAIN_SETUP.log','Related_defects':'P15-005','Notes':note})
command_rows.append({'ID':'C15-026','Command':'node scripts/generate-phase15-manifest.mjs && node scripts/validate-phase15-manifest.mjs','Environment':'Final Phase 15 repository; Node 22.16.0; dependency-free','Status':'Passed','Exit_code':'0','Duration_seconds':'0.52','Test_count':'548 files','Pass_count':'548','Failure_count':'0','Skipped_count':'0','Warning_count':'0','Evidence_path':'docs/quality/phase15-command-logs/12-phase15-package-manifest.log','Related_defects':'','Notes':'The validation log is explicitly excluded from its own manifest to avoid a self-referential checksum.'})
csv_write('COMMAND_MATRIX_v1.0.csv', command_fields, command_rows)
# Defect register
fields = ['ID','Summary','Severity','User_impact','Business_impact','Security_impact','Privacy_impact','Accessibility_impact','Affected_locale','Affected_theme','Affected_browser','Affected_viewport','Reproduction_steps','Expected_behavior','Actual_behavior','Evidence','Owner','Status','Release_disposition','Resolution_commit','Retest_result']
defects = [
{'ID':'P15-001','Summary':'Two release-evidence CSV files had inconsistent column counts','Severity':'High','User_impact':'Indirect; evidence could be misread or fields shifted','Business_impact':'Release decisions could use corrupted issue or QA records','Security_impact':'None direct','Privacy_impact':'None direct','Accessibility_impact':'None direct','Affected_locale':'All','Affected_theme':'All','Affected_browser':'All','Affected_viewport':'All','Reproduction_steps':'Parse all repository CSV files with a standards-compliant CSV reader','Expected_behavior':'Every row matches the header width','Actual_behavior':'P14-009 had 11 fields instead of 9; P14-M10 had 10 instead of 11','Evidence':'PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json','Owner':'Engineering + QA','Status':'Resolved','Release_disposition':'Fixed in Phase 15','Resolution_commit':'Unavailable; supplied archive had no Git metadata','Retest_result':'All CSV files parse; C15-004 and C15-006 pass'},
{'ID':'P15-002','Summary':'Demo API accepted JSON look-alike media types by prefix','Severity':'Medium','User_impact':'Malformed clients could reach JSON parsing instead of receiving 415','Business_impact':'Ambiguous API contract and weaker request validation','Security_impact':'Broader-than-documented content-type acceptance','Privacy_impact':'No direct data exposure identified','Accessibility_impact':'None','Affected_locale':'All','Affected_theme':'All','Affected_browser':'All clients','Affected_viewport':'N/A','Reproduction_steps':'Send Content-Type application/jsonp','Expected_behavior':'415 unsupported_content_type','Actual_behavior':'Prefix check treated it as JSON','Evidence':'PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json','Owner':'Engineering + Security','Status':'Resolved','Release_disposition':'Fixed in Phase 15','Resolution_commit':'Unavailable; supplied archive had no Git metadata','Retest_result':'Eight dependency-free assertions pass; Vitest regression authored but not run'},
{'ID':'P15-003','Summary':'Visual tests used a stale theme key and inconsistent snapshot project scope','Severity':'Medium','User_impact':'Dark-mode regressions could be recorded under the wrong theme','Business_impact':'Unreliable visual approval evidence','Security_impact':'None','Privacy_impact':'None','Accessibility_impact':'Theme contrast regressions could be missed','Affected_locale':'EN/FR/AR','Affected_theme':'Dark/system','Affected_browser':'All Playwright projects','Affected_viewport':'Configured visual matrix','Reproduction_steps':'Inspect component-system visual setup and run snapshots outside Chromium','Expected_behavior':'Canonical theme key; canonical Chromium snapshots only','Actual_behavior':'Unused storage key and non-canonical project execution','Evidence':'PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json','Owner':'Engineering + Design QA','Status':'Resolved','Release_disposition':'Fixed; browser retest still blocked','Resolution_commit':'Unavailable; supplied archive had no Git metadata','Retest_result':'Static regression validator passes; browser snapshots not executed'},
{'ID':'P15-004','Summary':'CI omitted the demo-service integration test suite','Severity':'High','User_impact':'Server-side conversion failures could merge undetected','Business_impact':'Primary lead workflow lacked CI integration protection','Security_impact':'Adapter error/timeout/idempotency regressions could escape CI','Privacy_impact':'Boundary regressions could escape CI','Accessibility_impact':'None direct','Affected_locale':'All','Affected_theme':'All','Affected_browser':'Server boundary','Affected_viewport':'N/A','Reproduction_steps':'Inspect Phase 14 CI quality job','Expected_behavior':'CI runs pnpm test:integration','Actual_behavior':'Only unit and Playwright suites were configured','Evidence':'PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json','Owner':'Engineering + QA','Status':'Resolved','Release_disposition':'CI command added; execution pending connected CI','Resolution_commit':'Unavailable; supplied archive had no Git metadata','Retest_result':'Phase 15 validator confirms command presence; integration suite not locally executed'},
{'ID':'P15-005','Summary':'Final Phase 15 dependency-backed, browser, performance, deployment, rollback, and manual QA evidence is incomplete','Severity':'High','User_impact':'Accessibility, browser, performance, and conversion defects may remain undiscovered','Business_impact':'Release readiness cannot be established','Security_impact':'Dependency and dynamic security checks are stale or absent','Privacy_impact':'Runtime analytics/consent behavior lacks current browser evidence','Accessibility_impact':'Manual WCAG, keyboard, screen-reader, zoom, and reflow evidence absent','Affected_locale':'EN/FR/AR','Affected_theme':'Light/dark/system','Affected_browser':'Chromium/Firefox/WebKit/mobile engines','Affected_viewport':'1440/1024/768/390/320 and zoom states','Reproduction_steps':'Attempt pinned toolchain activation and frozen install','Expected_behavior':'Exact toolchain installs and all Phase 15 gates execute','Actual_behavior':'Host has Node 22.16.0, no pnpm, no registry DNS, and no project dependencies','Evidence':'PHASE_15_TOOLCHAIN_SETUP.log; COMMAND_MATRIX_v1.0.csv','Owner':'Engineering + QA + Accessibility + Security','Status':'Open - environment blocked','Release_disposition':'Blocks Phase 15 acceptance and Phase 16 readiness','Resolution_commit':'N/A','Retest_result':'Not retested; requires connected pinned CI and human QA'},
]
csv_write('DEFECT_REGISTER_v1.0.csv', fields, defects)
# Release gates
fields = ['Gate','Severity','Status','Evidence','Owner','Exit_criteria','Accepted_exception']
gates = [
{'Gate':'Supplied Phase 14 package integrity','Severity':'High','Status':'Passed','Evidence':'C15-001','Owner':'Engineering','Exit_criteria':'Manifest validates fresh extraction','Accepted_exception':'No'},
{'Gate':'Phase 15 static validators','Severity':'High','Status':'Passed','Evidence':'C15-004','Owner':'Engineering + QA','Exit_criteria':'All dependency-free validators pass','Accepted_exception':'No'},
{'Gate':'Repository integrity and secret/PII fixture audit','Severity':'High','Status':'Passed','Evidence':'C15-006','Owner':'Engineering + Security + Privacy','Exit_criteria':'Zero Critical/High/Medium findings','Accepted_exception':'No'},
{'Gate':'Exact Node 24.17.0 and pnpm 11.9.0 frozen install','Severity':'High','Status':'Blocked','Evidence':'C15-002; C15-003','Owner':'Engineering + Security','Exit_criteria':'Connected clean environment completes frozen install','Accepted_exception':'No'},
{'Gate':'Format, lint, strict typecheck, unit, integration, and production build on final manifest','Severity':'High','Status':'Blocked','Evidence':'C15-009 through C15-020','Owner':'Engineering + QA','Exit_criteria':'All commands exit zero on final manifest','Accepted_exception':'No'},
{'Gate':'Dependency and license audit','Severity':'High','Status':'Blocked','Evidence':'C15-021','Owner':'Security + Legal','Exit_criteria':'Registry-backed audit and configured license check pass','Accepted_exception':'No'},
{'Gate':'Cross-browser functional execution','Severity':'High','Status':'Blocked','Evidence':'C15-014; P14-008','Owner':'QA','Exit_criteria':'Chromium, Firefox, WebKit, mobile Chrome, mobile Safari pass','Accepted_exception':'No'},
{'Gate':'Automated accessibility execution','Severity':'High','Status':'Blocked','Evidence':'C15-015; P14-008','Owner':'Accessibility + QA','Exit_criteria':'Axe matrix passes with reviewed exceptions','Accepted_exception':'No'},
{'Gate':'Manual keyboard, screen-reader, zoom, reflow, forced-colors, and RTL review','Severity':'High','Status':'Blocked','Evidence':'MANUAL_TEST_MATRIX_v1.0.csv','Owner':'Accessibility + QA + Localization','Exit_criteria':'Human matrix completed with evidence','Accepted_exception':'No'},
{'Gate':'Visual-regression baseline generation and approval','Severity':'High','Status':'Blocked','Evidence':'C15-016; VISUAL_REGRESSION_REPORT_v1.0.md','Owner':'Design + QA','Exit_criteria':'Pinned Chromium baselines generated and all diffs reviewed','Accepted_exception':'No'},
{'Gate':'Performance and bundle budgets','Severity':'High','Status':'Blocked','Evidence':'PERFORMANCE_REPORT_v1.0.md; BUNDLE_ANALYSIS_REPORT_v1.0.md','Owner':'Engineering','Exit_criteria':'Final build measured against approved budgets','Accepted_exception':'No'},
{'Gate':'Production-like deployment and rollback rehearsal','Severity':'High','Status':'Blocked','Evidence':'DEPLOYMENT_REHEARSAL_REPORT_v1.0.md; ROLLBACK_REHEARSAL_REPORT_v1.0.md','Owner':'Infrastructure + Engineering','Exit_criteria':'Documented rehearsal succeeds and is timed','Accepted_exception':'No'},
{'Gate':'Approved CRM/lead destination','Severity':'High','Status':'Blocked','Evidence':'P14-001','Owner':'Sales Ops + Engineering','Exit_criteria':'Approved vendor, credentials, mapping, retries, duplicates, sandbox, monitoring','Accepted_exception':'No'},
{'Gate':'Approved legal/privacy/form processing','Severity':'High','Status':'Blocked','Evidence':'P14-002','Owner':'Legal + Privacy','Exit_criteria':'Localized notice, legal basis, consent, retention, deletion, recipients, transfers approved','Accepted_exception':'No'},
{'Gate':'Approved analytics provider and consent','Severity':'High','Status':'Blocked','Evidence':'P14-003','Owner':'Analytics + Legal','Exit_criteria':'Provider, DPA, consent, region, retention, event policy approved','Accepted_exception':'No'},
{'Gate':'Distributed idempotency and abuse controls','Severity':'High','Status':'Blocked','Evidence':'P14-004; P9-002','Owner':'Engineering + Sales Ops','Exit_criteria':'Durable store, rate limit, duplicate policy, monitoring validated','Accepted_exception':'No'},
{'Gate':'Native French and Arabic review','Severity':'High','Status':'Open','Evidence':'P9-005','Owner':'Localization + Content','Exit_criteria':'Native reviewers approve all visible and metadata strings','Accepted_exception':'No'},
{'Gate':'Participant and accessibility acceptance research','Severity':'Critical','Status':'Blocked','Evidence':'P9-001','Owner':'Research + Product','Exit_criteria':'Required cohorts completed; S0/S1 findings fixed and retested','Accepted_exception':'No'},
{'Gate':'Canonical domain, brand assets, and public destinations','Severity':'High','Status':'Blocked','Evidence':'P10-001; P10-002; destination registry','Owner':'Product + Infrastructure + Design','Exit_criteria':'Approved production origin, logo, legal, login, sales, support, tour, pricing destinations','Accepted_exception':'No'},
{'Gate':'Final source-control commit identity','Severity':'Medium','Status':'Blocked','Evidence':'Supplied ZIP contains no .git metadata','Owner':'Engineering','Exit_criteria':'Commit final manifest and record immutable SHA','Accepted_exception':'No'},
]
csv_write('RELEASE_GATE_MATRIX_v1.0.csv', fields, gates)
csv_write('RISK_ACCEPTANCE_REGISTER_v1.0.csv', ['ID','Gate','Risk','User_impact','Business_impact','Mitigation','Owner','Approver','Expiration','Required_follow_up'], [])
# Manual test matrix
manual_fields = ['ID','Area','Platform_or_AT','Locale','Theme','Viewport_or_zoom','Status','Evidence','Blocking_issue']
manual_rows = [
{'ID':'M15-001','Area':'Keyboard-only primary demo flow','Platform_or_AT':'Desktop browser','Locale':'en/fr/ar','Theme':'light/dark','Viewport_or_zoom':'1440/390','Status':'Not run','Evidence':'Authored Playwright and component semantics only','Blocking_issue':'P15-005'},
{'ID':'M15-002','Area':'NVDA flow','Platform_or_AT':'NVDA + Firefox/Chrome on Windows','Locale':'en/ar','Theme':'light/dark','Viewport_or_zoom':'desktop','Status':'Not run','Evidence':'None; human AT required','Blocking_issue':'P15-005'},
{'ID':'M15-003','Area':'VoiceOver macOS','Platform_or_AT':'VoiceOver + Safari','Locale':'en/ar','Theme':'light/dark','Viewport_or_zoom':'desktop','Status':'Not run','Evidence':'None; human AT required','Blocking_issue':'P15-005'},
{'ID':'M15-004','Area':'VoiceOver iOS','Platform_or_AT':'VoiceOver + Mobile Safari','Locale':'en/ar','Theme':'system','Viewport_or_zoom':'390-class device','Status':'Not run','Evidence':'None; physical/device lab required','Blocking_issue':'P15-005'},
{'ID':'M15-005','Area':'TalkBack Android','Platform_or_AT':'TalkBack + Chrome','Locale':'en/ar','Theme':'system','Viewport_or_zoom':'390-class device','Status':'Not run','Evidence':'None; device/emulator required','Blocking_issue':'P15-005'},
{'ID':'M15-006','Area':'200% and 400% zoom/reflow','Platform_or_AT':'Desktop browsers','Locale':'en/fr/ar','Theme':'light/dark','Viewport_or_zoom':'200%/400%','Status':'Not run','Evidence':'Responsive source and tests authored only','Blocking_issue':'P15-005'},
{'ID':'M15-007','Area':'Forced colors and reduced motion','Platform_or_AT':'Windows forced colors; reduced-motion media','Locale':'en/fr/ar','Theme':'all','Viewport_or_zoom':'desktop/mobile','Status':'Not run','Evidence':'CSS controls present; human review absent','Blocking_issue':'P15-005'},
{'ID':'M15-008','Area':'Arabic reading and focus order','Platform_or_AT':'Browser + Arabic screen reader review','Locale':'ar','Theme':'light/dark','Viewport_or_zoom':'1440/390/320','Status':'Not run','Evidence':'Logical CSS validator passes; human review absent','Blocking_issue':'P15-005'},
]
csv_write('MANUAL_TEST_MATRIX_v1.0.csv', manual_fields, manual_rows)
# WCAG matrix
wcag_fields = ['Criterion','Level','Applicability','Status','Evidence','Remaining_validation','Related_issue']
criteria = [
('1.1.1 Non-text Content','A','Applicable','Static evidence only','MediaFrame requires alt; icon primitives support accessible names','Browser and screen-reader inspection of every rendered media state','P15-005'),
('1.3.1 Info and Relationships','A','Applicable','Static evidence only','Semantic landmarks, labels, fieldsets, headings, native dialog','Manual screen-reader verification','P15-005'),
('1.3.2 Meaningful Sequence','A','Applicable','Static evidence only','DOM order and logical CSS reviewed','EN/FR/AR screen-reader and keyboard order','P15-005'),
('1.3.3 Sensory Characteristics','A','Applicable','Static evidence only','Instructions do not rely solely on position','Rendered content review','P15-005'),
('1.4.1 Use of Color','A','Applicable','Static evidence only','Status text/icons accompany semantic color tokens','Manual light/dark/forced-colors review','P15-005'),
('1.4.3 Contrast Minimum','AA','Applicable','Blocked','Token intent exists','Measured contrast in all states/themes required','P15-005'),
('1.4.4 Resize Text','AA','Applicable','Blocked','Responsive typography authored','200% browser verification required','P15-005'),
('1.4.10 Reflow','AA','Applicable','Blocked','320px responsive scenarios authored','400% and narrow viewport execution required','P15-005'),
('1.4.11 Non-text Contrast','AA','Applicable','Blocked','Focus and border tokens exist','Rendered measurement required','P15-005'),
('1.4.12 Text Spacing','AA','Applicable','Blocked','Content-driven dimensions used','Text-spacing override test required','P15-005'),
('1.4.13 Content on Hover or Focus','AA','Applicable','Static evidence only','Tooltip component defined','Keyboard/pointer dismissal and persistence review','P15-005'),
('2.1.1 Keyboard','A','Applicable','Blocked','Native controls and keyboard tests authored','Full keyboard-only flow required','P15-005'),
('2.1.2 No Keyboard Trap','A','Applicable','Blocked','Native dialog used','Manual modal/drawer trap and escape review','P15-005'),
('2.2.1 Timing Adjustable','A','Limited','Static evidence only','No user-facing session timer; bounded server timeout preserves form','Slow completion and retry review','P15-005'),
('2.2.2 Pause Stop Hide','A','Applicable','Static evidence only','No autoplay media; reduced-motion styles exist','Rendered animation review','P15-005'),
('2.3.1 Three Flashes','A','Applicable','Static evidence only','No flashing content identified in source','Visual confirmation','P15-005'),
('2.4.1 Bypass Blocks','A','Applicable','Static evidence only','Skip navigation validator and shell tests authored','Keyboard confirmation','P15-005'),
('2.4.2 Page Titled','A','Applicable','Static evidence only','Localized metadata builder validator passes','Rendered route title verification','P15-005'),
('2.4.3 Focus Order','A','Applicable','Blocked','Logical DOM order and native controls','Manual all-state focus order','P15-005'),
('2.4.4 Link Purpose','A','Applicable','Static evidence only','Destination registry and localized labels','Rendered route review','P15-005'),
('2.4.6 Headings and Labels','AA','Applicable','Static evidence only','Structured headings and explicit form labels','Screen-reader review','P15-005'),
('2.4.7 Focus Visible','AA','Applicable','Blocked','Focus tokens/styles exist','Rendered keyboard review in themes/forced colors','P15-005'),
('2.4.11 Focus Not Obscured Minimum','AA','Applicable','Blocked','Sticky shell and dialog CSS reviewed','Viewport/zoom execution required','P15-005'),
('2.5.7 Dragging Movements','AA','Applicable','Static evidence only','No drag-only control identified','Rendered interaction inventory confirmation','P15-005'),
('2.5.8 Target Size Minimum','AA','Applicable','Blocked','Component sizing tokens exist','Measured desktop/mobile targets required','P15-005'),
('3.1.1 Language of Page','A','Applicable','Static evidence only','Server-rendered lang and dir validators pass','Rendered HTML verification','P15-005'),
('3.1.2 Language of Parts','AA','Applicable','Static evidence only','Bidi and localized content primitives exist','Foreign-language option pronunciation review','P15-005'),
('3.2.1 On Focus','A','Applicable','Static evidence only','No focus-triggered navigation identified','Manual interaction review','P15-005'),
('3.2.2 On Input','A','Applicable','Static evidence only','Locale/theme actions are explicit controls','Manual form and selector review','P15-005'),
('3.2.3 Consistent Navigation','AA','Applicable','Static evidence only','Shared shell components','Cross-route rendered comparison','P15-005'),
('3.3.1 Error Identification','A','Applicable','Static evidence only','Error summary, field errors, aria-invalid','Screen-reader announcement review','P15-005'),
('3.3.2 Labels or Instructions','A','Applicable','Static evidence only','Required/optional labels and supporting text','Rendered/AT review','P15-005'),
('3.3.3 Error Suggestion','AA','Applicable','Static evidence only','Localized actionable validation messages','Manual flow review','P15-005'),
('3.3.4 Error Prevention','AA','Applicable','Static evidence only','No irreversible legal/financial transaction; duplicate controls present','Business policy review','P14-004'),
('4.1.2 Name Role Value','A','Applicable','Static evidence only','Native controls and explicit labels; ARIA in composites','Automated and screen-reader execution','P15-005'),
('4.1.3 Status Messages','AA','Applicable','Static evidence only','LiveRegion, role=alert, success aria-live','Actual announcement capture required','P15-005'),
]
csv_write('WCAG_2_2_AA_EVIDENCE_MATRIX_v1.0.csv', wcag_fields, [dict(zip(wcag_fields,row)) for row in criteria])
# Individual reports
write('CLEAN_ROOM_BASELINE_REPORT_v1.0.md', report(
'Phase 15 Clean-room Baseline Report','Blocked / failed acceptance',
'The supplied ZIP was extracted into a fresh directory and its Phase 14 manifest verified 466 files. The exact Phase 15 clean install could not begin because the host does not provide the pinned Node/pnpm toolchain and cannot resolve the package registry.',
['Fresh archive manifest passed (C15-001).','ZIP SHA-256 and lockfile SHA-256 are recorded in the implementation report.','No dependency or lockfile update was performed.'],
['Node 24.17.0 is not installed; observed Node is 22.16.0.','pnpm 11.9.0 activation failed because registry DNS is unavailable.','Frozen install and every dependency-backed command are blocked.'],
'This gate fails Phase 15 acceptance. A connected clean runner using the pinned versions must rerun the complete matrix.'
))
write('REPOSITORY_INTEGRITY_AUDIT_v1.0.md', report(
'Phase 15 Repository Integrity Audit','Passed for static package integrity',
'The baseline manifest passed before changes. The Phase 15 audit checks JSON/CSV structure, symlinks, generated files, secret patterns, reserved test email domains, runtime URLs, dependency pinning, and release-control invariants.',
['Baseline manifest: 466 files verified.','Baseline audit exposed 4 High and 7 Medium findings.','Current audit reports zero findings under the same controls.'],
['Final package manifest validation occurs after all reports are generated.','Git commit history was not supplied and cannot be audited.'],
'Static repository integrity is acceptable, but it does not replace build, dependency, browser, or human QA evidence.'
))
write('STATIC_ANALYSIS_REPORT_v1.0.md', report(
'Phase 15 Static Analysis Report','Partially passed',
'All dependency-free repository validators and the Phase 15 audit pass after four defects were repaired. Package-backed formatting, ESLint, strict TypeScript, dead-code tooling, dependency/license analysis, and client-bundle inspection could not run on the final tree.',
['16 repository validators pass.','33 JavaScript and 64 TypeScript modules plus two Python scripts pass syntax checks.','JSON/CSV, secret, URL, PII-fixture, direct-dependency, visual-control, and CI-control audit passes.'],
['Prettier check, ESLint, strict typecheck, dead-code and unused-dependency tools.','Registry-backed audit and license validation.','Production client-bundle secret inspection.'],
'Static source controls pass; the overall static-analysis gate remains blocked until package-backed checks run.'
))
write('UNIT_TEST_REPORT_v1.0.md', report(
'Phase 15 Unit Test Report','Blocked for current release candidate',
'A Vitest regression test was added for exact JSON media-type handling. The current unit suite could not execute because dependencies are unavailable. Phase 14 recorded 39 passing unit tests, but that result predates Phase 15 code changes.',
['Eight dependency-free assertions validate the new media-type helper.','Phase 14 historical result: 39 passed, 0 failed, with recorded coverage.'],
['Current `pnpm test:unit` and coverage thresholds.','Current React/component and Zod-dependent tests.'],
'Do not use the Phase 14 unit result as current release evidence. Rerun on the final manifest.'
))
write('INTEGRATION_TEST_REPORT_v1.0.md', report(
'Phase 15 Integration Test Report','Blocked for current release candidate',
'Phase 14 recorded four passing demo-service integration tests. Phase 15 discovered that CI did not execute this suite and corrected the workflow, but the final suite could not run locally.',
['CI now contains `pnpm test:integration`.','Static validator confirms the integration gate remains configured.','Historical Phase 14 integration tests cover success, idempotent transport, normalized failure, and timeout.'],
['Current Vitest integration execution.','Production/sandbox CRM, scheduler, email, and durable idempotency integrations do not exist.'],
'The integration gate is blocked until connected CI executes the suite and approved vendor adapters exist.'
))
write('END_TO_END_TEST_REPORT_v1.0.md', report(
'Phase 15 End-to-End Test Report','Blocked',
'The repository contains authored Playwright flows for locale pages, navigation, the demo workflow, responsive states, no-JavaScript behavior, accessibility, and visual checks. No current Phase 15 browser flow executed.',
['Scenario source was inspected.','CI now invokes browser, accessibility, and visual slices explicitly.'],
['All 50 required Phase 15 end-to-end scenarios.','Success, validation, timeout, duplicate, history, offline, and dependency-failure browser evidence.'],
'High release blocker P15-005 remains open.'
))
write('CROSS_BROWSER_TEST_REPORT_v1.0.md', report(
'Phase 15 Cross-browser Test Report','Blocked',
'The configured matrix includes Chromium, Firefox, WebKit, mobile Chrome, and mobile Safari. No current application run was possible.',
['Playwright project configuration statically reviewed.','Canonical visual snapshots are now Chromium-only; other engines remain functional targets.'],
['Browser installation in the pinned environment.','Functional results by browser/version/OS/device/locale/theme/viewport.'],
'No browser support claim is approved from configuration alone.'
))
write('DEVICE_VIEWPORT_REPORT_v1.0.md', report(
'Phase 15 Device and Viewport Report','Blocked for rendered validation',
'Responsive CSS and authored tests cover the required widths, but no final rendered matrix was observed.',
['Logical CSS validator passes across 47 CSS files.','Authored scenarios include 1440, 1024, 768, 390, and 320 widths.'],
['Horizontal overflow, clipping, focus visibility, touch targets, safe areas, reduced height, and mobile landscape checks.'],
'Responsive implementation remains unaccepted until browser evidence is attached.'
))
write('ZOOM_REFLOW_REPORT_v1.0.md', report(
'Phase 15 Zoom and Reflow Report','Blocked',
'Source uses content-driven dimensions and logical properties, and text-expansion scenarios exist. Actual 200%/400% zoom, OS text scaling, French expansion, Arabic expansion, and pseudolocalization were not run.',
['Static logical CSS checks pass.','Manual matrix identifies required zoom and AT combinations.'],
['200% and 400% browser zoom.','Text-spacing overrides and OS scaling.','Dialog/error-summary focus visibility at zoom.'],
'WCAG reflow and resize criteria are not verified.'
))
write('AUTOMATED_ACCESSIBILITY_REPORT_v1.0.md', report(
'Phase 15 Automated Accessibility Report','Blocked',
'Axe suites are authored for shell, homepage, component system, and demo workflow. They could not execute on the final Phase 15 tree.',
['A11y source scenarios inspected.','Static semantic controls and WCAG matrix prepared.'],
['Axe results for EN/FR/AR, themes, mobile, form states, dialogs, FAQ, scheduler, forced colors.'],
'No automated accessibility pass is claimed.'
))
write('KEYBOARD_ACCESSIBILITY_REPORT_v1.0.md', report(
'Phase 15 Keyboard Accessibility Report','Not run',
'Native controls, native dialog, focus-return references, skip navigation, form error links, and visible-focus styles are present in source. Keyboard-only behavior was not manually observed.',
['Source-level focus and semantic review.','Manual test matrix created.'],
['Complete pointer-free path including mobile navigation, form errors, consent, dialogs, success, and recovery.'],
'Manual keyboard acceptance is a High blocker.'
))
write('SCREEN_READER_REPORT_v1.0.md', report(
'Phase 15 Screen-reader Report','Not run',
'No NVDA, VoiceOver, or TalkBack session was performed. The report intentionally contains no invented announcements.',
['Screen-reader combinations and required flows are listed in MANUAL_TEST_MATRIX_v1.0.csv.','Static language, direction, labels, status regions, and dialog relationships reviewed.'],
['Actual announcement transcripts for English and Arabic.','Focus movement, error/status announcements, mixed-direction values, and mobile AT behavior.'],
'Screen-reader acceptance is blocked.'
))
write('LOCALIZATION_QA_REPORT_v1.0.md', report(
'Phase 15 Localization QA Report','Static parity passed; linguistic approval blocked',
'English, French, and Arabic catalogs have exact key parity and valid direction metadata. Native linguistic review remains open.',
['Locale validator passes.','Metadata/route parity validator passes.','No non-reserved personal-data fixture or runtime implementation key was found by the audit.'],
['Native French review.','Native formal Modern Standard Arabic review.','External vendor localization because vendors are not selected.'],
'Mechanical parity is not translation approval; P9-005 remains High.'
))
write('RTL_QA_REPORT_v1.0.md', report(
'Phase 15 RTL QA Report','Static controls passed; end-to-end review blocked',
'The Arabic locale is configured for server-rendered RTL and the CSS validator reports 47 files using logical declarations. Native visual, focus-order, screen-reader, mixed-direction, and scheduler checks did not run.',
['Locale direction metadata passes.','Logical CSS validation passes.','Bidi primitives and LTR treatment for email/tel/url values are present.'],
['Rendered header, hero, workflow, forms, error summaries, dialogs, footer, animation, and 320px overflow review.'],
'Arabic RTL is not accepted from text alignment alone.'
))
write('THEME_QA_REPORT_v1.0.md', report(
'Phase 15 Theme QA Report','Static controls passed; browser verification blocked',
'Light, dark, and system foundations, nonce pre-paint bootstrap, persistence, and CSS fallback are present. A stale visual-test storage key was fixed.',
['Theme validators pass.','Canonical visual setup now uses `hpc.theme.preference`.','Semantic token controls pass.'],
['No-flash behavior, OS theme changes, delayed JavaScript, disabled JavaScript, contrast, embeds, and forced-colors states.'],
'Theme acceptance remains blocked pending rendered evidence.'
))
write('VISUAL_REGRESSION_REPORT_v1.0.md', report(
'Phase 15 Visual Regression Report','Blocked',
'No approved PNG baselines exist. Phase 15 corrected snapshot determinism but did not generate or approve product images.',
['Snapshot suites now use canonical Chromium scope.','Component-system test now writes the actual theme storage key.'],
['Baseline generation in pinned Chromium.','Manual diff classification for locales, themes, viewports, interaction states, focus, reduced motion, forced colors.'],
'Updating baselines is prohibited until the application executes and reviewers inspect every diff.'
))
write('DESIGN_FIDELITY_REPORT_v1.0.md', report(
'Phase 15 Design Fidelity Report','Blocked for visual comparison',
'Section order and component composition validators pass, but no rendered comparison against Phase 7 production designs was performed.',
['Homepage composition and content-model validators pass.','Design-token immutability and semantic color controls pass.'],
['Typography, spacing, alignment, media scale, responsive adaptation, themes, RTL, focus, loading, error, and success visual comparison.'],
'Design fidelity is unverified, not failed.'
))
write('PERFORMANCE_REPORT_v1.0.md', report(
'Phase 15 Performance Report','Blocked',
'Approved budgets exist for LCP, INP, CLS, TTFB, transfer sizes, fonts, media, and third-party requests. No final production build or browser timing was available.',
['Budget contract inspected: LCP 2500 ms, INP 200 ms, CLS 0.1, TTFB 800 ms maximums.','No approved vendor SDK or third-party runtime was added.'],
['Cold/warm desktop/mobile measurements in EN and AR.','LCP asset, main-thread, hydration, form latency, scheduler latency, failure response, and third-party request measurement.'],
'Performance gate is blocked; historical Phase 14 build inventory is not a Phase 15 measurement.'
))
write('BUNDLE_ANALYSIS_REPORT_v1.0.md', report(
'Phase 15 Bundle Analysis Report','Blocked for final build',
'Source review confirms no new runtime dependency was added and the content-type helper is server-side. Final route chunks and client boundaries could not be measured.',
['Direct dependency pins and lockfile contract pass.','No vendor SDK, consent manager, scheduler, email, or analytics network package is present.'],
['Production route bundle sizes, duplicate packages/locales/icons, source maps, tree shaking, dynamic imports, and client secret inspection.'],
'Bundle approval requires the final production build.'
))
write('IMAGE_FONT_PERFORMANCE_REPORT_v1.0.md', report(
'Phase 15 Image and Font Performance Report','Blocked for rendered/network measurement',
'The public directory contains no production visual assets. Inter and Noto Sans Arabic remain package dependencies. No network waterfall or font-layout measurement ran.',
['Asset inventory confirms no unapproved logo, screenshot, hero image, or social image was added.','Arabic font is separately represented in dependency and token foundations.'],
['Final logo/social/hero assets, responsive sizes, compression, preload policy, Arabic font payload, CLS, cache headers.'],
'Final asset approval and performance measurement remain Phase 16 gates after QA rerun.'
))
write('SECURITY_VALIDATION_REPORT_v1.0.md', report(
'Phase 15 Security Validation Report','Static controls passed; dynamic and dependency checks blocked',
'Static review confirms request marker, same-origin check, exact JSON content type, body limit, strict schema, honeypot, bounded timeout, idempotency, normalized errors, no-store responses, CSP, and server-only environment controls. The prefix media-type defect was fixed.',
['Phase 14 security validator passes through C15-004.','Secret-pattern and runtime URL audit passes.','Exact content-type behavior assertions pass.'],
['Registry vulnerability/license audit.','Dynamic CSRF, origin, replay, malformed input, CSP, headers, rate limit, webhook, and vendor credential testing on final build.'],
'Security is not production-approved. Rate limiting, durable idempotency, vendors, and dynamic evidence remain blockers.'
))
write('PRIVACY_VALIDATION_REPORT_v1.0.md', report(
'Phase 15 Privacy Validation Report','Static controls passed; legal and runtime approval blocked',
'Static checks find no personal data in analytics contracts, URLs, runtime logs, fixtures outside reserved domains, or browser persistence paths for form values. No production processing destination exists.',
['Privacy validator passes.','Audit finds only reserved `.test`/`.invalid` email fixtures.','Analytics context remains strict and excludes form fields.'],
['Runtime browser capture, logs, monitoring, vendor payloads, retention, deletion, recipients, transfers, staging separation.','Legal/privacy approval.'],
'P14-002 and related legal gates block production.'
))
write('CONSENT_VALIDATION_REPORT_v1.0.md', report(
'Phase 15 Consent Validation Report','Blocked by product/legal decisions',
'Analytics is disabled by default and optional marketing consent is not implemented. This prevents premature vendor loading but does not constitute a complete consent system.',
['Static environment defaults prohibit production analytics.','Test analytics requires an explicit test-only document flag.','Consent matrix reviewed.'],
['No-choice, necessary-only, accept/reject, withdrawal, returning visitor, locale change, consent version, vendor failure, delayed JavaScript.','Localized approved legal language and storage mechanism.'],
'No consent-compliance claim is permitted.'
))
write('ANALYTICS_VALIDATION_REPORT_v1.0.md', report(
'Phase 15 Analytics Validation Report','Static contract passed; provider/runtime blocked',
'The allowlisted event schema permits only route, locale, direction, resolved theme, source, step, error code/count, and reason values. The current sink has no network and requires test consent.',
['Analytics/privacy validators pass.','Forbidden payload keys include name, email, company, message, phone, field value, and server response.'],
['Provider, consent gating in real browser, duplicate hydration events, route changes, captured event evidence, retention, region, DPA.'],
'Analytics must remain disabled.'
))
write('DESTINATION_BROKEN_LINK_REPORT_v1.0.md', report(
'Phase 15 Destination and Broken-link Report','Static registry passed; external destinations blocked',
'The route and destination validators pass. Demo is local-only; contact sales, product tour, login, cookie settings, and support are blocked; pricing/legal/accessibility routes are placeholders.',
['Five route IDs have complete locale/hash/query/hreflang contracts.','No unapproved runtime external URL was found.','Destination registry is explicit.'],
['Rendered broken-link crawl of generated routes.','Production URLs, redirects, status codes, canonical/hreflang, external security behavior.'],
'Blocked destinations must not be activated until approved.'
))
write('FAILURE_SIMULATION_REPORT_v1.0.md', report(
'Phase 15 Failure Simulation Report','Blocked for current execution',
'Deterministic scenarios exist for success, duplicate, timeout, integration error, validation, and blocked configuration. Current browser/server simulations did not run.',
['Source adapters and scenario guards reviewed.','Test scenarios remain disabled in production.'],
['CRM/scheduler/analytics/consent/email/DNS/network/slow/partial/rate-limit/storage/script/CSP/offline failures.'],
'Authored scenarios are not observed resilience evidence.'
))
write('IDEMPOTENCY_DUPLICATE_REPORT_v1.0.md', report(
'Phase 15 Idempotency and Duplicate Report','Local static design reviewed; production gate blocked',
'The current process-local store coalesces repeated keys, caches non-retryable results, removes retryable failures, and contains no personal data in keys. It is explicitly test/local only.',
['Historical Phase 14 integration test covered repeated transport.','Source review confirms retryable failures release the key.'],
['Current integration/browser retry execution.','Distributed durable store, late vendor success, cross-instance duplicate policy, legitimate later request policy.'],
'P14-004 blocks scaled production.'
))
write('ABUSE_CONTROL_REPORT_v1.0.md', report(
'Phase 15 Abuse-control Report','Partial static controls; production gate blocked',
'The API enforces a 16 KiB body limit, request marker, origin check, strict schema, content type, and honeypot. No distributed rate limit or approved CAPTCHA/accessibility alternative exists.',
['Static API controls pass.','Exact content type defect fixed.'],
['Rapid requests, oversized/malformed payloads, origins, scripted submissions, slow assistive-technology users, international formats, threshold behavior.','Distributed rate limiting and operational monitoring.'],
'Production submission remains blocked.'
))
write('DEPLOYMENT_REHEARSAL_REPORT_v1.0.md', report(
'Phase 15 Deployment Rehearsal Report','Not run',
'No production-like deployment was built or started from the Phase 15 manifest. Historical Phase 14 standalone HTTP evidence is retained but stale after code and CI changes.',
['Dockerfile and CI deployment build path statically reviewed.','Production environment defaults fail closed.'],
['Clean build, assets, headers, redirects, metadata, robots, health/startup failure, flags, observability, no test adapter, exact commands and timing.'],
'Deployment gate blocks release.'
))
write('ROLLBACK_REHEARSAL_REPORT_v1.0.md', report(
'Phase 15 Rollback Rehearsal Report','Documented concept; not rehearsed',
'The public demo and analytics can be disabled through existing flags, and production defaults are blocked. No platform rollback was executed.',
['Feature-flag inventory and Phase 14 rollback guidance reviewed.'],
['Restore previous release, disable CRM/scheduler/analytics independently, verify no resend, stale secret handling, fallback UX, timing, owner confirmation.'],
'Rollback readiness is unproven.'
))
write('OBSERVABILITY_VALIDATION_REPORT_v1.0.md', report(
'Phase 15 Observability Validation Report','Blocked',
'Correlation identifiers are non-sensitive and raw form logging is absent. No production logging, metrics, dashboards, alert thresholds, retention, or environment separation is implemented.',
['Static no-raw-log controls pass.','Redaction helper and correlation IDs reviewed.'],
['Submission/failure/vendor latency/rate-limit/duplicate/config/client/server/build/deployment metrics and alerts.'],
'P14-005 blocks production operations.'
))
write('CONTENT_EVIDENCE_REVIEW_v1.0.md', report(
'Phase 15 Content and Evidence Review','Static prohibited-content controls passed; approvals blocked',
'Runtime validation finds no TODO/TBD/lorem, invented metrics, fake quotes, guessed external destinations, or research-only backdoor. Public claims and assets still require accountable approval.',
['Prohibited-content validator passes.','No production image/logo/customer evidence asset is present.','Pending legal and unsupported destinations remain gated.'],
['Native linguistic approval, claims, customer logos/testimonials, security/compliance statements, pricing, screenshots, contact details, legal content.'],
'Content is structurally safe but not final-approved.'
))
write('SEO_METADATA_REVIEW_v1.0.md', report(
'Phase 15 SEO and Metadata Review','Static foundation passed; rendered validation blocked',
'Localized metadata, canonical, hreflang, Open Graph, Twitter, robots, and non-indexable placeholder controls pass static validation.',
['Metadata validator passes.','Five route IDs have complete locale alternates.','Public release defaults to noindex.'],
['Production origin, rendered status codes/redirects, sitemap if implemented, social assets, final metadata copy, crawl.'],
'SEO release approval awaits production domain, assets, build, and crawl evidence.'
))
write('BROWSER_HISTORY_REPORT_v1.0.md', report(
'Phase 15 Browser History and Navigation Report','Blocked',
'Source avoids form values in URLs and keeps the demo in a dialog on the localized homepage. Direct entry, locale/theme changes, back/forward, refresh, success, resubmit, deep links, and external return were not executed.',
['Static privacy audit finds no form-value URL construction.','Authored browser scenarios include success URL checks.'],
['Complete back/forward/refresh/modal/scheduler/hash/redirect history matrix and focus restoration.'],
'History behavior is unverified on the final candidate.'
))
write('RETEST_REPORT_v1.0.md', report(
'Phase 15 Retest Report','Static retest passed; full regression blocked',
'All four remediated Phase 15 defects pass targeted static or dependency-free retests. Browser and package-backed nearby regressions were not executable.',
['P15-001: CSV structure passes.','P15-002: exact media-type assertions and validator pass.','P15-003: canonical theme key and Chromium scope validator pass.','P15-004: CI integration command validator passes.'],
['Prettier/ESLint/typecheck/unit/integration/build.','Browser visual/theme/form regression.'],
'Resolved defects remain conditionally closed until the final connected CI run.'
))
# Final recommendation
recommendation = f'''# Phase 15 Final Release Recommendation
## Recommendation
**Not ready for Phase 16**
The repository has materially better static controls than the supplied Phase 14 baseline, but final QA is not complete. The exact clean installation, production build, dependency audit, unit/integration regression, cross-browser flows, automated accessibility, manual keyboard/screen-reader/zoom review, visual baselines, performance budgets, deployment rehearsal, and rollback rehearsal have not been executed against the final Phase 15 manifest.
## Defect and issue counts
- Critical unresolved: **{unresolved_counts.get('Critical',0)}**
- High unresolved: **{unresolved_counts.get('High',0)}**
- Medium unresolved: **{unresolved_counts.get('Medium',0)}**
- Low unresolved: **{unresolved_counts.get('Low',0)}**
- Phase 15 defects resolved: **4**
- Accepted exceptions: **0**
The cumulative counts include inherited business, research, legal, localization, design, integration, operations, and verification issues. The Phase 15 defect register contains one open High aggregate acceptance blocker, P15-005.
## Failed or blocked release gates
The blocked gates include exact runtime/frozen install, final package-backed checks, current unit/integration/build, dependency/license audit, browser/accessibility/visual/manual matrices, performance and bundle measurement, production-like deployment, rollback, CRM, legal/privacy consent, analytics approval, distributed idempotency/rate limiting, native localization review, participant acceptance research, production origin, final brand assets, destinations, and a real commit identifier.
## Required Phase 16 approvals
Phase 16 must not begin release-candidate freeze until engineering QA closes P15-005. Business/legal work still required includes final copy, claims/evidence, customer assets, legal/privacy text, analytics and consent, CRM/scheduler operations, production destinations, domain/hosting, credentials, launch ownership, and final assets.
## Required remediation before production
1. Commit the Phase 15 repository and record the SHA.
2. Run the complete command matrix on Node 24.17.0 and pnpm 11.9.0 from a frozen clean install.
3. Fix and retest every resulting Critical, High, and Medium defect.
4. Complete browser, accessibility, localization/RTL, theme, responsive, visual, performance, deployment, rollback, and observability evidence.
5. Resolve or formally approve every production business/legal gate. No Critical or High exception currently exists.
## Overall risk statement
Static source integrity is improved and production integrations remain safely disabled. Release risk is nevertheless **unacceptably high** because the final executable artifact has not been built or observed in browsers and assistive technologies, and the production data-processing and destination decisions are unresolved.
'''
write('FINAL_RELEASE_RECOMMENDATION_v1.0.md', recommendation)
# Risk register note
write('RISK_ACCEPTANCE_SUMMARY_v1.0.md', '# Phase 15 Risk Acceptance Summary\n\nNo accepted exceptions were created. `RISK_ACCEPTANCE_REGISTER_v1.0.csv` intentionally contains only its header.\n')
# Phase 15 implementation report
sections = [
('1. Executive summary','Phase 15 found and fixed four repository defects: corrupted release-evidence CSV rows, loose JSON media-type matching, nondeterministic visual-test setup, and missing CI integration coverage. Dependency-free validation passes. Final acceptance is blocked because the pinned clean install and all dependency-backed/browser/human/production-like gates could not run.'),
('2. Authoritative inputs reviewed','The Phase 15 prompt, complete Phase 14 repository, Phase 14 handoff, implementation report, decision log, issue register, manifest, route/destination/form/integration/data-flow/consent/analytics/security/privacy/performance/release-gate records, tests, CI, README, and inherited Phase 9 through Phase 13 contracts were reviewed.'),
('3. Test environment',f"Observed: Debian GNU/Linux 13, Node {get_version(['node','--version'])}, npm {get_version(['npm','--version'])}, Corepack {get_version(['corepack','--version'])}, Python {platform.python_version()}, Chromium 144.0.7559.96. Required: Node 24.17.0 and pnpm 11.9.0. The supplied ZIP contained no `.git` directory."),
('4. Clean-room baseline','Fresh extraction and Phase 14 manifest verification passed. Corepack could not fetch pnpm because registry DNS is unavailable; frozen install did not run.'),
('5. Repository integrity','Baseline static audit found 4 High and 7 Medium findings. After remediation, the same audit reports zero findings. Final artifact integrity is governed by the Phase 15 manifest.'),
('6. Static analysis','Sixteen dependency-free validators pass. JavaScript/non-JSX TypeScript syntax and the Python audit script pass. Prettier, ESLint, strict TypeScript, dead-code, dependency/license, and bundle-secret tooling are blocked.'),
('7. Unit testing','Current Vitest suite not run. A media-type regression test was authored and eight dependency-free assertions pass. Historical Phase 14 unit evidence is stale for this release candidate.'),
('8. Integration testing','Current integration suite not run. CI omission was fixed. Historical Phase 14 integration evidence is stale.'),
('9. End-to-end testing','Authored scenarios exist; no current browser execution.'),
('10. Cross-browser testing','Configured projects cover Chromium, Firefox, WebKit, mobile Chrome, and mobile Safari; no current results.'),
('11. Device and viewport testing','Required widths are represented in tests; rendered validation blocked.'),
('12. Zoom and reflow','No current 200/400 percent or OS scaling evidence.'),
('13. Automated accessibility','Axe suites authored; not executed.'),
('14. Keyboard accessibility','Static semantics reviewed; manual keyboard flow not run.'),
('15. Screen-reader testing','NVDA, VoiceOver, and TalkBack not run. No announcement transcript was invented.'),
('16. WCAG 2.2 AA assessment','Criterion-to-evidence matrix produced. Many criteria have static evidence only; conformance is not established.'),
('17. Localization QA','Catalog key parity passes. Native French and Arabic approval remains open.'),
('18. RTL QA','Document direction and logical CSS controls pass; Arabic end-to-end and AT review blocked.'),
('19. Theme QA','Theme foundations pass static checks; no-flash, system changes, contrast, and forced-colors browser evidence blocked. Visual key defect fixed.'),
('20. Visual regression','Canonical Chromium setup fixed. No baselines generated or approved.'),
('21. Design fidelity','Composition/token validators pass; rendered design comparison not performed.'),
('22. Performance','Approved budgets exist; no final build or browser metrics.'),
('23. Bundle analysis','No new runtime dependency added; final chunks and client boundaries not measured.'),
('24. Image and font performance','No production public assets added; final images/fonts/network behavior unmeasured.'),
('25. Security validation','Static request, schema, timeout, idempotency, CSP, redaction, and fail-closed controls pass. Content-type boundary hardened. Dynamic/dependency/vendor/rate-limit evidence blocked.'),
('26. Privacy validation','Static no-PII controls pass. Legal basis, recipients, retention, deletion, transfers, and runtime vendor/log evidence blocked.'),
('27. Consent validation','Optional processing remains disabled; complete consent lifecycle not implemented or tested.'),
('28. Analytics validation','Strict non-PII allowlist passes static review; provider and real consent gating not approved.'),
('29. Destination validation','Registry and routes pass static checks; most production destinations remain blocked or placeholder.'),
('30. Failure simulation','Deterministic scenarios authored; current runtime execution blocked.'),
('31. Idempotency and duplicate handling','Process-local design reviewed; durable cross-instance policy remains blocked.'),
('32. Abuse-control validation','Body size, marker, origin, schema, content type, and honeypot controls present; distributed rate limits absent.'),
('33. Deployment rehearsal','Not run for Phase 15.'),
('34. Rollback rehearsal','Feature-flag strategy documented; not rehearsed.'),
('35. Observability validation','Correlation/redaction foundations exist; monitoring, alerts, retention, and dashboards absent.'),
('36. Content and evidence review','Prohibited-content controls pass; final claims, evidence, assets, copy, and legal approvals remain open.'),
('37. SEO and metadata','Static foundations pass; production-domain and rendered crawl evidence blocked.'),
('38. Browser history and navigation','No PII URL construction found; final browser history matrix not run.'),
('39. Defect summary','Four defects fixed: two High and two Medium. One High Phase 15 acceptance blocker remains open.'),
('40. Retest summary','Targeted static/dependency-free retests pass. Full regression is blocked.'),
('41. Release-gate assessment','Three static gates pass. All executable, browser/human, performance, deployment, and production approval gates remain blocked/open.'),
('42. Accepted exceptions','None.'),
('43. Remaining risks',f"Cumulative unresolved register: Critical {unresolved_counts.get('Critical',0)}, High {unresolved_counts.get('High',0)}, Medium {unresolved_counts.get('Medium',0)}, Low {unresolved_counts.get('Low',0)}."),
('44. Files added, changed, and removed','Source changes harden content type; tests and CI are corrected; Phase 15 validators, reports, registers, and manifest are added. No authoritative Phase 9 contract was modified. File inventory is in `docs/quality/PHASE_15_FILE_CHANGES_v1.0.txt`.'),
('45. Exact commands required to reproduce validation','See `docs/phase15/COMMAND_MATRIX_v1.0.csv` and the Phase 16 handoff. Blocked commands must be run exactly on the pinned toolchain.'),
('46. Final release recommendation','**Not ready for Phase 16.** See `docs/phase15/FINAL_RELEASE_RECOMMENDATION_v1.0.md`.'),
('47. Phase 16 readiness assessment','Phase 16 may review blockers and prepare approvals, but must not freeze or deploy a release candidate until P15-005 and all Critical/High release gates are closed or explicitly approved by accountable owners. No exception is currently accepted.'),
]
impl = ['# Phase 15 Implementation Report','',f'**Date:** {DATE}','',*sum(([f'## {title}', '', body, ''] for title,body in sections),[])]
(ROOT/'docs/PHASE_15_IMPLEMENTATION_REPORT_v1.0.md').write_text('\n'.join(impl).rstrip()+'\n',encoding='utf-8')
# Phase 16 handoff
handoff = f'''# Phase 16 Handoff: Final Content Approval and Deployment Preparation
## Handoff status
Phase 15 recommendation: **Not ready for Phase 16**. This handoff exists to make the remaining work explicit, not to imply release approval.
## Artifact identity
- Supplied archive had no Git metadata; final validated commit identifier is **unavailable**.
- Use `PHASE_15_PACKAGE_MANIFEST_v1.0.json` and the delivered ZIP checksum to identify this artifact.
- Phase 16 must commit the exact package and record the immutable commit SHA before release-candidate freeze.
## Evidence package
Review `docs/PHASE_15_IMPLEMENTATION_REPORT_v1.0.md`, `docs/phase15/FINAL_RELEASE_RECOMMENDATION_v1.0.md`, `docs/phase15/DEFECT_REGISTER_v1.0.csv`, `docs/phase15/RELEASE_GATE_MATRIX_v1.0.csv`, `docs/phase15/RISK_ACCEPTANCE_REGISTER_v1.0.csv`, `docs/phase15/COMMAND_MATRIX_v1.0.csv`, and all Phase 15 reports.
## Production configuration inventory
Authoritative inventories remain `.env.example` and the Phase 14 environment, feature-flag, destination, external-service, data-flow, consent, and analytics registries. Production defaults remain blocked. No production credential is included.
## Required engineering validation before freeze
1. Run Node 24.17.0 and pnpm 11.9.0.
2. Run `pnpm install --frozen-lockfile`.
3. Run `pnpm validate`, `pnpm validate:package`, `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test:unit`, `pnpm test:integration`, `pnpm test:security`, `pnpm test:privacy`, and the production build.
4. Install pinned Playwright browsers and run browser, accessibility, and visual suites.
5. Complete manual keyboard, screen-reader, zoom/reflow, forced-colors, reduced-motion, Arabic RTL, and mobile device matrices.
6. Measure performance and bundles against the approved budgets.
7. Rehearse production-like deployment and rollback.
8. Close all Critical/High defects and retest Medium defects.
## Required business and operational approvals
- Final EN/FR/AR copy and native linguistic review
- Claims, evidence, customer logos, testimonials, and product captures
- Legal/privacy/consent text, legal basis, retention, deletion, recipients, and transfers
- Analytics provider, event approval, consent category, DPA, region, retention, and owner
- CRM lead destination, mapping, authentication, duplicate/retry policy, sandbox, monitoring, and owner
- Scheduler and email decisions if enabled
- Production origin, DNS, hosting, cache/security headers, redirects, robots, canonical, and hreflang
- Final logo, social assets, hero/product assets, and performance approval
- Production secrets through an approved secret manager with least privilege and rotation
- Launch owner matrix, launch-day checklist, incident/rollback authority, and post-launch monitoring
## Launch-day checklist
Freeze the approved commit and manifest; verify environment and secret inventory; deploy with production integrations disabled until each gate is approved; run localized route, demo, headers, metadata, analytics/consent, privacy, accessibility, performance, and destination smoke checks; confirm monitoring; record go/no-go decision.
## Post-launch smoke and monitoring checklist
Verify EN/FR/AR pages, light/dark/system, mobile/desktop, demo acceptance semantics, no duplicate leads, no PII in URLs/logs/analytics, CRM/scheduler latency, errors/timeouts/rate limits, consent withdrawal, security headers, canonical/hreflang/robots, Core Web Vitals, and rollback triggers.
## Files Phase 16 may modify
Approved copy/resources, final governed assets, production environment templates without secrets, approved destination/provider adapters, deployment configuration, launch documentation, and targeted tests/evidence.
## Files Phase 16 must not modify without reopening QA
Phase 9 contracts, component behavior, page structure, validation/schema, data flow, consent semantics, analytics event names/properties, integration interfaces, accessibility behavior, RTL/theme/routing foundations, CSP/security controls, or performance-critical loading behavior.
Any change in those areas requires targeted Phase 15 regression and updated evidence before release.
## Definition of done for release
A real commit SHA and manifest identify the candidate; all required commands pass; browser/AT/visual/performance/deployment/rollback evidence is approved; no unresolved Critical or High defect remains without accountable written exception; all production business/legal/operational gates are approved; post-deployment smoke checks pass; monitoring and rollback owners are active.
'''
(ROOT/'docs/PHASE_16_HANDOFF_v1.0.md').write_text(handoff,encoding='utf-8')
print('Generated Phase 15 reports and matrices.')
+43
View File
@@ -0,0 +1,43 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_16_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set(['.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__']);
const excludedFiles = new Set(['PHASE_16_PACKAGE_MANIFEST_v1.0.json', 'package-lock.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({ path: file.relative, bytes: details.size, sha256: createHash('sha256').update(contents).digest('hex') });
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 16,
generated_at: '2026-06-25',
release_status: 'Launch aborted before deployment',
launch_authorization: 'Rejected',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 16 manifest for ${entries.length} files.`);
+324
View File
@@ -0,0 +1,324 @@
#!/usr/bin/env python3
"""Dependency-free Phase 15 repository integrity, privacy, and evidence audit."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import os
import re
import stat
import sys
from collections import Counter
from pathlib import Path
from typing import Any
EXCLUDED_DIRS = {'.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__'}
EXCLUDED_AUDIT_OUTPUTS = {
'docs/quality/PHASE_15_STATIC_AUDIT_RESULTS_v1.0.json',
'docs/quality/PHASE_15_STATIC_AUDIT_RESULTS_v1.0.md',
'docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json',
'docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.md',
}
TEXT_SUFFIXES = {
'.css', '.csv', '.html', '.js', '.json', '.jsx', '.md', '.mjs', '.mts', '.txt', '.ts', '.tsx', '.yml', '.yaml'
}
RESERVED_EMAIL_SUFFIXES = ('.test', '.invalid', '.example')
ALLOWED_EMAIL_DOMAINS = {'example.com', 'example.org', 'example.net'}
SECRET_PATTERNS = {
'private_key': re.compile(r'-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'),
'aws_access_key': re.compile(r'\bAKIA[0-9A-Z]{16}\b'),
'google_api_key': re.compile(r'\bAIza[0-9A-Za-z_-]{35}\b'),
'github_token': re.compile(r'\bgh[pousr]_[0-9A-Za-z]{20,}\b'),
'slack_token': re.compile(r'\bxox[baprs]-[0-9A-Za-z-]{10,}\b'),
'stripe_secret': re.compile(r'\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b'),
}
EMAIL_RE = re.compile(r'(?<![\w.+-])([A-Za-z0-9._%+-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,}))')
RUNTIME_EXTERNAL_URL_RE = re.compile(r'https?://[^\s\)\]"\'<>]+')
def walk_files(root: Path):
for path in root.rglob('*'):
if any(part in EXCLUDED_DIRS for part in path.relative_to(root).parts):
continue
relative = path.relative_to(root).as_posix()
if relative in EXCLUDED_AUDIT_OUTPUTS:
continue
if path.is_file() or path.is_symlink():
yield path
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open('rb') as f:
for chunk in iter(lambda: f.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def read_text(path: Path) -> str | None:
if path.suffix.lower() not in TEXT_SUFFIXES and path.name not in {'.env.example', '.gitignore', '.npmrc', '.nvmrc', '.node-version', '.editorconfig', '.prettierignore', '.dockerignore'}:
return None
try:
return path.read_text(encoding='utf-8')
except UnicodeDecodeError:
return None
def add(findings: list[dict[str, Any]], severity: str, code: str, path: str, message: str, line: int | None = None):
item = {'severity': severity, 'code': code, 'path': path, 'message': message}
if line is not None:
item['line'] = line
findings.append(item)
def audit(root: Path) -> dict[str, Any]:
findings: list[dict[str, Any]] = []
metrics: dict[str, Any] = {}
files = list(walk_files(root))
regular_files = [p for p in files if p.is_file() and not p.is_symlink()]
metrics['file_count'] = len(regular_files)
metrics['total_bytes'] = sum(p.stat().st_size for p in regular_files)
metrics['suffix_counts'] = dict(sorted(Counter(p.suffix.lower() or '<none>' for p in regular_files).items()))
# Filesystem integrity.
for p in files:
rel = p.relative_to(root).as_posix()
if p.is_symlink():
add(findings, 'High', 'symlink_present', rel, f'Symlink target: {os.readlink(p)}')
elif p.stat().st_mode & stat.S_IWOTH:
add(findings, 'Medium', 'world_writable', rel, 'Repository file is world-writable.')
for directory in EXCLUDED_DIRS - {'.git'}:
if (root / directory).exists():
add(findings, 'Medium', 'generated_directory_present', directory, 'Generated/cache directory is present in the release package.')
for forbidden in ('.env', '.env.local', '.env.production', 'package-lock.json', 'tsconfig.tsbuildinfo'):
if (root / forbidden).exists():
add(findings, 'High' if forbidden.startswith('.env') else 'Medium', 'forbidden_root_file', forbidden, 'Unexpected generated or secret-bearing root file.')
# JSON and CSV structural validity.
json_count = 0
csv_count = 0
for p in regular_files:
rel = p.relative_to(root).as_posix()
if p.suffix.lower() == '.json':
json_count += 1
try:
json.loads(p.read_text(encoding='utf-8'))
except Exception as exc:
add(findings, 'High', 'invalid_json', rel, str(exc))
elif p.suffix.lower() == '.csv':
csv_count += 1
try:
with p.open(newline='', encoding='utf-8-sig') as f:
rows = list(csv.reader(f))
if rows:
width = len(rows[0])
for index, row in enumerate(rows, start=1):
if len(row) != width:
add(findings, 'High', 'malformed_csv', rel, f'Expected {width} columns, found {len(row)}.', index)
except Exception as exc:
add(findings, 'High', 'invalid_csv', rel, str(exc))
metrics['json_files_parsed'] = json_count
metrics['csv_files_parsed'] = csv_count
# Secret, PII-fixture, and unsafe runtime scans.
secret_scan_files = 0
email_hits: list[dict[str, Any]] = []
runtime_urls: list[dict[str, Any]] = []
for p in regular_files:
rel = p.relative_to(root).as_posix()
text = read_text(p)
if text is None:
continue
lines = text.splitlines()
if rel not in {'scripts/phase15-static-audit.py'} and not rel.startswith('PHASE_') and p.name != 'pnpm-lock.yaml':
secret_scan_files += 1
for name, pattern in SECRET_PATTERNS.items():
for match in pattern.finditer(text):
line = text.count('\n', 0, match.start()) + 1
add(findings, 'Critical', f'secret_{name}', rel, 'Potential live credential or private key pattern.', line)
if not rel.startswith('contracts/phase9/') and p.name != 'pnpm-lock.yaml':
for index, line_text in enumerate(lines, start=1):
for match in EMAIL_RE.finditer(line_text):
address, domain = match.group(1), match.group(2).lower()
email_hits.append({'path': rel, 'line': index, 'address': address})
if not domain.endswith(RESERVED_EMAIL_SUFFIXES) and domain not in ALLOWED_EMAIL_DOMAINS:
add(findings, 'High', 'non_reserved_email_fixture', rel, f'Non-reserved email address found: {address}', index)
if rel.startswith('src/') or rel in {'.env.example', 'next.config.ts'}:
for index, line_text in enumerate(lines, start=1):
for match in RUNTIME_EXTERNAL_URL_RE.finditer(line_text):
url = match.group(0).rstrip('.,;')
runtime_urls.append({'path': rel, 'line': index, 'url': url})
allowed = (
url.startswith('http://localhost')
or url.startswith('http://127.0.0.1')
or url.startswith('https://your-approved-domain.example')
or url.startswith('https://design-tokens.github.io/')
)
if not allowed:
add(findings, 'High', 'unapproved_runtime_url', rel, f'Runtime/config URL is not on the documented safe list: {url}', index)
metrics['secret_scan_files'] = secret_scan_files
metrics['email_fixture_hits'] = email_hits
metrics['runtime_url_hits'] = runtime_urls
# Runtime anti-patterns and release-control invariants.
runtime_text = ''
for p in regular_files:
rel = p.relative_to(root).as_posix()
if rel.startswith('src/'):
text = read_text(p)
if text is not None:
runtime_text += f'\n/* {rel} */\n{text}'
for index, line_text in enumerate(text.splitlines(), start=1):
if re.search(r'\b(TODO|TBD|FIXME|lorem ipsum)\b', line_text, re.I):
add(findings, 'Medium', 'runtime_placeholder', rel, line_text.strip(), index)
if re.search(r'console\.(?:log|info|warn|error|debug)\s*\(', line_text):
add(findings, 'High', 'runtime_console_logging', rel, 'Runtime console logging requires privacy review.', index)
if re.search(r'\beval\s*\(|new\s+Function\s*\(', line_text):
add(findings, 'Critical', 'dynamic_code_execution', rel, 'Dynamic code execution found.', index)
if "startsWith('application/json')" in runtime_text or 'startsWith("application/json")' in runtime_text:
add(findings, 'Medium', 'loose_content_type_match', 'src/', 'JSON API still uses a prefix media-type check.')
helper = root / 'src/lib/security/content-type.ts'
if not helper.exists() or "=== 'application/json'" not in helper.read_text(encoding='utf-8'):
add(findings, 'Medium', 'missing_exact_content_type_control', helper.relative_to(root).as_posix(), 'Exact application/json media-type helper is absent.')
route = root / 'src/app/api/demo/route.ts'
if route.exists() and 'isApplicationJson(contentType)' not in route.read_text(encoding='utf-8'):
add(findings, 'Medium', 'content_type_helper_not_used', route.relative_to(root).as_posix(), 'Demo API does not use exact JSON media-type validation.')
# Visual-test determinism invariants.
visual_component = root / 'tests/visual/component-system.visual.spec.ts'
if visual_component.exists():
text = visual_component.read_text(encoding='utf-8')
if 'rdg-theme-preference' in text:
add(findings, 'Medium', 'stale_theme_storage_key', visual_component.relative_to(root).as_posix(), 'Visual test writes a storage key the application never reads.')
if "hpc.theme.preference" not in text:
add(findings, 'Medium', 'theme_storage_key_missing', visual_component.relative_to(root).as_posix(), 'Visual test does not use the canonical theme storage key.')
if "project.name !== 'chromium'" not in text:
add(findings, 'Medium', 'visual_project_scope', visual_component.relative_to(root).as_posix(), 'Canonical snapshot test is not restricted to Chromium.')
visual_integrations = root / 'tests/visual/integrations.visual.spec.ts'
if visual_integrations.exists() and "project.name !== 'chromium'" not in visual_integrations.read_text(encoding='utf-8'):
add(findings, 'Medium', 'visual_project_scope', visual_integrations.relative_to(root).as_posix(), 'Canonical integration snapshots are not restricted to Chromium.')
# CI must execute the integration suite; unit and browser suites do not cover the server adapter orchestration.
workflow_path = root / '.github/workflows/ci.yml'
if workflow_path.exists():
workflow_text = workflow_path.read_text(encoding='utf-8')
if 'pnpm test:integration' not in workflow_text:
add(findings, 'High', 'ci_integration_suite_omitted', workflow_path.relative_to(root).as_posix(), 'CI does not execute the demo-service integration suite.')
# Lockfile/direct dependency checks are delegated to the repository validator; record pinning shape here.
package = json.loads((root / 'package.json').read_text(encoding='utf-8'))
direct = {**package.get('dependencies', {}), **package.get('devDependencies', {})}
floating = {name: value for name, value in direct.items() if re.search(r'[~^*xX<>| ]', str(value))}
if floating:
add(findings, 'High', 'floating_direct_dependencies', 'package.json', json.dumps(floating, sort_keys=True))
metrics['direct_dependency_count'] = len(direct)
metrics['direct_dependencies_exact'] = not floating
# Issue/gate metrics.
def issue_version(path: Path) -> tuple[int, ...]:
match = re.search(r'_v(\d+(?:\.\d+)*)\.csv$', path.name)
return tuple(int(part) for part in match.group(1).split('.')) if match else (0,)
issue_candidates = sorted(
(root / 'docs').glob('IMPLEMENTATION_ISSUE_REGISTER_v*.csv'),
key=issue_version,
)
issue_path = issue_candidates[-1] if issue_candidates else None
if issue_path is not None:
with issue_path.open(newline='', encoding='utf-8-sig') as f:
issues = list(csv.DictReader(f))
metrics['issue_register_path'] = issue_path.relative_to(root).as_posix()
metrics['issue_register_rows'] = len(issues)
unresolved = [r for r in issues if 'resolved' not in (r.get('Status') or '').lower()]
metrics['unresolved_issue_counts'] = dict(Counter(r.get('Severity', 'Unknown') for r in unresolved))
if any(None in r for r in issues):
add(findings, 'High', 'issue_register_overflow', issue_path.relative_to(root).as_posix(), 'One or more issue-register rows contain overflow columns.')
gate_candidates = [
root / 'docs/phase15/RELEASE_GATE_MATRIX_v1.0.csv',
root / 'docs/phase14/RELEASE_GATE_MATRIX_v1.0.csv',
]
gate_path = next((candidate for candidate in gate_candidates if candidate.exists()), None)
if gate_path is not None:
with gate_path.open(newline='', encoding='utf-8-sig') as f:
gates = list(csv.DictReader(f))
metrics['release_gate_path'] = gate_path.relative_to(root).as_posix()
metrics['release_gate_status_counts'] = dict(Counter(r.get('Status', 'Unknown') for r in gates))
# High-level privacy invariants.
api_text = route.read_text(encoding='utf-8') if route.exists() else ''
required_api_controls = {
'request_marker': "x-rdg-form",
'origin_validation': 'isAllowedOrigin',
'body_limit': 'maximumBodyBytes',
'no_store': "'Cache-Control': 'no-store'",
'honeypot': 'honeypot',
}
for code, token in required_api_controls.items():
if token not in api_text:
add(findings, 'High', f'missing_api_{code}', route.relative_to(root).as_posix(), f'Missing expected API control token: {token}')
analytics_text = (root / 'src/lib/analytics/events.ts').read_text(encoding='utf-8')
for forbidden in ('email', 'company', 'message', 'phone', 'fieldvalue', 'serverresponse'):
if f"'{forbidden}'" not in analytics_text:
add(findings, 'High', 'analytics_forbidden_key_missing', 'src/lib/analytics/events.ts', f'Forbidden analytics key is not explicitly denied: {forbidden}')
severity_rank = {'Critical': 4, 'High': 3, 'Medium': 2, 'Low': 1, 'Info': 0}
findings.sort(key=lambda x: (-severity_rank.get(x['severity'], 0), x['path'], x.get('line', 0), x['code']))
counts = Counter(item['severity'] for item in findings)
return {
'schema_version': '1.0',
'audit': 'Phase 15 dependency-free static repository audit',
'root': str(root),
'metrics': metrics,
'finding_counts': dict(counts),
'findings': findings,
'passed': not any(item['severity'] in {'Critical', 'High', 'Medium'} for item in findings),
}
def render_markdown(result: dict[str, Any]) -> str:
lines = [
'# Phase 15 Static Repository Audit',
'',
f"- Result: **{'PASS' if result['passed'] else 'FAIL'}**",
f"- Files inspected: {result['metrics']['file_count']}",
f"- JSON files parsed: {result['metrics']['json_files_parsed']}",
f"- CSV files parsed: {result['metrics']['csv_files_parsed']}",
f"- Secret-scan files: {result['metrics']['secret_scan_files']}",
f"- Findings: {json.dumps(result['finding_counts'], sort_keys=True)}",
'',
'## Findings',
'',
]
if not result['findings']:
lines.append('No Critical, High, Medium, Low, or informational findings were produced by this audit.')
else:
lines.append('| Severity | Code | Path | Line | Finding |')
lines.append('|---|---|---|---:|---|')
for item in result['findings']:
message = str(item['message']).replace('|', '\\|').replace('\n', ' ')
lines.append(f"| {item['severity']} | `{item['code']}` | `{item['path']}` | {item.get('line', '')} | {message} |")
lines.extend(['', '## Metrics', '', '```json', json.dumps(result['metrics'], indent=2, sort_keys=True), '```', ''])
return '\n'.join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--root', type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument('--json-output', type=Path)
parser.add_argument('--markdown-output', type=Path)
args = parser.parse_args()
result = audit(args.root.resolve())
if args.json_output:
args.json_output.parent.mkdir(parents=True, exist_ok=True)
args.json_output.write_text(json.dumps(result, indent=2, sort_keys=True) + '\n', encoding='utf-8')
if args.markdown_output:
args.markdown_output.parent.mkdir(parents=True, exist_ok=True)
args.markdown_output.write_text(render_markdown(result), encoding='utf-8')
print(json.dumps({'passed': result['passed'], 'finding_counts': result['finding_counts'], 'files': result['metrics']['file_count']}, sort_keys=True))
return 0 if result['passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())
+325
View File
@@ -0,0 +1,325 @@
#!/usr/bin/env python3
"""Dependency-free Phase 16 repository integrity, privacy, and release-governance audit."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import os
import re
import stat
import sys
from collections import Counter
from pathlib import Path
from typing import Any
EXCLUDED_DIRS = {'.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__'}
EXCLUDED_AUDIT_OUTPUTS = {
'docs/quality/PHASE_15_STATIC_AUDIT_RESULTS_v1.0.json',
'docs/quality/PHASE_15_STATIC_AUDIT_RESULTS_v1.0.md',
'docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.json',
'docs/quality/PHASE_15_BASELINE_STATIC_AUDIT_RESULTS_v1.0.md',
}
TEXT_SUFFIXES = {
'.css', '.csv', '.html', '.js', '.json', '.jsx', '.md', '.mjs', '.mts', '.txt', '.ts', '.tsx', '.yml', '.yaml'
}
RESERVED_EMAIL_SUFFIXES = ('.test', '.invalid', '.example')
ALLOWED_EMAIL_DOMAINS = {'example.com', 'example.org', 'example.net'}
SECRET_PATTERNS = {
'private_key': re.compile(r'-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'),
'aws_access_key': re.compile(r'\bAKIA[0-9A-Z]{16}\b'),
'google_api_key': re.compile(r'\bAIza[0-9A-Za-z_-]{35}\b'),
'github_token': re.compile(r'\bgh[pousr]_[0-9A-Za-z]{20,}\b'),
'slack_token': re.compile(r'\bxox[baprs]-[0-9A-Za-z-]{10,}\b'),
'stripe_secret': re.compile(r'\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b'),
}
EMAIL_RE = re.compile(r'(?<![\w.+-])([A-Za-z0-9._%+-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,}))')
RUNTIME_EXTERNAL_URL_RE = re.compile(r'https?://[^\s\)\]"\'<>]+')
def walk_files(root: Path):
for path in root.rglob('*'):
if any(part in EXCLUDED_DIRS for part in path.relative_to(root).parts):
continue
relative = path.relative_to(root).as_posix()
if relative in EXCLUDED_AUDIT_OUTPUTS:
continue
if path.is_file() or path.is_symlink():
yield path
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open('rb') as f:
for chunk in iter(lambda: f.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def read_text(path: Path) -> str | None:
if path.suffix.lower() not in TEXT_SUFFIXES and path.name not in {'.env.example', '.gitignore', '.npmrc', '.nvmrc', '.node-version', '.editorconfig', '.prettierignore', '.dockerignore'}:
return None
try:
return path.read_text(encoding='utf-8')
except UnicodeDecodeError:
return None
def add(findings: list[dict[str, Any]], severity: str, code: str, path: str, message: str, line: int | None = None):
item = {'severity': severity, 'code': code, 'path': path, 'message': message}
if line is not None:
item['line'] = line
findings.append(item)
def audit(root: Path) -> dict[str, Any]:
findings: list[dict[str, Any]] = []
metrics: dict[str, Any] = {}
files = list(walk_files(root))
regular_files = [p for p in files if p.is_file() and not p.is_symlink()]
metrics['file_count'] = len(regular_files)
metrics['total_bytes'] = sum(p.stat().st_size for p in regular_files)
metrics['suffix_counts'] = dict(sorted(Counter(p.suffix.lower() or '<none>' for p in regular_files).items()))
# Filesystem integrity.
for p in files:
rel = p.relative_to(root).as_posix()
if p.is_symlink():
add(findings, 'High', 'symlink_present', rel, f'Symlink target: {os.readlink(p)}')
elif p.stat().st_mode & stat.S_IWOTH:
add(findings, 'Medium', 'world_writable', rel, 'Repository file is world-writable.')
for directory in EXCLUDED_DIRS - {'.git'}:
if (root / directory).exists():
add(findings, 'Medium', 'generated_directory_present', directory, 'Generated/cache directory is present in the release package.')
for forbidden in ('.env', '.env.local', '.env.production', 'package-lock.json', 'tsconfig.tsbuildinfo'):
if (root / forbidden).exists():
add(findings, 'High' if forbidden.startswith('.env') else 'Medium', 'forbidden_root_file', forbidden, 'Unexpected generated or secret-bearing root file.')
# JSON and CSV structural validity.
json_count = 0
csv_count = 0
for p in regular_files:
rel = p.relative_to(root).as_posix()
if p.suffix.lower() == '.json':
json_count += 1
try:
json.loads(p.read_text(encoding='utf-8'))
except Exception as exc:
add(findings, 'High', 'invalid_json', rel, str(exc))
elif p.suffix.lower() == '.csv':
csv_count += 1
try:
with p.open(newline='', encoding='utf-8-sig') as f:
rows = list(csv.reader(f))
if rows:
width = len(rows[0])
for index, row in enumerate(rows, start=1):
if len(row) != width:
add(findings, 'High', 'malformed_csv', rel, f'Expected {width} columns, found {len(row)}.', index)
except Exception as exc:
add(findings, 'High', 'invalid_csv', rel, str(exc))
metrics['json_files_parsed'] = json_count
metrics['csv_files_parsed'] = csv_count
# Secret, PII-fixture, and unsafe runtime scans.
secret_scan_files = 0
email_hits: list[dict[str, Any]] = []
runtime_urls: list[dict[str, Any]] = []
for p in regular_files:
rel = p.relative_to(root).as_posix()
text = read_text(p)
if text is None:
continue
lines = text.splitlines()
if rel not in {'scripts/phase15-static-audit.py', 'scripts/phase16-static-audit.py'} and not rel.startswith('PHASE_') and p.name != 'pnpm-lock.yaml':
secret_scan_files += 1
for name, pattern in SECRET_PATTERNS.items():
for match in pattern.finditer(text):
line = text.count('\n', 0, match.start()) + 1
add(findings, 'Critical', f'secret_{name}', rel, 'Potential live credential or private key pattern.', line)
if not rel.startswith('contracts/phase9/') and p.name != 'pnpm-lock.yaml':
for index, line_text in enumerate(lines, start=1):
for match in EMAIL_RE.finditer(line_text):
address, domain = match.group(1), match.group(2).lower()
email_hits.append({'path': rel, 'line': index, 'address': address})
if not domain.endswith(RESERVED_EMAIL_SUFFIXES) and domain not in ALLOWED_EMAIL_DOMAINS:
add(findings, 'High', 'non_reserved_email_fixture', rel, f'Non-reserved email address found: {address}', index)
if rel.startswith('src/') or rel in {'.env.example', 'next.config.ts'}:
for index, line_text in enumerate(lines, start=1):
for match in RUNTIME_EXTERNAL_URL_RE.finditer(line_text):
url = match.group(0).rstrip('.,;')
runtime_urls.append({'path': rel, 'line': index, 'url': url})
allowed = (
url.startswith('http://localhost')
or url.startswith('http://127.0.0.1')
or url.startswith('https://your-approved-domain.example')
or url.startswith('https://design-tokens.github.io/')
)
if not allowed:
add(findings, 'High', 'unapproved_runtime_url', rel, f'Runtime/config URL is not on the documented safe list: {url}', index)
metrics['secret_scan_files'] = secret_scan_files
metrics['email_fixture_hits'] = email_hits
metrics['runtime_url_hits'] = runtime_urls
# Runtime anti-patterns and release-control invariants.
runtime_text = ''
for p in regular_files:
rel = p.relative_to(root).as_posix()
if rel.startswith('src/'):
text = read_text(p)
if text is not None:
runtime_text += f'\n/* {rel} */\n{text}'
for index, line_text in enumerate(text.splitlines(), start=1):
if re.search(r'\b(TODO|TBD|FIXME|lorem ipsum)\b', line_text, re.I):
add(findings, 'Medium', 'runtime_placeholder', rel, line_text.strip(), index)
if re.search(r'console\.(?:log|info|warn|error|debug)\s*\(', line_text):
add(findings, 'High', 'runtime_console_logging', rel, 'Runtime console logging requires privacy review.', index)
if re.search(r'\beval\s*\(|new\s+Function\s*\(', line_text):
add(findings, 'Critical', 'dynamic_code_execution', rel, 'Dynamic code execution found.', index)
if "startsWith('application/json')" in runtime_text or 'startsWith("application/json")' in runtime_text:
add(findings, 'Medium', 'loose_content_type_match', 'src/', 'JSON API still uses a prefix media-type check.')
helper = root / 'src/lib/security/content-type.ts'
if not helper.exists() or "=== 'application/json'" not in helper.read_text(encoding='utf-8'):
add(findings, 'Medium', 'missing_exact_content_type_control', helper.relative_to(root).as_posix(), 'Exact application/json media-type helper is absent.')
route = root / 'src/app/api/demo/route.ts'
if route.exists() and 'isApplicationJson(contentType)' not in route.read_text(encoding='utf-8'):
add(findings, 'Medium', 'content_type_helper_not_used', route.relative_to(root).as_posix(), 'Demo API does not use exact JSON media-type validation.')
# Visual-test determinism invariants.
visual_component = root / 'tests/visual/component-system.visual.spec.ts'
if visual_component.exists():
text = visual_component.read_text(encoding='utf-8')
if 'rdg-theme-preference' in text:
add(findings, 'Medium', 'stale_theme_storage_key', visual_component.relative_to(root).as_posix(), 'Visual test writes a storage key the application never reads.')
if "hpc.theme.preference" not in text:
add(findings, 'Medium', 'theme_storage_key_missing', visual_component.relative_to(root).as_posix(), 'Visual test does not use the canonical theme storage key.')
if "project.name !== 'chromium'" not in text:
add(findings, 'Medium', 'visual_project_scope', visual_component.relative_to(root).as_posix(), 'Canonical snapshot test is not restricted to Chromium.')
visual_integrations = root / 'tests/visual/integrations.visual.spec.ts'
if visual_integrations.exists() and "project.name !== 'chromium'" not in visual_integrations.read_text(encoding='utf-8'):
add(findings, 'Medium', 'visual_project_scope', visual_integrations.relative_to(root).as_posix(), 'Canonical integration snapshots are not restricted to Chromium.')
# CI must execute the integration suite; unit and browser suites do not cover the server adapter orchestration.
workflow_path = root / '.github/workflows/ci.yml'
if workflow_path.exists():
workflow_text = workflow_path.read_text(encoding='utf-8')
if 'pnpm test:integration' not in workflow_text:
add(findings, 'High', 'ci_integration_suite_omitted', workflow_path.relative_to(root).as_posix(), 'CI does not execute the demo-service integration suite.')
# Lockfile/direct dependency checks are delegated to the repository validator; record pinning shape here.
package = json.loads((root / 'package.json').read_text(encoding='utf-8'))
direct = {**package.get('dependencies', {}), **package.get('devDependencies', {})}
floating = {name: value for name, value in direct.items() if re.search(r'[~^*xX<>| ]', str(value))}
if floating:
add(findings, 'High', 'floating_direct_dependencies', 'package.json', json.dumps(floating, sort_keys=True))
metrics['direct_dependency_count'] = len(direct)
metrics['direct_dependencies_exact'] = not floating
# Issue/gate metrics.
def issue_version(path: Path) -> tuple[int, ...]:
match = re.search(r'_v(\d+(?:\.\d+)*)\.csv$', path.name)
return tuple(int(part) for part in match.group(1).split('.')) if match else (0,)
issue_candidates = sorted(
(root / 'docs').glob('IMPLEMENTATION_ISSUE_REGISTER_v*.csv'),
key=issue_version,
)
issue_path = issue_candidates[-1] if issue_candidates else None
if issue_path is not None:
with issue_path.open(newline='', encoding='utf-8-sig') as f:
issues = list(csv.DictReader(f))
metrics['issue_register_path'] = issue_path.relative_to(root).as_posix()
metrics['issue_register_rows'] = len(issues)
unresolved = [r for r in issues if 'resolved' not in (r.get('Status') or '').lower()]
metrics['unresolved_issue_counts'] = dict(Counter(r.get('Severity', 'Unknown') for r in unresolved))
if any(None in r for r in issues):
add(findings, 'High', 'issue_register_overflow', issue_path.relative_to(root).as_posix(), 'One or more issue-register rows contain overflow columns.')
gate_candidates = [
root / 'docs/phase16/FINAL_RELEASE_GATE_MATRIX_v1.0.csv',
root / 'docs/phase15/RELEASE_GATE_MATRIX_v1.0.csv',
root / 'docs/phase14/RELEASE_GATE_MATRIX_v1.0.csv',
]
gate_path = next((candidate for candidate in gate_candidates if candidate.exists()), None)
if gate_path is not None:
with gate_path.open(newline='', encoding='utf-8-sig') as f:
gates = list(csv.DictReader(f))
metrics['release_gate_path'] = gate_path.relative_to(root).as_posix()
metrics['release_gate_status_counts'] = dict(Counter(r.get('Phase16_decision') or r.get('Status', 'Unknown') for r in gates))
# High-level privacy invariants.
api_text = route.read_text(encoding='utf-8') if route.exists() else ''
required_api_controls = {
'request_marker': "x-rdg-form",
'origin_validation': 'isAllowedOrigin',
'body_limit': 'maximumBodyBytes',
'no_store': "'Cache-Control': 'no-store'",
'honeypot': 'honeypot',
}
for code, token in required_api_controls.items():
if token not in api_text:
add(findings, 'High', f'missing_api_{code}', route.relative_to(root).as_posix(), f'Missing expected API control token: {token}')
analytics_text = (root / 'src/lib/analytics/events.ts').read_text(encoding='utf-8')
for forbidden in ('email', 'company', 'message', 'phone', 'fieldvalue', 'serverresponse'):
if f"'{forbidden}'" not in analytics_text:
add(findings, 'High', 'analytics_forbidden_key_missing', 'src/lib/analytics/events.ts', f'Forbidden analytics key is not explicitly denied: {forbidden}')
severity_rank = {'Critical': 4, 'High': 3, 'Medium': 2, 'Low': 1, 'Info': 0}
findings.sort(key=lambda x: (-severity_rank.get(x['severity'], 0), x['path'], x.get('line', 0), x['code']))
counts = Counter(item['severity'] for item in findings)
return {
'schema_version': '1.0',
'audit': 'Phase 16 dependency-free static repository audit',
'root': str(root),
'metrics': metrics,
'finding_counts': dict(counts),
'findings': findings,
'passed': not any(item['severity'] in {'Critical', 'High', 'Medium'} for item in findings),
}
def render_markdown(result: dict[str, Any]) -> str:
lines = [
'# Phase 16 Static Repository Audit',
'',
f"- Result: **{'PASS' if result['passed'] else 'FAIL'}**",
f"- Files inspected: {result['metrics']['file_count']}",
f"- JSON files parsed: {result['metrics']['json_files_parsed']}",
f"- CSV files parsed: {result['metrics']['csv_files_parsed']}",
f"- Secret-scan files: {result['metrics']['secret_scan_files']}",
f"- Findings: {json.dumps(result['finding_counts'], sort_keys=True)}",
'',
'## Findings',
'',
]
if not result['findings']:
lines.append('No Critical, High, Medium, Low, or informational findings were produced by this audit.')
else:
lines.append('| Severity | Code | Path | Line | Finding |')
lines.append('|---|---|---|---:|---|')
for item in result['findings']:
message = str(item['message']).replace('|', '\\|').replace('\n', ' ')
lines.append(f"| {item['severity']} | `{item['code']}` | `{item['path']}` | {item.get('line', '')} | {message} |")
lines.extend(['', '## Metrics', '', '```json', json.dumps(result['metrics'], indent=2, sort_keys=True), '```', ''])
return '\n'.join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--root', type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument('--json-output', type=Path)
parser.add_argument('--markdown-output', type=Path)
args = parser.parse_args()
result = audit(args.root.resolve())
if args.json_output:
args.json_output.parent.mkdir(parents=True, exist_ok=True)
args.json_output.write_text(json.dumps(result, indent=2, sort_keys=True) + '\n', encoding='utf-8')
if args.markdown_output:
args.markdown_output.parent.mkdir(parents=True, exist_ok=True)
args.markdown_output.write_text(render_markdown(result), encoding='utf-8')
print(json.dumps({'passed': result['passed'], 'finding_counts': result['finding_counts'], 'files': result['metrics']['file_count']}, sort_keys=True))
return 0 if result['passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())
+4
View File
@@ -0,0 +1,4 @@
import { rm } from 'node:fs/promises';
await rm('.next/dev/types', { recursive: true, force: true });
console.log('Removed conflicting Next.js development route declarations before type checking.');
+3
View File
@@ -6,6 +6,9 @@ const commands = [
['pnpm', ['lint']],
['pnpm', ['typecheck']],
['pnpm', ['test:unit']],
['pnpm', ['test:integration']],
['pnpm', ['test:security']],
['pnpm', ['test:privacy']],
['pnpm', ['build'], { SITE_ORIGIN: 'http://127.0.0.1:3000', PUBLIC_RELEASE_APPROVED: 'false' }],
['pnpm', ['audit']],
];
+6
View File
@@ -12,6 +12,12 @@ const scripts = [
'validate-metadata.mjs',
'validate-phase12-components.mjs',
'validate-phase13-homepage.mjs',
'validate-phase14-integrations.mjs',
'validate-phase14-environment.mjs',
'validate-phase14-security.mjs',
'validate-phase14-privacy.mjs',
'validate-phase15-readiness.mjs',
'validate-phase16-readiness.mjs',
];
for (const script of scripts) {
const result = spawnSync(process.execPath, [`scripts/${script}`], { stdio: 'inherit' });
+35
View File
@@ -0,0 +1,35 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const failures = [];
const example = await readFile(path.join(repositoryRoot, '.env.example'), 'utf8');
const implementation = await readFile(
path.join(repositoryRoot, 'src/lib/integrations/environment.ts'),
'utf8',
);
const keys = [
'NEXT_PUBLIC_DEMO_ENABLED',
'DEMO_SUBMISSION_MODE',
'DEMO_REQUEST_TIMEOUT_MS',
'DEMO_IDEMPOTENCY_TTL_SECONDS',
'DEMO_TEST_SCENARIOS_ENABLED',
'DEMO_LOCAL_TEST_MODE',
'NEXT_PUBLIC_ANALYTICS_MODE',
];
for (const key of keys) {
if (!example.includes(`${key}=`)) failures.push(`.env.example omits ${key}.`);
if (!implementation.includes(key)) failures.push(`Environment validator omits ${key}.`);
}
if (!example.includes('DEMO_SUBMISSION_MODE=blocked')) {
failures.push('Safe production example does not default demo submission to blocked.');
}
if (!example.includes('NEXT_PUBLIC_ANALYTICS_MODE=disabled')) {
failures.push('Safe production example does not default analytics to disabled.');
}
if (failures.length) fail(failures);
else
pass(
'Phase 14 environment inventory is explicit and defaults production integrations to blocked.',
);
+72
View File
@@ -0,0 +1,72 @@
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const failures = [];
const requiredFiles = [
'src/app/api/demo/route.ts',
'src/components/integrations/DemoDialogHost.tsx',
'src/lib/demo/schema.ts',
'src/lib/demo/service.ts',
'src/lib/demo/idempotency.ts',
'src/lib/demo/adapters/types.ts',
'src/lib/demo/adapters/local.ts',
'src/lib/demo/adapters/blocked.ts',
'src/lib/integrations/destinations.ts',
'src/lib/integrations/environment.ts',
'src/lib/integrations/feature-flags.ts',
'src/lib/analytics/events.ts',
'src/lib/analytics/client.ts',
'docs/phase14/INTEGRATION_TRACEABILITY_MATRIX_v1.0.csv',
'docs/phase14/DATA_FLOW_INVENTORY_v1.0.csv',
'docs/phase14/RELEASE_GATE_MATRIX_v1.0.csv',
];
for (const file of requiredFiles) {
try {
const details = await stat(path.join(repositoryRoot, file));
if (!details.isFile()) failures.push(`${file}: not a file.`);
} catch {
failures.push(`${file}: missing.`);
}
}
const schema = await readFile(path.join(repositoryRoot, 'src/lib/demo/schema.ts'), 'utf8');
for (const field of ['fullName', 'workEmail', 'company', 'fleetSize', 'idempotencyKey', 'source']) {
if (!schema.includes(`${field}:`)) failures.push(`Demo schema is missing ${field}.`);
}
if (!schema.includes('.strict()'))
failures.push('Demo transport schemas must reject unknown fields.');
if (schema.includes('phone:'))
failures.push('Unapproved phone field was added to the demo contract.');
const destinations = await readFile(
path.join(repositoryRoot, 'src/lib/integrations/destinations.ts'),
'utf8',
);
for (const issue of ['P9-002', 'P9-003', 'P9-004', 'P9-008', 'P9-012', 'P9-014', 'P9-015']) {
if (!destinations.includes(issue))
failures.push(`Destination registry does not preserve ${issue}.`);
}
if (/href:\s*['"]https?:\/\//.test(destinations)) {
failures.push('Destination registry contains an unapproved hard-coded external URL.');
}
for (const locale of ['en', 'fr', 'ar']) {
const catalog = JSON.parse(
await readFile(
path.join(repositoryRoot, `src/content/locales/${locale}/homepage.json`),
'utf8',
),
);
if (!catalog.form || catalog.form.fleetOptions?.length !== 7) {
failures.push(
`${locale}: demo form fleet options do not match the approved six-value contract.`,
);
}
}
if (failures.length) fail(failures);
else
pass(
'Phase 14 integration structure, form contract, destinations, and locale resources are present.',
);
+69
View File
@@ -0,0 +1,69 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_14_PACKAGE_MANIFEST_v1.0.json';
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([manifestName, 'package-lock.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
}
return files;
}
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 14) {
failures.push('Phase 14 manifest identity is invalid.');
}
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length) {
failures.push('Manifest file count is invalid.');
}
const listed = new Set();
for (const entry of manifest.files ?? []) {
if (
typeof entry.path !== 'string' ||
path.isAbsolute(entry.path) ||
entry.path.split('/').includes('..')
) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
continue;
}
if (listed.has(entry.path)) {
failures.push(`${entry.path}: duplicate manifest entry.`);
continue;
}
listed.add(entry.path);
try {
const filePath = path.join(repositoryRoot, entry.path);
const details = await stat(filePath);
if (!details.isFile() || details.size !== entry.bytes)
failures.push(`${entry.path}: file metadata changed.`);
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
const actual = new Set(await walk(repositoryRoot));
for (const file of actual)
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
for (const file of listed)
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
if (failures.length) fail(failures);
else pass(`Phase 14 package manifest verifies ${listed.size} repository files with no extras.`);
+35
View File
@@ -0,0 +1,35 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const failures = [];
const analytics = await readFile(path.join(repositoryRoot, 'src/lib/analytics/events.ts'), 'utf8');
const client = await readFile(path.join(repositoryRoot, 'src/lib/analytics/client.ts'), 'utf8');
const dialog = await readFile(
path.join(repositoryRoot, 'src/components/integrations/DemoDialogHost.tsx'),
'utf8',
);
for (const forbidden of ['email', 'company', 'message', 'phone', 'fieldvalue', 'serverresponse']) {
if (!analytics.toLowerCase().includes(`'${forbidden}'`)) {
failures.push(`Analytics forbidden-key control omits ${forbidden}.`);
}
}
if (!client.includes("dataset.analyticsConsent === 'granted'")) {
failures.push('Analytics test adapter is not consent-gated.');
}
if (!client.includes("analyticsMode() !== 'test'")) {
failures.push('Analytics adapter lacks an explicit disabled-by-default mode.');
}
if (/localStorage|sessionStorage/.test(dialog)) {
failures.push('Demo form persists lead data in browser storage.');
}
if (/URLSearchParams|searchParams\.set/.test(dialog)) {
failures.push('Demo form can place form data in a URL.');
}
if (failures.length) fail(failures);
else
pass(
'Phase 14 privacy controls prevent browser persistence, URL leakage, and unconsented analytics.',
);
+43
View File
@@ -0,0 +1,43 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const failures = [];
const route = await readFile(path.join(repositoryRoot, 'src/app/api/demo/route.ts'), 'utf8');
const service = await readFile(path.join(repositoryRoot, 'src/lib/demo/service.ts'), 'utf8');
const environment = await readFile(
path.join(repositoryRoot, 'src/lib/integrations/environment.ts'),
'utf8',
);
for (const control of [
"request.headers.get('origin')",
"request.headers.get('content-type')",
"request.headers.get('content-length')",
"request.headers.get('x-rdg-form')",
'maximumBodyBytes',
'demoSubmissionEnvelopeSchema.safeParse',
'Cache-Control',
]) {
if (!route.includes(control)) failures.push(`Submission API is missing control: ${control}`);
}
if (!service.includes('AbortController'))
failures.push('Integration orchestration has no timeout controller.');
if (!service.includes('executeIdempotent'))
failures.push('Integration orchestration bypasses idempotency.');
if (!environment.includes('DEMO_LOCAL_TEST_MODE'))
failures.push('Loopback test escape is not explicit.');
if (!environment.includes("['127.0.0.1', 'localhost']")) {
failures.push('Production-like local adapter escape is not restricted to loopback origins.');
}
const sourceFiles = [route, service];
if (sourceFiles.some((source) => /console\.(log|info|debug|warn|error)\s*\(/.test(source))) {
failures.push('Submission path writes diagnostics directly to the console.');
}
if (failures.length) fail(failures);
else
pass(
'Phase 14 API boundary includes origin, content-type, size, timeout, idempotency, and no-raw-log controls.',
);
+75
View File
@@ -0,0 +1,75 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_15_PACKAGE_MANIFEST_v1.0.json';
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
'__pycache__',
]);
const excludedFiles = new Set([
manifestName,
'package-lock.json',
'tsconfig.tsbuildinfo',
'docs/quality/phase15-command-logs/12-phase15-package-manifest.log',
]);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
}
return files;
}
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 15) {
failures.push('Phase 15 manifest identity is invalid.');
}
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length) {
failures.push('Manifest file count is invalid.');
}
const listed = new Set();
for (const entry of manifest.files ?? []) {
if (
typeof entry.path !== 'string' ||
path.isAbsolute(entry.path) ||
entry.path.split('/').includes('..')
) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
continue;
}
if (listed.has(entry.path)) {
failures.push(`${entry.path}: duplicate manifest entry.`);
continue;
}
listed.add(entry.path);
try {
const filePath = path.join(repositoryRoot, entry.path);
const details = await stat(filePath);
if (!details.isFile() || details.size !== entry.bytes)
failures.push(`${entry.path}: file metadata changed.`);
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
const actual = new Set(await walk(repositoryRoot));
for (const file of actual)
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
for (const file of listed)
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
if (failures.length) fail(failures);
else pass(`Phase 15 package manifest verifies ${listed.size} repository files with no extras.`);
+111
View File
@@ -0,0 +1,111 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
function parseCsv(text) {
const rows = [];
let row = [];
let field = '';
let quoted = false;
for (let index = 0; index < text.length; index += 1) {
const character = text[index];
if (quoted) {
if (character === '"' && text[index + 1] === '"') {
field += '"';
index += 1;
} else if (character === '"') {
quoted = false;
} else {
field += character;
}
} else if (character === '"') {
quoted = true;
} else if (character === ',') {
row.push(field);
field = '';
} else if (character === '\n') {
row.push(field.replace(/\r$/, ''));
rows.push(row);
row = [];
field = '';
} else {
field += character;
}
}
if (quoted) throw new Error('Unterminated CSV quote.');
if (field || row.length) {
row.push(field.replace(/\r$/, ''));
rows.push(row);
}
return rows;
}
const failures = [];
for (const file of [
'docs/IMPLEMENTATION_ISSUE_REGISTER_v1.4.csv',
'docs/quality/PHASE_14_MANUAL_QA_RESULTS_v1.0.csv',
]) {
const rows = parseCsv(await readFile(path.join(repositoryRoot, file), 'utf8'));
const expected = rows[0]?.length ?? 0;
for (const [index, row] of rows.entries()) {
if (row.length !== expected) {
failures.push(`${file}:${index + 1} has ${row.length} columns; expected ${expected}.`);
}
}
}
const contentType = await readFile(
path.join(repositoryRoot, 'src/lib/security/content-type.ts'),
'utf8',
);
const route = await readFile(path.join(repositoryRoot, 'src/app/api/demo/route.ts'), 'utf8');
if (!contentType.includes("=== 'application/json'")) {
failures.push('Exact JSON media-type comparison is missing.');
}
if (!route.includes('isApplicationJson(contentType)')) {
failures.push('Demo API does not use the exact JSON media-type helper.');
}
if (route.includes("startsWith('application/json')")) {
failures.push('Demo API still accepts look-alike JSON media types by prefix.');
}
const componentVisual = await readFile(
path.join(repositoryRoot, 'tests/visual/component-system.visual.spec.ts'),
'utf8',
);
const integrationVisual = await readFile(
path.join(repositoryRoot, 'tests/visual/integrations.visual.spec.ts'),
'utf8',
);
if (!componentVisual.includes("hpc.theme.preference")) {
failures.push('Component visual test does not use the canonical theme storage key.');
}
if (componentVisual.includes('rdg-theme-preference')) {
failures.push('Component visual test still uses the stale theme storage key.');
}
for (const [name, text] of [
['component-system.visual.spec.ts', componentVisual],
['integrations.visual.spec.ts', integrationVisual],
]) {
if (!text.includes("project.name !== 'chromium'")) {
failures.push(`${name} is not restricted to canonical Chromium snapshots.`);
}
}
const workflow = await readFile(path.join(repositoryRoot, '.github/workflows/ci.yml'), 'utf8');
for (const command of [
'pnpm test:integration',
'pnpm test:security',
'pnpm test:privacy',
'pnpm test:browser',
'pnpm test:a11y',
'pnpm test:visual',
]) {
if (!workflow.includes(command)) failures.push(`CI omits required command: ${command}`);
}
if (failures.length) fail(failures);
else
pass(
'Phase 15 evidence CSVs, exact media-type control, deterministic visual setup, and CI test gates are present.',
);
+46
View File
@@ -0,0 +1,46 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_16_PACKAGE_MANIFEST_v1.0.json';
const excludedDirectories = new Set(['.git', '.next', 'node_modules', 'coverage', 'playwright-report', 'test-results', '__pycache__']);
const excludedFiles = new Set([manifestName, 'package-lock.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
}
return files;
}
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 16) failures.push('Phase 16 manifest identity is invalid.');
if (manifest.release_status !== 'Launch aborted before deployment') failures.push('Manifest release status is not the recorded Phase 16 decision.');
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length) failures.push('Manifest file count is invalid.');
const listed = new Set();
for (const entry of manifest.files ?? []) {
if (typeof entry.path !== 'string' || path.isAbsolute(entry.path) || entry.path.split('/').includes('..')) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`); continue;
}
if (listed.has(entry.path)) { failures.push(`${entry.path}: duplicate manifest entry.`); continue; }
listed.add(entry.path);
try {
const filePath = path.join(repositoryRoot, entry.path);
const details = await stat(filePath);
if (!details.isFile() || details.size !== entry.bytes) failures.push(`${entry.path}: file metadata changed.`);
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch { failures.push(`${entry.path}: file is missing.`); }
}
const actual = new Set(await walk(repositoryRoot));
for (const file of actual) if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
for (const file of listed) if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
if (failures.length) fail(failures);
else pass(`Phase 16 package manifest verifies ${listed.size} repository files with no extras.`);
+31
View File
@@ -0,0 +1,31 @@
import { readFile, access } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const required = [
'docs/PHASE_16_IMPLEMENTATION_AND_LAUNCH_REPORT_v1.0.md',
'docs/DECISION_LOG_v16.0.md',
'docs/IMPLEMENTATION_ISSUE_REGISTER_v1.6.csv',
'docs/phase16/FINAL_LAUNCH_STATUS_v1.0.md',
'docs/phase16/LAUNCH_AUTHORIZATION_RECORD_v1.0.md',
'docs/phase16/FINAL_RELEASE_GATE_MATRIX_v1.0.csv',
'docs/phase16/COMMAND_AND_PLATFORM_ACTION_MATRIX_v1.0.csv',
'docs/phase16/DEFERRED_WORK_REGISTER_v1.0.csv',
'docs/phase16/FEATURE_FLAG_RELEASE_STATE_v1.0.csv',
'docs/phase16/SECRET_PROVISIONING_RECORD_v1.0.csv',
];
const failures = [];
for (const file of required) {
try { await access(path.join(repositoryRoot, file)); }
catch { failures.push(`Missing required Phase 16 artifact: ${file}`); }
}
const status = await readFile(path.join(repositoryRoot, 'docs/phase16/FINAL_LAUNCH_STATUS_v1.0.md'), 'utf8');
const auth = await readFile(path.join(repositoryRoot, 'docs/phase16/LAUNCH_AUTHORIZATION_RECORD_v1.0.md'), 'utf8');
const flags = await readFile(path.join(repositoryRoot, 'docs/phase16/FEATURE_FLAG_RELEASE_STATE_v1.0.csv'), 'utf8');
const secrets = await readFile(path.join(repositoryRoot, 'docs/phase16/SECRET_PROVISIONING_RECORD_v1.0.csv'), 'utf8');
if (!status.includes('Launch aborted before deployment')) failures.push('Final launch status is missing or inconsistent.');
if (!auth.includes('**Rejected**')) failures.push('Launch authorization is not explicitly rejected.');
if (/\btrue\b/.test(flags.split('\n').slice(1).map((line) => line.split(',')[1]).join('\n'))) failures.push('A Phase 16 production feature flag is enabled.');
if (!secrets.includes('No production credentials exist') && !secrets.includes('No credential supplied')) failures.push('Secret provisioning record does not explicitly preserve missing credentials as blockers.');
if (failures.length) fail(failures);
else pass('Phase 16 no-go governance, disabled feature state, blocked approvals, and evidence package are explicit and internally consistent.');