comber 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +732 -0
  3. package/action.yml +279 -0
  4. package/comber.config.example.json +64 -0
  5. package/dist/api/classify.js +99 -0
  6. package/dist/api/classify.js.map +1 -0
  7. package/dist/api/driver.js +235 -0
  8. package/dist/api/driver.js.map +1 -0
  9. package/dist/api/graphql-driver.js +150 -0
  10. package/dist/api/graphql-driver.js.map +1 -0
  11. package/dist/api/graphql.js +115 -0
  12. package/dist/api/graphql.js.map +1 -0
  13. package/dist/api/har-driver.js +167 -0
  14. package/dist/api/har-driver.js.map +1 -0
  15. package/dist/api/har.js +100 -0
  16. package/dist/api/har.js.map +1 -0
  17. package/dist/api/load.js +83 -0
  18. package/dist/api/load.js.map +1 -0
  19. package/dist/api/openapi.js +159 -0
  20. package/dist/api/openapi.js.map +1 -0
  21. package/dist/api/request.js +141 -0
  22. package/dist/api/request.js.map +1 -0
  23. package/dist/api/schema.js +126 -0
  24. package/dist/api/schema.js.map +1 -0
  25. package/dist/cli.js +352 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/comment.js +164 -0
  28. package/dist/comment.js.map +1 -0
  29. package/dist/config.js +664 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/connectors/builtin/discord.js +55 -0
  32. package/dist/connectors/builtin/github-checks.js +53 -0
  33. package/dist/connectors/builtin/junit.js +70 -0
  34. package/dist/connectors/builtin/linear.js +58 -0
  35. package/dist/connectors/builtin/slack.js +60 -0
  36. package/dist/connectors/builtin/webhook.js +39 -0
  37. package/dist/connectors/index.js +3 -0
  38. package/dist/connectors/loader.js +72 -0
  39. package/dist/connectors/registry.js +28 -0
  40. package/dist/connectors/types.js +23 -0
  41. package/dist/core/action-gate.js +48 -0
  42. package/dist/core/baseline.js +85 -0
  43. package/dist/core/baseline.js.map +1 -0
  44. package/dist/core/blind-spots.js +22 -0
  45. package/dist/core/blind-spots.js.map +1 -0
  46. package/dist/core/contract-extras.js +1 -0
  47. package/dist/core/contract-extras.js.map +1 -0
  48. package/dist/core/contract.js +1 -0
  49. package/dist/core/contract.js.map +1 -0
  50. package/dist/core/coverage.js +70 -0
  51. package/dist/core/coverage.js.map +1 -0
  52. package/dist/core/crawl.js +229 -0
  53. package/dist/core/crawl.js.map +1 -0
  54. package/dist/core/egress.js +128 -0
  55. package/dist/core/egress.js.map +1 -0
  56. package/dist/core/ledger.js +41 -0
  57. package/dist/core/ledger.js.map +1 -0
  58. package/dist/core/net.js +39 -0
  59. package/dist/core/pipeline.js +191 -0
  60. package/dist/core/pipeline.js.map +1 -0
  61. package/dist/core/plan.js +121 -0
  62. package/dist/core/plan.js.map +1 -0
  63. package/dist/core/redact.js +240 -0
  64. package/dist/core/redact.js.map +1 -0
  65. package/dist/core/replay.js +263 -0
  66. package/dist/core/replay.js.map +1 -0
  67. package/dist/core/report.js +253 -0
  68. package/dist/core/report.js.map +1 -0
  69. package/dist/core/safety-judge.js +116 -0
  70. package/dist/core/safety.js +92 -0
  71. package/dist/core/safety.js.map +1 -0
  72. package/dist/core/signal-normalize.js +190 -0
  73. package/dist/core/signal-normalize.js.map +1 -0
  74. package/dist/core/signals.js +5 -0
  75. package/dist/core/signals.js.map +1 -0
  76. package/dist/core/triage.js +104 -0
  77. package/dist/core/triage.js.map +1 -0
  78. package/dist/core/types.js +1 -0
  79. package/dist/core/types.js.map +1 -0
  80. package/dist/desktop/desktop-profile.js +6 -0
  81. package/dist/desktop/desktop-profile.js.map +1 -0
  82. package/dist/desktop/driver.js +433 -0
  83. package/dist/desktop/driver.js.map +1 -0
  84. package/dist/desktop/evidence.js +206 -0
  85. package/dist/desktop/evidence.js.map +1 -0
  86. package/dist/desktop/macos/accessibility-runner.js +118 -0
  87. package/dist/desktop/macos/accessibility-runner.js.map +1 -0
  88. package/dist/desktop/macos/types.js +1 -0
  89. package/dist/desktop/macos/types.js.map +1 -0
  90. package/dist/desktop/preflight.js +107 -0
  91. package/dist/desktop/preflight.js.map +1 -0
  92. package/dist/desktop/proxy-proof.js +185 -0
  93. package/dist/desktop/proxy-proof.js.map +1 -0
  94. package/dist/desktop/view.js +348 -0
  95. package/dist/desktop/view.js.map +1 -0
  96. package/dist/desktop/windows/types.js +1 -0
  97. package/dist/desktop/windows/types.js.map +1 -0
  98. package/dist/desktop/windows/uia-runner.js +127 -0
  99. package/dist/desktop/windows/uia-runner.js.map +1 -0
  100. package/dist/native/apple.js +14 -0
  101. package/dist/native/apple.js.map +1 -0
  102. package/dist/native/device-profile.js +6 -0
  103. package/dist/native/device-profile.js.map +1 -0
  104. package/dist/native/driver.js +416 -0
  105. package/dist/native/driver.js.map +1 -0
  106. package/dist/native/evidence.js +164 -0
  107. package/dist/native/evidence.js.map +1 -0
  108. package/dist/native/load.js +9 -0
  109. package/dist/native/load.js.map +1 -0
  110. package/dist/native/maestro.js +153 -0
  111. package/dist/native/maestro.js.map +1 -0
  112. package/dist/native/preflight.js +72 -0
  113. package/dist/native/preflight.js.map +1 -0
  114. package/dist/native/proxy-proof.js +182 -0
  115. package/dist/native/proxy-proof.js.map +1 -0
  116. package/dist/native/samsung.js +34 -0
  117. package/dist/native/samsung.js.map +1 -0
  118. package/dist/native/signals.js +50 -0
  119. package/dist/native/signals.js.map +1 -0
  120. package/dist/native/view.js +139 -0
  121. package/dist/native/view.js.map +1 -0
  122. package/dist/profile.js +237 -0
  123. package/dist/profile.js.map +1 -0
  124. package/dist/sink.js +134 -0
  125. package/dist/sink.js.map +1 -0
  126. package/dist/sinks/file.js +49 -0
  127. package/dist/sinks/file.js.map +1 -0
  128. package/dist/sinks/http.js +38 -0
  129. package/dist/sinks/http.js.map +1 -0
  130. package/dist/sinks/index.js +5 -0
  131. package/dist/sinks/index.js.map +1 -0
  132. package/dist/sinks/multi.js +40 -0
  133. package/dist/sinks/multi.js.map +1 -0
  134. package/dist/sinks/paths.js +32 -0
  135. package/dist/sinks/paths.js.map +1 -0
  136. package/dist/sinks/record.js +58 -0
  137. package/dist/sinks/record.js.map +1 -0
  138. package/dist/sinks/types.js +1 -0
  139. package/dist/sinks/types.js.map +1 -0
  140. package/dist/web/breakage.js +210 -0
  141. package/dist/web/breakage.js.map +1 -0
  142. package/dist/web/controls.js +102 -0
  143. package/dist/web/controls.js.map +1 -0
  144. package/dist/web/driver.js +803 -0
  145. package/dist/web/driver.js.map +1 -0
  146. package/dist/web/elements.js +151 -0
  147. package/dist/web/elements.js.map +1 -0
  148. package/dist/web/fingerprint.js +13 -0
  149. package/dist/web/fingerprint.js.map +1 -0
  150. package/dist/web/forms.js +74 -0
  151. package/dist/web/forms.js.map +1 -0
  152. package/dist/web/links.js +194 -0
  153. package/dist/web/links.js.map +1 -0
  154. package/dist/web/types.js +1 -0
  155. package/dist/web/types.js.map +1 -0
  156. package/package.json +80 -0
  157. package/runtime/macos-accessibility-runner.js +275 -0
  158. package/runtime/windows-uia-runner.ps1 +539 -0
@@ -0,0 +1,153 @@
1
+ // The MaestroRunner transport seam — the pluggable boundary between the NativeDriver and the
2
+ // real `maestro` CLI / Maestro MCP / a booted emulator|simulator. It mirrors the ApiDriver's
3
+ // `ApiTransport`: the driver depends ONLY on this interface, so the whole driver is testable
4
+ // hermetically by injecting a stub (no device, no maestro binary). The default shell-out
5
+ // implementation below is best-effort and REQUIRES `maestro` + a booted device to do anything.
6
+ //
7
+ // Dependency arrow: this file imports nothing from `core`, `web`, or `api` — it is a leaf
8
+ // transport type plus a stdlib-only default impl. `src/native/*` may import it; `core` never does.
9
+ import { execFile } from "node:child_process";
10
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ /** Promisified execFile that resolves { stdout } and rejects on a non-zero exit / timeout. */
14
+ function run(bin, args, opts) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = execFile(bin, args, { timeout: opts.timeoutMs, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
17
+ if (err)
18
+ return reject(err);
19
+ resolve({ stdout: String(stdout), stderr: String(stderr) });
20
+ });
21
+ if (opts.input !== undefined) {
22
+ child.stdin?.end(opts.input);
23
+ }
24
+ });
25
+ }
26
+ /**
27
+ * REQUIRES `maestro` on PATH + a booted emulator/simulator (or a Maestro Cloud session). This
28
+ * is a BEST-EFFORT default: it composes ephemeral one-command Maestro flows for launch/tap/
29
+ * input/back, parses `maestro hierarchy` for the view tree, and reads `adb logcat`/`xcrun simctl`
30
+ * for crash/ANR logs. None of it runs in the hermetic test suite — the tests inject a stub
31
+ * MaestroRunner. Treat the exact command strings as a starting point to reconcile against the
32
+ * installed maestro version, not a verified-on-CI contract (no device was available at authoring).
33
+ */
34
+ export class DefaultMaestroRunner {
35
+ opts;
36
+ bin;
37
+ timeoutMs;
38
+ constructor(opts) {
39
+ this.opts = opts;
40
+ this.bin = opts.maestroBin ?? "maestro";
41
+ this.timeoutMs = opts.commandTimeoutMs ?? 30_000;
42
+ }
43
+ deviceArgs() {
44
+ return this.opts.device ? ["--device", this.opts.device] : [];
45
+ }
46
+ /** Write a temp flow YAML, run it via `maestro test`, and clean up. */
47
+ async flow(body) {
48
+ const dir = await mkdtemp(join(tmpdir(), "comber-maestro-"));
49
+ const file = join(dir, "flow.yaml");
50
+ const yaml = `appId: ${this.opts.appId}\n---\n${body}\n`;
51
+ try {
52
+ await writeFile(file, yaml, "utf8");
53
+ await run(this.bin, [...this.deviceArgs(), "test", file], { timeoutMs: this.timeoutMs });
54
+ }
55
+ finally {
56
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
57
+ }
58
+ }
59
+ async launch(appId, deeplink) {
60
+ // `launchApp` with a clearState reset, then (optionally) openLink to the deeplink.
61
+ const lines = [`- launchApp:`, ` appId: "${appId}"`, ` clearState: false`];
62
+ if (deeplink)
63
+ lines.push(`- openLink: "${deeplink}"`);
64
+ await this.flow(lines.join("\n"));
65
+ }
66
+ async viewHierarchy() {
67
+ const { stdout } = await run(this.bin, [...this.deviceArgs(), "hierarchy"], { timeoutMs: this.timeoutMs });
68
+ return parseMaestroHierarchy(stdout, this.opts.appId);
69
+ }
70
+ async tap(selector) {
71
+ await this.flow(`- tapOn:\n${selectorYaml(selector)}`);
72
+ }
73
+ async input(selector, text) {
74
+ await this.flow(`- tapOn:\n${selectorYaml(selector)}\n- inputText: "${escapeYaml(text)}"`);
75
+ }
76
+ async back() {
77
+ await this.flow(`- back`);
78
+ }
79
+ async foregroundApp() {
80
+ if (this.opts.platform === "android") {
81
+ const { stdout } = await run("adb", [...(this.opts.device ? ["-s", this.opts.device] : []), "shell", "dumpsys", "activity", "activities"], { timeoutMs: this.timeoutMs }).catch(() => ({ stdout: "", stderr: "" }));
82
+ const m = stdout.match(/mResumedActivity.*?\s([A-Za-z0-9_.]+)\//);
83
+ return m?.[1] ?? this.opts.appId;
84
+ }
85
+ // iOS: no first-class CLI; best-effort fall back to the configured appId.
86
+ return this.opts.appId;
87
+ }
88
+ async logsSince(sinceMs) {
89
+ if (this.opts.platform !== "android")
90
+ return [];
91
+ const stamp = new Date(sinceMs).toISOString().replace("T", " ").slice(0, 19) + ".000";
92
+ const { stdout } = await run("adb", [...(this.opts.device ? ["-s", this.opts.device] : []), "logcat", "-d", "-t", stamp, "*:E"], { timeoutMs: this.timeoutMs }).catch(() => ({ stdout: "", stderr: "" }));
93
+ return stdout.split(/\r?\n/).filter(Boolean);
94
+ }
95
+ async runFlow(yaml) {
96
+ const dir = await mkdtemp(join(tmpdir(), "comber-maestro-"));
97
+ const file = join(dir, "flow.yaml");
98
+ try {
99
+ await writeFile(file, yaml, "utf8");
100
+ await run(this.bin, [...this.deviceArgs(), "test", file], { timeoutMs: this.timeoutMs });
101
+ }
102
+ finally {
103
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
104
+ }
105
+ }
106
+ async screenshot() {
107
+ // Maestro writes screenshots to disk; a stdout-PNG path is version-dependent. Default: none.
108
+ return Buffer.alloc(0);
109
+ }
110
+ }
111
+ function escapeYaml(s) {
112
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
113
+ }
114
+ /** Compose a maestro selector block (id ⇒ id:, else text, else index). */
115
+ function selectorYaml(sel) {
116
+ if (sel.id)
117
+ return ` id: "${escapeYaml(sel.id)}"`;
118
+ if (sel.text)
119
+ return ` text: "${escapeYaml(sel.text)}"`;
120
+ return ` index: ${sel.index ?? 0}`;
121
+ }
122
+ /**
123
+ * Parse the JSON `maestro hierarchy` dump into a NativeView. Tolerant: maestro's node shape
124
+ * carries an `attributes` bag + `children`; field names vary by platform/version, so we read
125
+ * a superset and degrade gracefully. Unknown shapes yield an empty root (an honest blind spot).
126
+ */
127
+ export function parseMaestroHierarchy(stdout, appId) {
128
+ let json;
129
+ try {
130
+ json = JSON.parse(stdout);
131
+ }
132
+ catch {
133
+ return { appId, root: {} };
134
+ }
135
+ const obj = (json ?? {});
136
+ const rootRaw = (obj.root ?? obj);
137
+ return { appId, root: convertNode(rootRaw) };
138
+ }
139
+ function convertNode(raw) {
140
+ const attrs = (raw.attributes ?? raw);
141
+ const str = (k) => (typeof attrs[k] === "string" ? attrs[k] : undefined);
142
+ const bool = (k) => attrs[k] === true || attrs[k] === "true" ? true : attrs[k] === false || attrs[k] === "false" ? false : undefined;
143
+ const childrenRaw = Array.isArray(raw.children) ? raw.children : [];
144
+ return {
145
+ id: str("resource-id") ?? str("resourceId") ?? str("accessibilityIdentifier") ?? str("testTag") ?? str("id"),
146
+ text: str("text") ?? str("label") ?? str("accessibilityText") ?? str("hintText"),
147
+ type: str("class") ?? str("className") ?? str("elementType") ?? str("type"),
148
+ enabled: bool("enabled"),
149
+ clickable: bool("clickable") ?? bool("focusable"),
150
+ focusable: bool("focusable"),
151
+ children: childrenRaw.map(convertNode),
152
+ };
153
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maestro.js","sourceRoot":"","sources":["../../src/native/maestro.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import { normalizeDeviceProfile } from "./device-profile.js";
2
+ import { validateNativeDeviceProfile } from "./evidence.js";
3
+ import { samsungEvidence } from "./samsung.js";
4
+ function hasText(value) {
5
+ return value !== undefined && value.trim().length > 0;
6
+ }
7
+ function addIssue(issues, severity, code, field, message) {
8
+ issues.push({
9
+ severity,
10
+ code,
11
+ ...(field !== undefined ? { field } : {}),
12
+ message,
13
+ });
14
+ }
15
+ export function evaluateNativeReadiness(input) {
16
+ const profile = normalizeDeviceProfile(input);
17
+ const issues = [];
18
+ if (!hasText(profile.deviceId)) {
19
+ addIssue(issues, "blocker", "device-id-missing", "deviceId", "Native readiness requires a concrete device id");
20
+ }
21
+ if (!hasText(profile.provider)) {
22
+ addIssue(issues, "blocker", "provider-missing", "provider", "Native readiness requires a provider or local device source");
23
+ }
24
+ if (!hasText(profile.verificationCommand)) {
25
+ addIssue(issues, "blocker", "verification-command-missing", "verificationCommand", "Native readiness requires the command that produced the evidence");
26
+ }
27
+ if ((profile.evidenceArtifacts ?? []).length === 0) {
28
+ addIssue(issues, "blocker", "evidence-artifacts-missing", "evidenceArtifacts", "Native readiness requires at least one evidence artifact path");
29
+ }
30
+ const validation = validateNativeDeviceProfile(profile);
31
+ for (const error of validation.errors) {
32
+ addIssue(issues, "blocker", error.code, error.field, error.message);
33
+ }
34
+ const samsung = samsungEvidence(profile);
35
+ if (profile.samsung?.requested === true && samsung.samsung?.claimAllowed !== true) {
36
+ addIssue(issues, "blocker", "samsung-evidence-incomplete", "samsung", "Samsung coverage requires exact Galaxy/One UI evidence from a physical or device-lab class device");
37
+ }
38
+ if (profile.platform === "ios" && profile.appleCrashLogsAvailable !== true) {
39
+ addIssue(issues, "warning", "apple-crash-log-evidence-missing", "appleCrashLogsAvailable", "Apple crash-log evidence was not declared available");
40
+ }
41
+ const blockerCount = issues.filter((issue) => issue.severity === "blocker").length;
42
+ const warningCount = issues.filter((issue) => issue.severity === "warning").length;
43
+ return {
44
+ platform: profile.platform,
45
+ appId: profile.appId,
46
+ status: blockerCount > 0 ? "blocked" : warningCount > 0 ? "warning" : "ready",
47
+ blockerCount,
48
+ warningCount,
49
+ issues,
50
+ };
51
+ }
52
+ function blindSpotKindForIssue(issue) {
53
+ if (issue.code === "samsung-evidence-incomplete")
54
+ return "device-class-unavailable";
55
+ return "native-evidence-invalid";
56
+ }
57
+ export function readinessBlindSpots(report, ignoredCodes, scope) {
58
+ const out = [];
59
+ for (const issue of report.issues) {
60
+ if (issue.severity !== "blocker")
61
+ continue;
62
+ if (ignoredCodes.has(issue.code))
63
+ continue;
64
+ out.push({
65
+ kind: blindSpotKindForIssue(issue),
66
+ severity: "blocker-for-exhaustive",
67
+ scope,
68
+ message: `Native readiness preflight blocked exhaustive proof: ${issue.message}`,
69
+ });
70
+ }
71
+ return out;
72
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/native/preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { redactText } from "../core/redact.js";
6
+ export const NATIVE_EGRESS_PROXY_PROOF_SCHEMA = "comber.native-egress-proxy-proof.v1";
7
+ const DEFAULT_TIMEOUT_MS = 15_000;
8
+ const MAX_CAPTURE_CHARS = 16_000;
9
+ function hasText(value) {
10
+ return typeof value === "string" && value.trim().length > 0;
11
+ }
12
+ function isRecord(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ function redactedBounded(value) {
16
+ const redacted = redactText(value);
17
+ if (redacted.length <= MAX_CAPTURE_CHARS)
18
+ return redacted;
19
+ const keep = Math.floor(MAX_CAPTURE_CHARS / 2);
20
+ const omitted = redacted.length - MAX_CAPTURE_CHARS;
21
+ return `${redacted.slice(0, keep)}\n...[truncated ${omitted} chars]...\n${redacted.slice(-keep)}`;
22
+ }
23
+ function shellQuoteForDisplay(value) {
24
+ if (/^[A-Za-z0-9_./:=@%+-]+$/.test(value))
25
+ return value;
26
+ return `'${value.replace(/'/g, "'\\''")}'`;
27
+ }
28
+ function formatCommand(argv) {
29
+ return redactText(argv.map(shellQuoteForDisplay).join(" "));
30
+ }
31
+ function errorExitCode(error) {
32
+ if (isRecord(error) && typeof error.code === "number")
33
+ return error.code;
34
+ return 1;
35
+ }
36
+ function errorMessage(error) {
37
+ if (error instanceof Error)
38
+ return error.message;
39
+ return String(error);
40
+ }
41
+ export function execFileCommandRunner(argv, timeoutMs) {
42
+ const executable = argv[0];
43
+ if (!hasText(executable)) {
44
+ return Promise.resolve({ exitCode: 1, stdout: "", stderr: "proof command executable is missing" });
45
+ }
46
+ return new Promise((resolve) => {
47
+ execFile(executable, argv.slice(1), { timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
48
+ const stderrText = String(stderr);
49
+ const errorText = error ? errorMessage(error) : "";
50
+ resolve({
51
+ exitCode: error ? errorExitCode(error) : 0,
52
+ stdout: String(stdout),
53
+ stderr: errorText && !stderrText.includes(errorText) ? `${stderrText}${stderrText ? "\n" : ""}${errorText}` : stderrText,
54
+ });
55
+ });
56
+ });
57
+ }
58
+ export function parseCommandJson(value, fieldName) {
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(value);
62
+ }
63
+ catch {
64
+ throw new Error(`${fieldName} must be a JSON array of command argv strings`);
65
+ }
66
+ if (!Array.isArray(parsed) || parsed.length === 0) {
67
+ throw new Error(`${fieldName} must be a non-empty JSON array of command argv strings`);
68
+ }
69
+ const argv = [];
70
+ for (const entry of parsed) {
71
+ if (typeof entry !== "string") {
72
+ throw new Error(`${fieldName} entries must be strings`);
73
+ }
74
+ argv.push(entry);
75
+ }
76
+ if (!hasText(argv[0])) {
77
+ throw new Error(`${fieldName} executable must be a non-empty string`);
78
+ }
79
+ return argv;
80
+ }
81
+ function validateRequest(request) {
82
+ if (!hasText(request.deviceId))
83
+ throw new Error("deviceId is required");
84
+ if (!hasText(request.provider))
85
+ throw new Error("provider is required");
86
+ if (!hasText(request.proxyCommand))
87
+ throw new Error("proxyCommand is required");
88
+ if (!hasText(request.artifactPath))
89
+ throw new Error("artifactPath is required");
90
+ if (request.proofCommand.length === 0 || !hasText(request.proofCommand[0])) {
91
+ throw new Error("proofCommand must include an executable");
92
+ }
93
+ if (request.expectedOutput.length === 0)
94
+ throw new Error("expectedOutput must include at least one marker");
95
+ for (const expected of request.expectedOutput) {
96
+ if (!hasText(expected))
97
+ throw new Error("expectedOutput markers must be non-empty strings");
98
+ }
99
+ }
100
+ export async function runNativeIosProxyVerification(request) {
101
+ validateRequest(request);
102
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
103
+ const commandRunner = request.commandRunner ?? execFileCommandRunner;
104
+ const result = await commandRunner(request.proofCommand, timeoutMs);
105
+ const stdout = redactedBounded(result.stdout);
106
+ const stderr = redactedBounded(result.stderr);
107
+ const combinedOutput = `${result.stdout}\n${result.stderr}`;
108
+ const checks = [
109
+ {
110
+ code: "proof-command-exit-zero",
111
+ passed: result.exitCode === 0,
112
+ message: result.exitCode === 0 ? "proof command exited 0" : `proof command exited ${result.exitCode}`,
113
+ },
114
+ ];
115
+ for (const expected of request.expectedOutput) {
116
+ checks.push({
117
+ code: "expected-output",
118
+ expected: redactText(expected),
119
+ passed: combinedOutput.includes(expected),
120
+ message: combinedOutput.includes(expected)
121
+ ? "proof command output contained expected marker"
122
+ : "proof command output did not contain expected marker",
123
+ });
124
+ }
125
+ const artifact = {
126
+ schema: NATIVE_EGRESS_PROXY_PROOF_SCHEMA,
127
+ platform: "ios",
128
+ deviceId: request.deviceId,
129
+ provider: request.provider,
130
+ verified: checks.every((check) => check.passed),
131
+ command: redactText(request.proxyCommand),
132
+ proofCommand: formatCommand(request.proofCommand),
133
+ artifactPath: request.artifactPath,
134
+ createdAt: (request.now ?? (() => new Date()))().toISOString(),
135
+ checks,
136
+ stdout,
137
+ stderr,
138
+ };
139
+ await mkdir(dirname(request.artifactPath), { recursive: true });
140
+ await writeFile(request.artifactPath, JSON.stringify(artifact, null, 2), "utf8");
141
+ return artifact;
142
+ }
143
+ function textField(record, field) {
144
+ const value = record[field];
145
+ return hasText(value) ? value : undefined;
146
+ }
147
+ function checksAreVerified(value) {
148
+ if (!Array.isArray(value) || value.length === 0)
149
+ return false;
150
+ for (const check of value) {
151
+ if (!isRecord(check) || check.passed !== true)
152
+ return false;
153
+ }
154
+ return true;
155
+ }
156
+ export function loadNativeEgressProxyEvidenceFromProof(proofPath) {
157
+ let parsed;
158
+ try {
159
+ parsed = JSON.parse(readFileSync(proofPath, "utf8"));
160
+ }
161
+ catch {
162
+ return { verified: false, artifactPath: proofPath };
163
+ }
164
+ if (!isRecord(parsed))
165
+ return { verified: false, artifactPath: proofPath };
166
+ const provider = textField(parsed, "provider");
167
+ const command = textField(parsed, "command");
168
+ const deviceId = textField(parsed, "deviceId");
169
+ const verified = parsed.schema === NATIVE_EGRESS_PROXY_PROOF_SCHEMA &&
170
+ parsed.platform === "ios" &&
171
+ parsed.verified === true &&
172
+ provider !== undefined &&
173
+ command !== undefined &&
174
+ deviceId !== undefined &&
175
+ checksAreVerified(parsed.checks);
176
+ return {
177
+ verified,
178
+ ...(provider !== undefined ? { provider: redactText(provider) } : {}),
179
+ ...(command !== undefined ? { command: redactText(command) } : {}),
180
+ artifactPath: proofPath,
181
+ };
182
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-proof.js","sourceRoot":"","sources":["../../src/native/proxy-proof.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ function hasSamsungIdentity(profile) {
2
+ const model = profile.samsung?.model ?? profile.model ?? "";
3
+ return /\b(samsung|galaxy|sm-)/i.test(model);
4
+ }
5
+ export function samsungEvidence(profile) {
6
+ if (profile.samsung?.requested !== true)
7
+ return { gaps: [] };
8
+ const model = profile.samsung.model ?? profile.model;
9
+ const androidVersion = profile.androidVersion ?? profile.osVersion;
10
+ const oneUiVersion = profile.samsung.oneUiVersion;
11
+ const buildFingerprint = profile.samsung.buildFingerprint;
12
+ const physicalOrLabDevice = profile.deviceClass === "phone" || profile.deviceClass === "tablet" || profile.deviceClass === "foldable";
13
+ const claimAllowed = profile.platform === "android" && physicalOrLabDevice && hasSamsungIdentity(profile) && Boolean(androidVersion && oneUiVersion);
14
+ const gaps = [];
15
+ if (!claimAllowed) {
16
+ gaps.push({
17
+ kind: "device-class-unavailable",
18
+ severity: "blocker-for-exhaustive",
19
+ scope: "samsung",
20
+ message: "Samsung coverage was requested but exact Galaxy/One UI physical or device-lab evidence is unavailable",
21
+ });
22
+ }
23
+ return {
24
+ samsung: {
25
+ requested: true,
26
+ claimAllowed,
27
+ ...(model !== undefined ? { model } : {}),
28
+ ...(androidVersion !== undefined ? { androidVersion } : {}),
29
+ ...(oneUiVersion !== undefined ? { oneUiVersion } : {}),
30
+ ...(buildFingerprint !== undefined ? { buildFingerprint } : {}),
31
+ },
32
+ gaps,
33
+ };
34
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"samsung.js","sourceRoot":"","sources":["../../src/native/samsung.ts"],"names":[],"mappings":"AAYA,SAAS,kBAAkB,CAAC,OAA4B;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAC5D,OAAO,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,IAAI,OAAO,CAAC,OAAO,EAAE,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAE7D,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IACrD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC,SAAS,CAAC;IACnE,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC;IAClD,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAC1D,MAAM,mBAAmB,GACvB,OAAO,CAAC,WAAW,KAAK,OAAO,IAAI,OAAO,CAAC,WAAW,KAAK,QAAQ,IAAI,OAAO,CAAC,WAAW,KAAK,UAAU,CAAC;IAC5G,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,mBAAmB,IAAI,kBAAkB,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,cAAc,IAAI,YAAY,CAAC,CAAC;IACrJ,MAAM,IAAI,GAAgB,EAAE,CAAC;IAE7B,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,0BAA0B;YAChC,QAAQ,EAAE,wBAAwB;YAClC,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,uGAAuG;SACjH,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,OAAO,EAAE;YACP,SAAS,EAAE,IAAI;YACf,YAAY;YACZ,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChE;QACD,IAAI;KACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ const SEVERITY = {
2
+ crash: "fail",
3
+ anr: "warn",
4
+ frozen: "warn",
5
+ "error-text": "warn",
6
+ "assertion-fail": "warn",
7
+ };
8
+ /**
9
+ * Dedupe key — keyed on screen SCOPE + signal KIND (per the native design), so the same broken
10
+ * screen re-seen across the crawl collapses to one finding. An optional `discriminator` (e.g. the
11
+ * control id that crashed) separates two distinct breakages on the SAME screen.
12
+ */
13
+ export function signatureOf(kind, scope, discriminator) {
14
+ return `${kind}|${scope}${discriminator ? `|${discriminator}` : ""}`.slice(0, 80);
15
+ }
16
+ /** Construct a native BreakageSignal. `where` is the screen scope/name (NOT a real URL — the core
17
+ * redactor leaves a scheme-less screen token intact); `action` is what we did just before. */
18
+ export function nativeSignal(kind, scope, message, opts = {}) {
19
+ return {
20
+ kind,
21
+ severity: SEVERITY[kind],
22
+ message,
23
+ url: scope,
24
+ signature: signatureOf(kind, scope, opts.discriminator),
25
+ ...(opts.action ? { action: opts.action } : {}),
26
+ };
27
+ }
28
+ /**
29
+ * On-screen error canaries — the native analogue of the web console/error-banner canaries. A
30
+ * visible string matching this on a screen is surfaced as `error-text` (warn). Conservative so
31
+ * ordinary UI copy ("Error code" labels in a form's help text are still flagged, by design — a
32
+ * QA pass would rather over-surface a visible "error" than miss a real one).
33
+ */
34
+ export const NATIVE_ERROR_TEXT = /(something went wrong|unexpected error|an error occurred|app (?:keeps )?stopp(?:ed|ing)|isn'?t responding|unfortunately|failed to load|couldn'?t (?:load|connect)|null is not|undefined is not|NullPointerException|RuntimeException|IndexOutOfBounds|unhandled exception|fatal error|stack trace)/i;
35
+ /** Log-line markers for a hard crash (fail). */
36
+ export const CRASH_LOG = /(FATAL EXCEPTION|SIGSEGV|SIGABRT|has died|Process .* crashed|libc: Fatal signal)/i;
37
+ /** Log-line markers for an ANR / not-responding condition (warn). */
38
+ export const ANR_LOG = /(ANR in |Application Not Responding|Input dispatching timed out|Reason: Input|executing service)/i;
39
+ /** Scan a window of device log lines; return the first crash/ANR kind seen, if any. */
40
+ export function scanLogs(lines) {
41
+ for (const line of lines) {
42
+ if (CRASH_LOG.test(line))
43
+ return { kind: "crash", line };
44
+ }
45
+ for (const line of lines) {
46
+ if (ANR_LOG.test(line))
47
+ return { kind: "anr", line };
48
+ }
49
+ return undefined;
50
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.js","sourceRoot":"","sources":["../../src/native/signals.ts"],"names":[],"mappings":"AAiBA,MAAM,QAAQ,GAA8C;IAC1D,KAAK,EAAE,MAAM;IACb,GAAG,EAAE,MAAM;IACX,MAAM,EAAE,MAAM;IACd,YAAY,EAAE,MAAM;IACpB,gBAAgB,EAAE,MAAM;CACzB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAsB,EAAE,KAAa,EAAE,aAAsB;IACvF,OAAO,GAAG,IAAI,IAAI,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACpF,CAAC;AAED;+FAC+F;AAC/F,MAAM,UAAU,YAAY,CAC1B,IAAsB,EACtB,KAAa,EACb,OAAe,EACf,OAAoD,EAAE;IAEtD,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC;QACxB,OAAO;QACP,GAAG,EAAE,KAAK;QACV,SAAS,EAAE,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC;QACvD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,qSAAqS,CAAC;AAExS,gDAAgD;AAChD,MAAM,CAAC,MAAM,SAAS,GAAG,mFAAmF,CAAC;AAE7G,qEAAqE;AACrE,MAAM,CAAC,MAAM,OAAO,GAAG,mGAAmG,CAAC;AAE3H,uFAAuF;AACvF,MAAM,UAAU,QAAQ,CAAC,KAAe;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3D,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACvD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}