@wix/himalaya-cli 0.1055.0 → 0.1057.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.
package/dist/cli.mjs CHANGED
@@ -2168,462 +2168,1239 @@ var init_himi_report = __esm({
2168
2168
  }
2169
2169
  });
2170
2170
 
2171
- // ../../core/serve-storage/src/widget-identity.ts
2172
- function stableWidgets(widgets) {
2173
- if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return widgets;
2174
- return Object.fromEntries(Object.entries(widgets).map(([id, value]) => {
2175
- if (!value || typeof value !== "object" || Array.isArray(value)) return [id, value];
2176
- const { contentEtag: _regenerated, ...stable } = value;
2177
- return [id, stable];
2178
- }));
2179
- }
2180
- var init_widget_identity = __esm({
2181
- "../../core/serve-storage/src/widget-identity.ts"() {
2171
+ // ../ai-review-gate/paths.mjs
2172
+ import { resolve as resolve3, dirname as dirname3 } from "node:path";
2173
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2174
+ var REPO_ROOT;
2175
+ var init_paths = __esm({
2176
+ "../ai-review-gate/paths.mjs"() {
2177
+ REPO_ROOT = resolve3(dirname3(fileURLToPath2(import.meta.url)), "../..");
2182
2178
  }
2183
2179
  });
2184
2180
 
2185
- // ../../core/admin-web/src/utils/qr.ts
2186
- function rawDataModules(ver) {
2187
- let result = (16 * ver + 128) * ver + 64;
2188
- if (ver >= 2) {
2189
- const numAlign = Math.floor(ver / 7) + 2;
2190
- result -= (25 * numAlign - 10) * numAlign - 55;
2191
- if (ver >= 7) result -= 36;
2181
+ // ../ai-review-gate/review-diff.mjs
2182
+ var init_review_diff = __esm({
2183
+ "../ai-review-gate/review-diff.mjs"() {
2184
+ init_paths();
2192
2185
  }
2193
- return result;
2186
+ });
2187
+
2188
+ // ../pre-pr-review/command.mjs
2189
+ var SHELL_NAMES, SHELL_TOKEN;
2190
+ var init_command = __esm({
2191
+ "../pre-pr-review/command.mjs"() {
2192
+ SHELL_NAMES = "sh|bash|zsh|ksh|dash|ash|fish|csh|tcsh|pwsh|xonsh|nu";
2193
+ SHELL_TOKEN = new RegExp(`^(?:.*/)?(?:${SHELL_NAMES})$`);
2194
+ }
2195
+ });
2196
+
2197
+ // ../pre-pr-review/scope.mjs
2198
+ import { execFileSync } from "node:child_process";
2199
+ import { createHash } from "node:crypto";
2200
+ import { resolve as resolve4, dirname as dirname4, isAbsolute, join as join4 } from "node:path";
2201
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
2202
+ function resolveRepoRoot(cwd = process.cwd()) {
2203
+ let top;
2204
+ try {
2205
+ top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
2206
+ cwd,
2207
+ encoding: "utf8",
2208
+ stdio: ["ignore", "pipe", "pipe"]
2209
+ }).toString().trim();
2210
+ } catch (err) {
2211
+ throw new Error(`cannot resolve a repo root from ${cwd}: ${err?.message || err}`);
2212
+ }
2213
+ if (!top) throw new Error(`cannot resolve a repo root from ${cwd}: git named no toplevel`);
2214
+ return top;
2194
2215
  }
2195
- function byteCapacity(ver) {
2196
- const lenBits = ver < 10 ? 8 : 16;
2197
- return dataCodewords(ver) - Math.ceil((4 + lenBits) / 8);
2216
+ function git(args, opts = {}) {
2217
+ return execFileSync("git", args, {
2218
+ cwd: opts.cwd || REPO_ROOT2,
2219
+ encoding: "utf8",
2220
+ // A full PR diff can be megabytes; the default 1MB cap would truncate mid-hunk and the
2221
+ // delta parser would then read a malformed diff as "unsupported shape".
2222
+ maxBuffer: opts.maxBuffer ?? 256 * 1024 * 1024,
2223
+ stdio: ["ignore", "pipe", "pipe"]
2224
+ }).toString();
2198
2225
  }
2199
- function fitVersion(len) {
2200
- for (let v = 1; v <= QR_MAX_VERSION; v++) if (byteCapacity(v) >= len) return v;
2201
- return void 0;
2226
+ function diffHash(diff) {
2227
+ return createHash("sha256").update(diff).digest("hex");
2202
2228
  }
2203
- function rsDivisor(degree) {
2204
- const result = new Array(degree).fill(0);
2205
- result[degree - 1] = 1;
2206
- let root = 1;
2207
- for (let i = 0; i < degree; i++) {
2208
- for (let j = 0; j < degree; j++) {
2209
- result[j] = gfMul(result[j], root);
2210
- if (j + 1 < degree) result[j] ^= result[j + 1];
2211
- }
2212
- root = gfMul(root, 2);
2229
+ var REPO_ROOT2, BASE_REF;
2230
+ var init_scope = __esm({
2231
+ "../pre-pr-review/scope.mjs"() {
2232
+ init_paths();
2233
+ init_review_diff();
2234
+ init_command();
2235
+ REPO_ROOT2 = resolve4(dirname4(fileURLToPath3(import.meta.url)), "../..");
2236
+ BASE_REF = process.env.PRE_PR_REVIEW_BASE || "origin/master";
2213
2237
  }
2214
- return result;
2238
+ });
2239
+
2240
+ // ../verify-gate/diff-hash.mjs
2241
+ import {
2242
+ lstatSync as lstatSync2,
2243
+ mkdirSync as mkdirSync4,
2244
+ mkdtempSync,
2245
+ readFileSync as readFileSync4,
2246
+ readlinkSync,
2247
+ rmSync as rmSync2,
2248
+ writeFileSync as writeFileSync4
2249
+ } from "node:fs";
2250
+ import { createHash as createHash2 } from "node:crypto";
2251
+ import { tmpdir } from "node:os";
2252
+ import { basename, dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
2253
+ import { pathToFileURL } from "node:url";
2254
+ function isValidationMetadata(path2) {
2255
+ return VALIDATION_METADATA.has(path2) || /(^|[\\/])\.himi([\\/]|$)/.test(path2);
2215
2256
  }
2216
- function rsRemainder(data, degree) {
2217
- const divisor = rsDivisor(degree);
2218
- const res = new Array(degree).fill(0);
2219
- for (const b of data) {
2220
- const factor = b ^ res.shift();
2221
- res.push(0);
2222
- for (let i = 0; i < degree; i++) res[i] ^= gfMul(divisor[i], factor);
2257
+ function tryGit(args, root) {
2258
+ try {
2259
+ return git(args, { cwd: root });
2260
+ } catch {
2261
+ return null;
2223
2262
  }
2224
- return res;
2225
2263
  }
2226
- function encodeData(bytes2, ver) {
2227
- const bits = [];
2228
- const push = (val, len) => {
2229
- for (let i = len - 1; i >= 0; i--) bits.push(val >>> i & 1);
2230
- };
2231
- push(4, 4);
2232
- push(bytes2.length, ver < 10 ? 8 : 16);
2233
- for (const b of bytes2) push(b, 8);
2234
- const capacityBits = dataCodewords(ver) * 8;
2235
- push(0, Math.min(4, capacityBits - bits.length));
2236
- while (bits.length % 8 !== 0) bits.push(0);
2237
- const out = [];
2238
- for (let i = 0; i < bits.length; i += 8) {
2239
- let byte2 = 0;
2240
- for (let j = 0; j < 8; j++) byte2 = byte2 << 1 | bits[i + j];
2241
- out.push(byte2);
2264
+ function committedRecords(raw) {
2265
+ const fields = raw.split("\0");
2266
+ if (fields.length > 0 && fields[fields.length - 1] === "") fields.pop();
2267
+ if (fields.length % 2 !== 0) return null;
2268
+ const records = [];
2269
+ for (let i = 0; i < fields.length; i += 2) {
2270
+ const metadata2 = fields[i];
2271
+ const path2 = fields[i + 1];
2272
+ if (!metadata2.startsWith(":") || path2 === "") return null;
2273
+ if (isValidationMetadata(path2)) continue;
2274
+ records.push(`${Buffer.byteLength(path2)}:${path2}\0${metadata2}\0`);
2242
2275
  }
2243
- for (let pad2 = 236; out.length < dataCodewords(ver); pad2 ^= 236 ^ 17) out.push(pad2);
2244
- return out;
2276
+ return records.sort().join("");
2245
2277
  }
2246
- function interleave(data, ver) {
2247
- const numBlocks = NUM_BLOCKS_L[ver];
2248
- const eccLen = ECC_PER_BLOCK_L[ver];
2249
- const shortLen = Math.floor(totalCodewords(ver) / numBlocks) - eccLen;
2250
- const numShort = numBlocks - totalCodewords(ver) % numBlocks;
2251
- const blocks = [];
2252
- const eccs = [];
2253
- let off = 0;
2254
- for (let i = 0; i < numBlocks; i++) {
2255
- const len = shortLen + (i < numShort ? 0 : 1);
2256
- const blk = data.slice(off, off + len);
2257
- off += len;
2258
- blocks.push(blk);
2259
- eccs.push(rsRemainder(blk, eccLen));
2278
+ function committedIdentity(root) {
2279
+ const head = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root)?.trim();
2280
+ if (!head) return "unborn\0";
2281
+ const base = tryGit(["merge-base", BASE_REF, "HEAD"], root)?.trim();
2282
+ if (base) {
2283
+ const raw = tryGit(["diff", "--raw", "--no-renames", "-z", "--no-abbrev", base, "HEAD"], root);
2284
+ const records = raw === null ? null : committedRecords(raw);
2285
+ if (records !== null) return `base\0${base}\0${records}`;
2260
2286
  }
2261
- const out = [];
2262
- for (let i = 0; i < shortLen + 1; i++) {
2263
- for (let b = 0; b < numBlocks; b++) {
2264
- const v = blocks[b][i];
2265
- if (v !== void 0) out.push(v);
2266
- }
2287
+ const listing = tryGit(["ls-tree", "-rz", "--full-tree", "HEAD"], root);
2288
+ const tree = listing === null ? null : treeRecords(listing);
2289
+ return tree !== null ? `tree-listing\0${tree}` : `commit\0${head}\0`;
2290
+ }
2291
+ function treeRecords(listing) {
2292
+ const records = [];
2293
+ for (const record2 of listing.split("\0")) {
2294
+ if (record2 === "") continue;
2295
+ const tab = record2.indexOf(" ");
2296
+ if (tab <= 0 || tab === record2.length - 1) return null;
2297
+ const path2 = record2.slice(tab + 1);
2298
+ if (isValidationMetadata(path2)) continue;
2299
+ records.push(`${Buffer.byteLength(path2)}:${path2}\0${record2.slice(0, tab)}\0`);
2267
2300
  }
2268
- for (let i = 0; i < eccLen; i++) for (let b = 0; b < numBlocks; b++) out.push(eccs[b][i]);
2269
- return out;
2301
+ return records.sort().join("");
2270
2302
  }
2271
- function alignmentPositions(ver) {
2272
- if (ver === 1) return [];
2273
- const num3 = Math.floor(ver / 7) + 2;
2274
- const size = ver * 4 + 17;
2275
- const step = Math.ceil((ver * 4 + 4) / (num3 * 2 - 2)) * 2;
2276
- const res = [6];
2277
- for (let pos = size - 7; res.length < num3; pos -= step) res.splice(1, 0, pos);
2278
- return res;
2303
+ function treeEntries(output, index = false) {
2304
+ const entries = /* @__PURE__ */ new Map();
2305
+ for (const record2 of output.split("\0").filter(Boolean)) {
2306
+ const tab = record2.indexOf(" ");
2307
+ if (tab === -1) continue;
2308
+ const metadata2 = record2.slice(0, tab).split(" ");
2309
+ if (index && metadata2[2] !== "0") continue;
2310
+ entries.set(record2.slice(tab + 1), {
2311
+ mode: metadata2[0],
2312
+ oid: index ? metadata2[1] : metadata2[2]
2313
+ });
2314
+ }
2315
+ return entries;
2279
2316
  }
2280
- function bchFormat(data) {
2281
- let rem = data;
2282
- for (let i = 0; i < 10; i++) rem = rem << 1 ^ (rem >>> 9) * 1335;
2283
- return (data << 10 | rem) ^ 21522;
2317
+ function sameEntry(left, right) {
2318
+ if (!left || !right) return left === right;
2319
+ return left.mode === right.mode && left.oid === right.oid;
2284
2320
  }
2285
- function bchVersion(ver) {
2286
- let rem = ver;
2287
- for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >>> 11) * 7973;
2288
- return ver << 12 | rem;
2321
+ function stagedIdentity(head, index, worktree) {
2322
+ if (sameEntry(index, head) || sameEntry(index, worktree)) return "";
2323
+ if (!index) return head ? "deleted" : "";
2324
+ return `${index.mode}:${index.oid}`;
2289
2325
  }
2290
- function penalty(m, size) {
2291
- let score = 0;
2292
- const run2 = (get) => {
2293
- for (let i = 0; i < size; i++) {
2294
- let last = get(i, 0), len = 1;
2295
- for (let j = 1; j < size; j++) {
2296
- const cur = get(i, j);
2297
- if (cur === last) {
2298
- len++;
2299
- if (len === 5) score += 3;
2300
- else if (len > 5) score++;
2301
- } else {
2302
- last = cur;
2303
- len = 1;
2304
- }
2305
- }
2306
- }
2307
- };
2308
- run2((i, j) => m[i][j]);
2309
- run2((i, j) => m[j][i]);
2310
- for (let y = 0; y < size - 1; y++)
2311
- for (let x = 0; x < size - 1; x++) {
2312
- const c = m[y][x];
2313
- if (c === m[y][x + 1] && c === m[y + 1][x] && c === m[y + 1][x + 1]) score += 3;
2314
- }
2315
- let dark = 0;
2316
- for (const row of m) for (const c of row) if (c) dark++;
2317
- const total = size * size;
2318
- score += Math.floor(Math.abs(dark * 20 - total * 10) / total) * 10;
2319
- return score;
2326
+ function blobOid(contents, algorithm) {
2327
+ const body = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
2328
+ return createHash2(algorithm).update(Buffer.from(`blob ${body.byteLength}\0`)).update(body).digest("hex");
2320
2329
  }
2321
- function encodeQr(text5) {
2322
- const bytes2 = [...new TextEncoder().encode(text5)];
2323
- const version2 = fitVersion(bytes2.length);
2324
- if (!version2) {
2325
- throw new RangeError(`QR payload too long: ${bytes2.length} bytes exceeds version ${QR_MAX_VERSION} at EC level L`);
2330
+ function validationDiffHash(cwd = process.cwd()) {
2331
+ const root = resolveRepoRoot(cwd);
2332
+ const born = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root) !== null;
2333
+ const staged = git(["diff", "--cached", "--name-only", "--no-renames", "-z", ...born ? ["HEAD"] : []], {
2334
+ cwd: root
2335
+ }).split("\0").filter(Boolean);
2336
+ const unstaged = git(["diff", "--name-only", "--no-renames", "-z"], { cwd: root }).split("\0").filter(Boolean);
2337
+ const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], {
2338
+ cwd: root
2339
+ }).split("\0").filter(Boolean);
2340
+ const headEntries = born ? treeEntries(git(["ls-tree", "-rz", "--full-tree", "HEAD"], { cwd: root })) : /* @__PURE__ */ new Map();
2341
+ const indexEntries = treeEntries(git(["ls-files", "--stage", "-z"], { cwd: root }), true);
2342
+ const objectAlgorithm = git(["rev-parse", "--show-object-format"], { cwd: root }).trim();
2343
+ let trustFilesystemMode = true;
2344
+ try {
2345
+ trustFilesystemMode = git(["config", "--bool", "--get", "core.fileMode"], { cwd: root }).trim() !== "false";
2346
+ } catch {
2326
2347
  }
2327
- const codewords = interleave(encodeData(bytes2, version2), version2);
2328
- const size = version2 * 4 + 17;
2329
- const modules = Array.from({ length: size }, () => new Array(size).fill(false));
2330
- const reserved = Array.from({ length: size }, () => new Array(size).fill(false));
2331
- const set = (x, y, dark) => {
2332
- modules[y][x] = dark;
2333
- reserved[y][x] = true;
2334
- };
2335
- const finder = (cx, cy) => {
2336
- for (let dy = -4; dy <= 4; dy++)
2337
- for (let dx = -4; dx <= 4; dx++) {
2338
- const x = cx + dx, y = cy + dy;
2339
- if (x < 0 || y < 0 || x >= size || y >= size) continue;
2340
- const d = Math.max(Math.abs(dx), Math.abs(dy));
2341
- set(x, y, d !== 2 && d !== 4);
2348
+ const paths = [.../* @__PURE__ */ new Set([...staged, ...unstaged, ...untracked])].filter((path2) => !isValidationMetadata(path2)).sort();
2349
+ const identity = paths.map((path2) => {
2350
+ const absolute = resolve5(root, path2);
2351
+ const headEntry = headEntries.get(path2);
2352
+ const indexEntry = indexEntries.get(path2);
2353
+ const deletedRecord = () => [`${Buffer.byteLength(path2)}:${path2}`, "deleted", stagedIdentity(headEntry, indexEntry, void 0), ""].join("\0");
2354
+ let stat2;
2355
+ try {
2356
+ stat2 = lstatSync2(absolute);
2357
+ } catch (error) {
2358
+ if (error?.code === "ENOENT") return deletedRecord();
2359
+ throw error;
2360
+ }
2361
+ try {
2362
+ if (stat2.isSymbolicLink()) {
2363
+ const target = readlinkSync(absolute);
2364
+ const worktreeEntry = { mode: "120000", oid: blobOid(target, objectAlgorithm) };
2365
+ const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
2366
+ return [
2367
+ `${Buffer.byteLength(path2)}:${path2}`,
2368
+ "symlink",
2369
+ "120000",
2370
+ diffHash(target),
2371
+ stagedState2,
2372
+ ""
2373
+ ].join("\0");
2342
2374
  }
2343
- };
2344
- finder(3, 3);
2345
- finder(size - 4, 3);
2346
- finder(3, size - 4);
2347
- for (let i = 8; i < size - 8; i++) {
2348
- set(i, 6, i % 2 === 0);
2349
- set(6, i, i % 2 === 0);
2350
- }
2351
- for (const ay of alignmentPositions(version2))
2352
- for (const ax of alignmentPositions(version2)) {
2353
- if (ax === 6 && ay === 6 || ax === 6 && ay === size - 7 || ax === size - 7 && ay === 6) continue;
2354
- for (let dy = -2; dy <= 2; dy++)
2355
- for (let dx = -2; dx <= 2; dx++) set(ax + dx, ay + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
2375
+ if (stat2.isFile()) {
2376
+ const filesystemMode = stat2.mode & 73 ? "100755" : "100644";
2377
+ const worktreeMode = trustFilesystemMode ? filesystemMode : indexEntry?.mode ?? headEntry?.mode ?? "100644";
2378
+ const worktreeOid = git(["hash-object", `--path=${path2}`, "--", path2], { cwd: root }).trim();
2379
+ const worktreeEntry = { mode: worktreeMode, oid: worktreeOid };
2380
+ const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
2381
+ return [
2382
+ `${Buffer.byteLength(path2)}:${path2}`,
2383
+ "file",
2384
+ worktreeMode,
2385
+ diffHash(readFileSync4(absolute)),
2386
+ stagedState2,
2387
+ ""
2388
+ ].join("\0");
2389
+ }
2390
+ } catch (error) {
2391
+ try {
2392
+ lstatSync2(absolute);
2393
+ } catch (recheck) {
2394
+ if (recheck?.code === "ENOENT") return deletedRecord();
2395
+ }
2396
+ throw error;
2356
2397
  }
2357
- for (let i = 0; i < 9; i++) {
2358
- reserved[i][8] = true;
2359
- reserved[8][i] = true;
2360
- }
2361
- for (let i = 0; i < 8; i++) {
2362
- reserved[size - 1 - i][8] = true;
2363
- reserved[8][size - 1 - i] = true;
2364
- }
2365
- set(8, size - 8, true);
2366
- if (version2 >= 7)
2367
- for (let i = 0; i < 18; i++) {
2368
- const a = size - 11 + i % 3, b = Math.floor(i / 3);
2369
- reserved[a][b] = true;
2370
- reserved[b][a] = true;
2398
+ const type = stat2.isDirectory() ? "directory" : "other";
2399
+ const stagedState = stagedIdentity(headEntry, indexEntry, void 0);
2400
+ return [
2401
+ `${Buffer.byteLength(path2)}:${path2}`,
2402
+ type,
2403
+ stat2.mode.toString(8),
2404
+ stagedState,
2405
+ ""
2406
+ ].join("\0");
2407
+ }).join("");
2408
+ const committed = committedIdentity(root);
2409
+ return diffHash(
2410
+ `validation-change-v3\0${Buffer.byteLength(committed)}\0${committed}${identity}`
2411
+ );
2412
+ }
2413
+ function seed(repo, commands) {
2414
+ for (const args of commands) git(args, { cwd: repo });
2415
+ }
2416
+ function write(repo, rel, body) {
2417
+ const absolute = resolve5(repo, rel);
2418
+ mkdirSync4(dirname5(absolute), { recursive: true });
2419
+ writeFileSync4(absolute, body);
2420
+ }
2421
+ function selfTestRepo({ withBase }) {
2422
+ const repo = mkdtempSync(join5(tmpdir(), "diff-hash-self-test-"));
2423
+ seed(repo, [
2424
+ ["init", "-q", repo],
2425
+ ["-C", repo, "config", "user.email", "self-test@test.invalid"],
2426
+ ["-C", repo, "config", "user.name", "diff-hash-self-test"]
2427
+ ]);
2428
+ write(repo, "src/base.txt", "base\n");
2429
+ seed(repo, [["-C", repo, "add", "-A"], ["-C", repo, "commit", "-qm", "base"]]);
2430
+ const base = git(["-C", repo, "rev-parse", "HEAD"], { cwd: repo }).trim();
2431
+ if (withBase) seed(repo, [["-C", repo, "update-ref", "refs/remotes/origin/master", base]]);
2432
+ return { repo, base };
2433
+ }
2434
+ function commitChange(repo, base, branch, rel, body) {
2435
+ seed(repo, [["-C", repo, "checkout", "-q", base], ["-C", repo, "checkout", "-q", "-b", branch]]);
2436
+ write(repo, rel, body);
2437
+ seed(repo, [["-C", repo, "add", rel], ["-C", repo, "commit", "-qm", `commit ${rel}`]]);
2438
+ return validationDiffHash(repo);
2439
+ }
2440
+ function runSelfTest() {
2441
+ let failed2 = 0;
2442
+ for (const testCase of SELF_TEST_CASES) {
2443
+ let ok = false;
2444
+ let thrown = null;
2445
+ try {
2446
+ ok = testCase.run() === true;
2447
+ } catch (error) {
2448
+ thrown = error?.message || String(error);
2371
2449
  }
2372
- let bit = 0;
2373
- const totalBits = codewords.length * 8;
2374
- for (let right = size - 1; right >= 1; right -= 2) {
2375
- if (right === 6) right = 5;
2376
- for (let vert = 0; vert < size; vert++) {
2377
- for (let j = 0; j < 2; j++) {
2378
- const x = right - j;
2379
- const upward = (right + 1 & 2) === 0;
2380
- const y = upward ? size - 1 - vert : vert;
2381
- if (reserved[y][x]) continue;
2382
- modules[y][x] = bit < totalBits ? (codewords[bit >>> 3] >>> 7 - (bit & 7) & 1) === 1 : false;
2383
- bit++;
2384
- }
2450
+ if (ok) {
2451
+ process.stdout.write(` \u2713 ${testCase.name}
2452
+ `);
2453
+ continue;
2385
2454
  }
2455
+ failed2++;
2456
+ process.stderr.write(` \u2717 ${testCase.name}
2457
+ ${thrown ?? testCase.reason}
2458
+ `);
2386
2459
  }
2387
- let best = null, bestMask = 0, bestScore = Infinity;
2388
- for (let mask = 0; mask < 8; mask++) {
2389
- const cand = modules.map((row) => [...row]);
2390
- for (let y = 0; y < size; y++)
2391
- for (let x = 0; x < size; x++) if (!reserved[y][x] && MASKS[mask](x, y)) cand[y][x] = !cand[y][x];
2392
- applyFormat(cand, size, mask);
2393
- if (version2 >= 7) applyVersion(cand, size, version2);
2394
- const s = penalty(cand, size);
2395
- if (s < bestScore) {
2396
- bestScore = s;
2397
- best = cand;
2398
- bestMask = mask;
2460
+ return failed2;
2461
+ }
2462
+ var VALIDATION_METADATA, SELF_TEST_CASES;
2463
+ var init_diff_hash = __esm({
2464
+ "../verify-gate/diff-hash.mjs"() {
2465
+ init_scope();
2466
+ VALIDATION_METADATA = /* @__PURE__ */ new Set([
2467
+ ".claude/.validation-baseline.json",
2468
+ ".claude/.validation-evidence.json"
2469
+ ]);
2470
+ SELF_TEST_CASES = [
2471
+ {
2472
+ name: "committed diffs are distinguished (the #1831 regression)",
2473
+ reason: "two unrelated COMMITTED diffs share one identity \u2014 a record for either would satisfy the gate for the other",
2474
+ run: () => {
2475
+ const { repo, base } = selfTestRepo({ withBase: true });
2476
+ try {
2477
+ return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
2478
+ } finally {
2479
+ rmSync2(repo, { recursive: true, force: true });
2480
+ }
2481
+ }
2482
+ },
2483
+ {
2484
+ name: "committed diffs are distinguished with NO base ref (fallback ladder)",
2485
+ reason: "without origin/master the committed identity collapses to a constant",
2486
+ run: () => {
2487
+ const { repo, base } = selfTestRepo({ withBase: false });
2488
+ try {
2489
+ return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
2490
+ } finally {
2491
+ rmSync2(repo, { recursive: true, force: true });
2492
+ }
2493
+ }
2494
+ },
2495
+ {
2496
+ name: "a clean tree is not the digest of an empty path list",
2497
+ reason: 'a clean tree still hashes to sha256("validation-worktree-v2\\0"), the constant measured in #1831',
2498
+ run: () => {
2499
+ const { repo } = selfTestRepo({ withBase: true });
2500
+ try {
2501
+ return validationDiffHash(repo) !== createHash2("sha256").update("validation-worktree-v2\0").digest("hex");
2502
+ } finally {
2503
+ rmSync2(repo, { recursive: true, force: true });
2504
+ }
2505
+ }
2506
+ },
2507
+ {
2508
+ name: "an uncommitted edit still changes the identity",
2509
+ reason: "the working-tree half of the identity stopped responding to edits",
2510
+ run: () => {
2511
+ const { repo } = selfTestRepo({ withBase: true });
2512
+ try {
2513
+ const before = validationDiffHash(repo);
2514
+ write(repo, "src/base.txt", "edited in the working tree\n");
2515
+ return validationDiffHash(repo) !== before;
2516
+ } finally {
2517
+ rmSync2(repo, { recursive: true, force: true });
2518
+ }
2519
+ }
2520
+ },
2521
+ {
2522
+ // The "can pass" half. Without it every case above is satisfied by a hash that simply
2523
+ // changes every call, which would identify nothing and invalidate every record.
2524
+ name: "one unchanged state hashes to one value",
2525
+ reason: "the identity is not stable for an unchanged state \u2014 every record would read stale",
2526
+ run: () => {
2527
+ const { repo } = selfTestRepo({ withBase: true });
2528
+ try {
2529
+ write(repo, "src/base.txt", "edited in the working tree\n");
2530
+ return validationDiffHash(repo) === validationDiffHash(repo);
2531
+ } finally {
2532
+ rmSync2(repo, { recursive: true, force: true });
2533
+ }
2534
+ }
2535
+ }
2536
+ ];
2537
+ if (process.argv[1] && basename(process.argv[1]) === "diff-hash.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) {
2538
+ if (process.argv.slice(2).includes("--self-test")) {
2539
+ process.stdout.write("\u25B8 diff-hash --self-test: proving the identity can change, and can hold\n");
2540
+ const failed2 = runSelfTest();
2541
+ if (failed2) {
2542
+ process.stderr.write(
2543
+ `\u2717 diff-hash self-test FAILED (${failed2} case(s)) \u2014 the evidence record's diff identity cannot be trusted
2544
+ `
2545
+ );
2546
+ process.exit(1);
2547
+ }
2548
+ process.stdout.write(`\u2713 diff-hash self-test passed (${SELF_TEST_CASES.length} cases)
2549
+ `);
2550
+ } else {
2551
+ process.stdout.write(`${validationDiffHash()}
2552
+ `);
2553
+ }
2399
2554
  }
2400
2555
  }
2401
- return { size, version: version2, mask: bestMask, modules: best };
2556
+ });
2557
+
2558
+ // ../../core/serve-storage/src/widget-identity.ts
2559
+ function stableWidgets(widgets) {
2560
+ if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return widgets;
2561
+ return Object.fromEntries(Object.entries(widgets).map(([id, value]) => {
2562
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [id, value];
2563
+ const { contentEtag: _regenerated, ...stable } = value;
2564
+ return [id, stable];
2565
+ }));
2402
2566
  }
2403
- function applyFormat(m, size, mask) {
2404
- const bits = bchFormat(1 << 3 | mask);
2405
- const on3 = (i) => (bits >>> i & 1) === 1;
2406
- const put = (x, y, dark) => {
2407
- m[y][x] = dark;
2408
- };
2409
- for (let i = 0; i <= 5; i++) put(8, i, on3(i));
2410
- put(8, 7, on3(6));
2411
- put(8, 8, on3(7));
2412
- put(7, 8, on3(8));
2413
- for (let i = 9; i < 15; i++) put(14 - i, 8, on3(i));
2414
- for (let i = 0; i < 8; i++) put(size - 1 - i, 8, on3(i));
2415
- for (let i = 8; i < 15; i++) put(8, size - 15 + i, on3(i));
2416
- put(8, size - 8, true);
2417
- }
2418
- function applyVersion(m, size, version2) {
2419
- const bits = bchVersion(version2);
2420
- for (let i = 0; i < 18; i++) {
2421
- const on3 = (bits >>> i & 1) === 1;
2422
- const a = size - 11 + i % 3, b = Math.floor(i / 3);
2423
- m[a][b] = on3;
2424
- m[b][a] = on3;
2425
- }
2426
- }
2427
- var ECC_PER_BLOCK_L, NUM_BLOCKS_L, QR_MAX_VERSION, totalCodewords, dataCodewords, EXP, LOG, gfMul, MASKS;
2428
- var init_qr = __esm({
2429
- "../../core/admin-web/src/utils/qr.ts"() {
2430
- ECC_PER_BLOCK_L = [0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28];
2431
- NUM_BLOCKS_L = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8];
2432
- QR_MAX_VERSION = 20;
2433
- totalCodewords = (ver) => Math.floor(rawDataModules(ver) / 8);
2434
- dataCodewords = (ver) => totalCodewords(ver) - ECC_PER_BLOCK_L[ver] * NUM_BLOCKS_L[ver];
2435
- EXP = new Uint8Array(512);
2436
- LOG = new Uint8Array(256);
2437
- (() => {
2438
- let x = 1;
2439
- for (let i = 0; i < 255; i++) {
2440
- EXP[i] = x;
2441
- LOG[x] = i;
2442
- x <<= 1;
2443
- if (x & 256) x ^= 285;
2444
- }
2445
- for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
2446
- })();
2447
- gfMul = (a, b) => a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]];
2448
- MASKS = [
2449
- (x, y) => (x + y) % 2 === 0,
2450
- (_x, y) => y % 2 === 0,
2451
- (x) => x % 3 === 0,
2452
- (x, y) => (x + y) % 3 === 0,
2453
- (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0,
2454
- (x, y) => x * y % 2 + x * y % 3 === 0,
2455
- (x, y) => (x * y % 2 + x * y % 3) % 2 === 0,
2456
- (x, y) => ((x + y) % 2 + x * y % 3) % 2 === 0
2457
- ];
2567
+ var init_widget_identity = __esm({
2568
+ "../../core/serve-storage/src/widget-identity.ts"() {
2458
2569
  }
2459
2570
  });
2460
2571
 
2461
- // ../../core/dev-server/src/wix-placeholders.ts
2462
- var PLACEHOLDER_CLIENT_ID;
2463
- var init_wix_placeholders = __esm({
2464
- "../../core/dev-server/src/wix-placeholders.ts"() {
2465
- PLACEHOLDER_CLIENT_ID = "YOUR_WIX_CLIENT_ID";
2572
+ // src/attestation.ts
2573
+ import { execFileSync as execFileSync2 } from "node:child_process";
2574
+ import { createHash as createHash3, randomBytes } from "node:crypto";
2575
+ import {
2576
+ existsSync as existsSync3,
2577
+ lstatSync as lstatSync3,
2578
+ mkdirSync as mkdirSync5,
2579
+ realpathSync,
2580
+ readlinkSync as readlinkSync2,
2581
+ readFileSync as readFileSync5,
2582
+ readdirSync as readdirSync3,
2583
+ unlinkSync,
2584
+ writeFileSync as writeFileSync5
2585
+ } from "node:fs";
2586
+ import { basename as basename2, join as join6, relative, resolve as resolve6 } from "node:path";
2587
+ function attestationVersionOf(value) {
2588
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2589
+ const version2 = value.attestation;
2590
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 > 0 ? version2 : null;
2591
+ }
2592
+ function classifyRetainedRun(dir, name) {
2593
+ let value;
2594
+ try {
2595
+ value = JSON.parse(readFileSync5(join6(dir, name), "utf8"));
2596
+ } catch {
2597
+ return { kind: "unreadable" };
2466
2598
  }
2467
- });
2468
-
2469
- // ../../core/ts/src/widget-vocabulary.ts
2470
- function isComponentAllowedForFamily(key2, family) {
2471
- return WIDGET_VOCABULARY[key2]?.[family] === true;
2599
+ if (isAttestationV2(value)) return { kind: "current", run: value };
2600
+ const version2 = attestationVersionOf(value);
2601
+ if (version2 === null || version2 === ATTESTATION_VERSION) return { kind: "malformed" };
2602
+ return { kind: version2 < ATTESTATION_VERSION ? "legacy" : "future" };
2472
2603
  }
2473
- var WIDGET_VOCABULARY, WIDGET_COMPONENTS;
2474
- var init_widget_vocabulary = __esm({
2475
- "../../core/ts/src/widget-vocabulary.ts"() {
2476
- WIDGET_VOCABULARY = {
2477
- "himalaya.VStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: true },
2478
- "himalaya.HStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: true },
2479
- "himalaya.ZStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2480
- "himalaya.Card": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
2481
- "himalaya.Text": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2482
- "himalaya.Image": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: true },
2483
- "himalaya.Icon": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2484
- "himalaya.Divider": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
2485
- "himalaya.Spacer": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2486
- "himalaya.Button": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: false },
2487
- "himalaya.ProgressBar": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2488
- "himalaya.Gauge": { ios_home: true, ios_lock: true, android_home: false, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2489
- // Line/area/bar sparkline. No Glance Canvas → android_home:false (degrades), like Gauge.
2490
- "himalaya.Chart": { ios_home: true, ios_lock: true, android_home: false, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
2491
- "himalaya.Label": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
2492
- "himalaya.Chip": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
2493
- "himalaya.ListItem": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
2494
- "himalaya.LazyList": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
2495
- // ── RFC 0052 temporal vocabulary: a DECLARED divergence, not a gap ──────────
2496
- // A 42-cell month grid and an hour-gutter day timetable are not "too big for a
2497
- // widget" — they are the wrong SHAPE for one, and both are interactive (month
2498
- // paging, tap-to-select, drag-to-reschedule) where a widget snapshot is frozen.
2499
- // Shrinking either produces something illegible rather than something smaller,
2500
- // so they are excluded from EVERY family, deliberately.
2501
- "himalaya.MonthCalendar": { ios_home: false, ios_lock: false, android_home: false, web_card: false, ios_live_activity: false, watch_complication: false, car_list: false },
2502
- "himalaya.TimeGrid": { ios_home: false, ios_lock: false, android_home: false, web_card: false, ios_live_activity: false, watch_complication: false, car_list: false },
2503
- // Agenda IS admitted: it is list-shaped, so it degrades to "the next few things"
2504
- // exactly the way LazyList/ListItem do — and "what's next today" is the single
2505
- // most useful widget a calendar can produce. Its row mirrors LazyList's: the
2506
- // accessory surfaces (lock / Live Activity / complication) drop lists entirely.
2507
- "himalaya.Agenda": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true }
2508
- };
2509
- WIDGET_COMPONENTS = new Set(Object.keys(WIDGET_VOCABULARY));
2604
+ function screenshotArtifactPath(contentDir2, hash) {
2605
+ return join6(resolve6(contentDir2), ".himi", "attestations", "artifacts", `${hash}.png`);
2606
+ }
2607
+ function canonical(value) {
2608
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2609
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
2610
+ const record2 = value;
2611
+ return `{${Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${canonical(record2[key2])}`).join(",")}}`;
2612
+ }
2613
+ function releasePayloadHash(payload) {
2614
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return sha2562(canonical(payload));
2615
+ const record2 = payload;
2616
+ const manifest = record2.manifest;
2617
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return sha2562(canonical(payload));
2618
+ const manifestRecord = manifest;
2619
+ const widgets = manifestRecord.widgets;
2620
+ if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return sha2562(canonical(payload));
2621
+ return sha2562(canonical({ ...record2, manifest: { ...manifestRecord, widgets: stableWidgets(widgets) } }));
2622
+ }
2623
+ function withoutSelfHash(attestation) {
2624
+ const { selfHash: _selfHash, ...body } = attestation;
2625
+ return body;
2626
+ }
2627
+ function selfHashFor(attestation) {
2628
+ return sha2562(canonical(withoutSelfHash(attestation)));
2629
+ }
2630
+ function repoRoot(contentDir2) {
2631
+ try {
2632
+ const root = execFileSync2("git", ["-C", contentDir2, "rev-parse", "--show-toplevel"], {
2633
+ encoding: "utf8",
2634
+ stdio: ["ignore", "pipe", "ignore"]
2635
+ }).trim();
2636
+ if (!root) return null;
2637
+ execFileSync2("git", ["-C", root, "rev-parse", "--verify", "--quiet", "HEAD^{commit}"], { stdio: "ignore" });
2638
+ return root;
2639
+ } catch {
2640
+ return null;
2510
2641
  }
2511
- });
2512
-
2513
- // ../../core/ts/src/preview-surfaces.ts
2514
- function surfaceById(id) {
2515
- return PREVIEW_SURFACES.find((s) => s.id === id);
2516
2642
  }
2517
- function surfaceFrozenFamily(surface) {
2518
- const gate = surface.gate;
2519
- return gate.kind === "family" || gate.kind === "projection" ? gate.family : void 0;
2643
+ function storedContentDir(contentDir2) {
2644
+ const absolute = resolve6(contentDir2);
2645
+ const root = repoRoot(absolute);
2646
+ return root ? relative(root, absolute) || "." : ".";
2520
2647
  }
2521
- function isWidgetFamilySurface(surface) {
2522
- return surface.gate.kind === "family";
2648
+ function currentGitDiffHash(contentDir2) {
2649
+ const root = repoRoot(resolve6(contentDir2));
2650
+ if (!root) return "unavailable";
2651
+ return validationDiffHash(root);
2523
2652
  }
2524
- function surfaceRenderMode(surface) {
2525
- const { kind } = surface.gate;
2526
- return kind === "family" || kind === "projection" ? "frozen" : "interactive";
2653
+ function currentGitUntrackedHash(contentDir2) {
2654
+ const unresolved = resolve6(contentDir2);
2655
+ const absolute = realpathSync.native(unresolved);
2656
+ const root = repoRoot(absolute);
2657
+ if (!root) return sourceTreeHash(absolute);
2658
+ const scope = relative(root, absolute) || ".";
2659
+ const listed = execFileSync2("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z", "--", scope], {
2660
+ encoding: "utf8",
2661
+ maxBuffer: GIT_HASH_MAX_BUFFER
2662
+ });
2663
+ const hash = createHash3("sha256");
2664
+ for (const path2 of listed.split("\0").filter(Boolean).sort()) {
2665
+ const local = relative(absolute, join6(root, path2));
2666
+ const segs = local.split(/[\\/]/);
2667
+ if (NOT_ATTESTED_DIRS.has(segs[0])) continue;
2668
+ if (segs.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(segs[0])) continue;
2669
+ const full = join6(root, path2);
2670
+ const stats = lstatSync3(full, { throwIfNoEntry: false });
2671
+ if (!stats) continue;
2672
+ const kind = stats.isSymbolicLink() ? "link" : stats.isDirectory() ? "dir" : stats.isFile() ? "file" : null;
2673
+ if (!kind) continue;
2674
+ const payload = kind === "link" ? Buffer.from(readlinkSync2(full)) : kind === "file" ? readFileSync5(full) : Buffer.alloc(0);
2675
+ const pathBytes = Buffer.from(path2);
2676
+ hash.update(`${kind} ${pathBytes.length} ${payload.length}
2677
+ `).update(pathBytes).update(payload);
2678
+ }
2679
+ return hash.digest("hex");
2527
2680
  }
2528
- function isViewTarget(key2) {
2529
- return SURFACE_GROUPS.includes(key2) || surfaceById(key2) !== void 0;
2681
+ function sourceTreeHash(root) {
2682
+ const hash = createHash3("sha256");
2683
+ const visit = (dir) => {
2684
+ for (const entry of readdirSync3(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
2685
+ if (entry.isDirectory() && (entry.name === ".git" || entry.name === "node_modules" || NOT_ATTESTED_DIRS.has(entry.name))) continue;
2686
+ const path2 = join6(dir, entry.name);
2687
+ const local = relative(root, path2);
2688
+ if (dir === root && entry.isFile() && AGENT_TOOLING_ROOT_FILES.includes(entry.name)) continue;
2689
+ if (entry.isDirectory()) visit(path2);
2690
+ else if (entry.isFile()) hash.update(local).update("\0").update(readFileSync5(path2)).update("\0");
2691
+ else if (entry.isSymbolicLink()) hash.update(local).update("\0link\0").update(readlinkSync2(path2)).update("\0");
2692
+ }
2693
+ };
2694
+ visit(root);
2695
+ return hash.digest("hex");
2530
2696
  }
2531
- function viewTargetRenderMode(target) {
2532
- if (target === "frozen") return "frozen";
2533
- if (SURFACE_GROUPS.includes(target)) return "interactive";
2534
- const surface = surfaceById(target);
2535
- return surface ? surfaceRenderMode(surface) : void 0;
2697
+ function isAttestationV2(value) {
2698
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2699
+ const a = value;
2700
+ const coverage = a.coverage;
2701
+ const report2 = a.report;
2702
+ return a.attestation === 2 && typeof a.runId === "string" && ["himi-test", "himi-render", "himi-shots"].includes(a.tool) && [a.app, a.contentDir, a.gitDiffHash, a.gitUntrackedHash, a.releaseId, a.releaseHash, a.selfHash].every((item) => typeof item === "string" && item.length > 0) && stringRecord(a.descriptorHashes) && stringRecord(a.screenshots) && !!coverage && [coverage.screens, coverage.actionsDispatched, coverage.uncoveredFns, coverage.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && typeof report2?.ok === "boolean" && !!report2.counts && [report2.counts.errors, report2.counts.warnings, report2.counts.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && (a.prev === null || typeof a.prev === "string");
2536
2703
  }
2537
- function resolveSurfaceViewKey(surfaceId, registered) {
2538
- const ladder = SURFACE_VIEW_LADDER[surfaceId];
2539
- if (!ladder) return void 0;
2540
- const keys = registered instanceof Set ? registered : new Set(registered);
2541
- return ladder.find((rung) => keys.has(rung));
2704
+ function readLatestAttestation(contentDir2) {
2705
+ try {
2706
+ const parsed = JSON.parse(readFileSync5(join6(resolve6(contentDir2), ".himi", "attestations", "latest.json"), "utf8"));
2707
+ return isAttestationV2(parsed) ? parsed : null;
2708
+ } catch {
2709
+ return null;
2710
+ }
2542
2711
  }
2543
- function isTargeted(surface, platforms) {
2544
- if (surface.platformKey === null) return true;
2545
- if (surface.platformKey === "macos") return platforms.macos !== false;
2546
- return platforms[surface.platformKey] === true;
2712
+ function readRetainedAttestations(contentDir2) {
2713
+ const dir = join6(resolve6(contentDir2), ".himi", "attestations");
2714
+ if (!existsSync3(dir)) return { runs: [], errors: [] };
2715
+ try {
2716
+ const names = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
2717
+ const parsedRuns = [];
2718
+ const errors = [];
2719
+ for (const name of names) {
2720
+ const classified = classifyRetainedRun(dir, name);
2721
+ if (classified.kind === "current") parsedRuns.push(classified.run);
2722
+ else if (classified.kind === "legacy") {
2723
+ errors.push(`attestation history contains retained run ${name} from an older attestation version \u2014 re-run himi test/render to start a v${ATTESTATION_VERSION} chain`);
2724
+ } else if (classified.kind === "future") {
2725
+ errors.push(`attestation history contains retained run ${name} from a newer attestation version \u2014 upgrade himi rather than deleting it`);
2726
+ } else if (classified.kind === "unreadable") {
2727
+ errors.push(`attestation history contains unreadable retained run ${name}`);
2728
+ } else errors.push(`attestation history contains malformed retained run ${name}`);
2729
+ }
2730
+ const latest = readLatestAttestation(contentDir2);
2731
+ if (existsSync3(join6(dir, "latest.json")) && !latest) errors.push("attestation history has no valid latest.json");
2732
+ else if (names.length > 0 && !latest) errors.push("attestation history has no latest.json");
2733
+ const byHash = new Map(parsedRuns.map((run2) => [run2.selfHash, run2]));
2734
+ if (byHash.size !== parsedRuns.length) errors.push("attestation history contains duplicate self hashes");
2735
+ for (const run2 of parsedRuns) {
2736
+ if (selfHashFor(run2) !== run2.selfHash) errors.push(`selfHash does not match retained run ${run2.runId}`);
2737
+ for (const expected of Object.values(run2.screenshots)) {
2738
+ const path2 = screenshotArtifactPath(contentDir2, expected);
2739
+ if (!existsSync3(path2)) errors.push(`missing screenshot for retained run ${run2.runId}: ${basename2(path2)}`);
2740
+ else if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match retained run ${run2.runId}: ${basename2(path2)}`);
2741
+ }
2742
+ }
2743
+ const runs = [];
2744
+ const seen = /* @__PURE__ */ new Set();
2745
+ let cursor = latest ?? void 0;
2746
+ while (cursor && !seen.has(cursor.selfHash)) {
2747
+ runs.push(cursor);
2748
+ seen.add(cursor.selfHash);
2749
+ cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
2750
+ }
2751
+ if (latest && !byHash.has(latest.selfHash)) errors.push("latest.json has no matching retained run");
2752
+ if (seen.size !== parsedRuns.length) errors.push("attestation hash chain does not reach every retained run");
2753
+ runs.push(...parsedRuns.filter((run2) => !seen.has(run2.selfHash)).sort((a, b) => a.runId < b.runId ? 1 : -1));
2754
+ return {
2755
+ runs,
2756
+ errors
2757
+ };
2758
+ } catch {
2759
+ return { runs: [], errors: ["attestation history could not be read"] };
2760
+ }
2547
2761
  }
2548
- function surfacesForApp(platforms, producedFamilies) {
2549
- const produced = producedFamilies === void 0 ? void 0 : producedFamilies instanceof Set ? producedFamilies : new Set(producedFamilies);
2550
- return PREVIEW_SURFACES.map((surface) => {
2551
- const inEnvelope = isTargeted(surface, platforms);
2552
- const family = isWidgetFamilySurface(surface) ? surfaceFrozenFamily(surface) : void 0;
2553
- if (produced && family !== void 0) {
2554
- const isProduced = produced.has(family);
2555
- return inEnvelope && isProduced ? { surface, targeted: true, produced: true } : {
2556
- surface,
2557
- targeted: false,
2558
- produced: isProduced,
2559
- // The four native families carry no platform key, so there is nothing to
2560
- // offer; `web_card` and `watch_complication` DO have one, and when that is
2561
- // the half that is missing it is still the right advice. Hence the guard —
2562
- // it is load-bearing for two families, not defensive padding.
2563
- ...inEnvelope ? {} : surface.platformKey ? { optIn: `"${surface.platformKey}": true` } : {},
2564
- ...isProduced ? {} : {
2565
- // The rebuild half is not padding: the produced set is read off BUILT
2566
- // bundles, so an author who has just written the registration and not
2567
- // rebuilt lands here too, and "write defineWidget" alone would be advice
2568
- // to write the code they are looking at.
2569
- needs: `defineWidget({ family: "${family}", \u2026 }) in a Tier-5 bundle \u2014 and a rebuild, if you just added it`
2570
- }
2571
- };
2762
+ function selectAttestationForPush(runs) {
2763
+ const newestBehavioral = runs.find((run2) => run2.tool === "himi-test");
2764
+ if (!newestBehavioral) return runs[0];
2765
+ const behavioralIndex = runs.indexOf(newestBehavioral);
2766
+ const newerFailure = runs.slice(0, behavioralIndex).find((run2) => run2.report.ok === false);
2767
+ return newerFailure ?? newestBehavioral;
2768
+ }
2769
+ function inRepoCommit(releaseId) {
2770
+ const m = /^([0-9a-f]{40})(?:-[0-9a-f]{12})?$/.exec(releaseId ?? "");
2771
+ return m ? m[1] : null;
2772
+ }
2773
+ function sameReleaseCommit(a, b) {
2774
+ if (a !== void 0 && a === b) return true;
2775
+ const commit = inRepoCommit(a);
2776
+ return commit !== null && commit === inRepoCommit(b);
2777
+ }
2778
+ function pushEvidence(contentDir2, releaseId, verify) {
2779
+ const retained = readRetainedAttestations(contentDir2);
2780
+ const sameCommit = retained.runs.filter((candidate) => sameReleaseCommit(candidate.releaseId, releaseId));
2781
+ const verified = sameCommit.filter((candidate) => candidate.releaseId === releaseId).filter(verify);
2782
+ const selected = selectAttestationForPush(verified);
2783
+ return {
2784
+ ...selected ? { selected } : {},
2785
+ historyErrors: retained.errors,
2786
+ staleRuns: verified.length === 0 ? sameCommit.length : 0
2787
+ };
2788
+ }
2789
+ function summaryOf(attestation) {
2790
+ return {
2791
+ runId: attestation.runId,
2792
+ selfHash: attestation.selfHash,
2793
+ tool: attestation.tool,
2794
+ screenIds: Object.keys(attestation.descriptorHashes).sort(),
2795
+ coverage: attestation.coverage,
2796
+ report: { ok: attestation.report.ok, counts: attestation.report.counts },
2797
+ gitDiffHash: attestation.gitDiffHash,
2798
+ releaseHash: attestation.releaseHash
2799
+ };
2800
+ }
2801
+ function writeAttestation(input, opts = {}) {
2802
+ const contentDir2 = resolve6(input.contentDir);
2803
+ const dir = join6(contentDir2, ".himi", "attestations");
2804
+ mkdirSync5(dir, { recursive: true });
2805
+ const previous = readLatestAttestation(contentDir2);
2806
+ const runId = `${(opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()}-${(opts.randomSuffix ?? (() => randomBytes(3).toString("hex")))()}`;
2807
+ const descriptorHashes = Object.fromEntries(
2808
+ Object.entries(input.descriptors).sort(([a], [b]) => a.localeCompare(b)).map(([screen, bytes2]) => [screen, sha2562(bytes2)])
2809
+ );
2810
+ const screenshotEntries = Object.entries(input.screenshots ?? {}).sort(([a], [b]) => a.localeCompare(b)).filter(([, path2]) => existsSync3(path2)).map(([screen, path2]) => {
2811
+ const bytes2 = readFileSync5(path2);
2812
+ return { screen, bytes: bytes2, hash: sha2562(bytes2) };
2813
+ });
2814
+ const screenshots = Object.fromEntries(screenshotEntries.map(({ screen, hash }) => [screen, hash]));
2815
+ for (const { bytes: bytes2, hash } of screenshotEntries) {
2816
+ const artifact = screenshotArtifactPath(contentDir2, hash);
2817
+ mkdirSync5(join6(dir, "artifacts"), { recursive: true });
2818
+ if (!existsSync3(artifact)) writeFileSync5(artifact, bytes2);
2819
+ }
2820
+ const unsigned = {
2821
+ attestation: 2,
2822
+ runId,
2823
+ tool: input.tool,
2824
+ app: input.app,
2825
+ contentDir: storedContentDir(contentDir2),
2826
+ gitDiffHash: input.gitDiffHash,
2827
+ releaseId: input.releaseId,
2828
+ releaseHash: releasePayloadHash(input.releasePayload),
2829
+ gitUntrackedHash: currentGitUntrackedHash(contentDir2),
2830
+ descriptorHashes,
2831
+ screenshots,
2832
+ coverage: input.coverage,
2833
+ report: input.report,
2834
+ prev: previous?.selfHash ?? null
2835
+ };
2836
+ const attestation = { ...unsigned, selfHash: "" };
2837
+ attestation.selfHash = selfHashFor(attestation);
2838
+ const json5 = JSON.stringify(attestation, null, 2) + "\n";
2839
+ const runFile = `${runId.replace(/:/g, "-")}.json`;
2840
+ writeFileSync5(join6(dir, runFile), json5);
2841
+ writeFileSync5(join6(dir, "latest.json"), json5);
2842
+ const keep = Math.max(1, opts.maxRuns ?? DEFAULT_MAX_RUNS);
2843
+ const allPriorRuns = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").filter((name) => name !== runFile).sort().reverse();
2844
+ const priorRuns = allPriorRuns.filter((name) => {
2845
+ const kind = classifyRetainedRun(dir, name).kind;
2846
+ if (kind === "legacy") {
2847
+ unlinkSync(join6(dir, name));
2848
+ return false;
2572
2849
  }
2573
- if (inEnvelope) return { surface, targeted: true };
2574
- return surface.platformKey ? { surface, targeted: false, optIn: `"${surface.platformKey}": true` } : { surface, targeted: false };
2850
+ return kind !== "future";
2851
+ });
2852
+ for (const name of priorRuns.slice(Math.max(0, keep - 1))) unlinkSync(join6(dir, name));
2853
+ const retainedFiles = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
2854
+ const retainedRuns = retainedFiles.map((name) => {
2855
+ const classified = classifyRetainedRun(dir, name);
2856
+ return classified.kind === "current" ? classified.run : null;
2575
2857
  });
2858
+ if (retainedRuns.every((run2) => run2 !== null)) {
2859
+ const retainedScreenshotHashes = new Set(
2860
+ retainedRuns.flatMap((run2) => Object.values(run2.screenshots))
2861
+ );
2862
+ const artifactDir = join6(dir, "artifacts");
2863
+ if (existsSync3(artifactDir)) {
2864
+ for (const name of readdirSync3(artifactDir).filter((candidate) => candidate.endsWith(".png"))) {
2865
+ if (!retainedScreenshotHashes.has(name.slice(0, -4))) unlinkSync(join6(artifactDir, name));
2866
+ }
2867
+ }
2868
+ }
2869
+ return attestation;
2576
2870
  }
2577
- function resolveSurfaceEnvelope(platforms, requested) {
2578
- const envelope = PREVIEW_SURFACES.filter((s) => isTargeted(s, platforms)).map((s) => s.id);
2579
- if (requested == null) return { enabled: envelope, rejected: [] };
2580
- const inEnvelope = new Set(envelope);
2581
- const enabled = [];
2582
- const rejected = [];
2583
- for (const id of requested) {
2584
- if (inEnvelope.has(id)) {
2585
- if (!enabled.includes(id)) enabled.push(id);
2586
- } else if (!surfaceById(id)) {
2587
- rejected.push({ id, reason: `"${id}" is not a surface` });
2871
+ function verifyAttestation(attestation, contentDir2, descriptors, expectedApp, releasePayload, opts = {}) {
2872
+ const errors = [];
2873
+ if (selfHashFor(attestation) !== attestation.selfHash) errors.push("selfHash does not match the attestation bytes");
2874
+ try {
2875
+ const dir = join6(resolve6(contentDir2), ".himi", "attestations");
2876
+ const latest = readLatestAttestation(contentDir2);
2877
+ if (!latest || opts.requireLatest !== false && latest.selfHash !== attestation.selfHash) {
2878
+ errors.push("attestation does not match latest.json");
2588
2879
  } else {
2589
- const key2 = surfaceById(id).platformKey;
2590
- rejected.push({
2591
- id,
2592
- reason: `"${id}" is outside this binary's envelope \u2014 add ${key2 ? `"${key2}": true` : "the target"} to apps/<app>/platforms.json and ship a new binary`
2593
- });
2880
+ const classified = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").map((name) => classifyRetainedRun(dir, name)).filter((entry) => entry.kind !== "future");
2881
+ const retained = classified.map((entry) => entry.kind === "current" ? entry.run : null);
2882
+ if (retained.some((run2) => run2 === null)) {
2883
+ errors.push("attestation history contains a malformed retained run");
2884
+ } else {
2885
+ const runs = retained;
2886
+ const byHash = new Map(runs.map((run2) => [run2.selfHash, run2]));
2887
+ if (!byHash.has(attestation.selfHash)) errors.push("attestation has no matching retained run");
2888
+ if (!byHash.has(latest.selfHash)) {
2889
+ errors.push("latest.json has no matching retained run");
2890
+ } else {
2891
+ const seen = /* @__PURE__ */ new Set();
2892
+ let cursor = latest;
2893
+ while (cursor && !seen.has(cursor.selfHash)) {
2894
+ seen.add(cursor.selfHash);
2895
+ if (selfHashFor(cursor) !== cursor.selfHash) {
2896
+ errors.push(`selfHash does not match retained run ${cursor.runId}`);
2897
+ break;
2898
+ }
2899
+ cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
2900
+ }
2901
+ if (seen.size !== runs.length) errors.push("attestation hash chain does not reach every retained run");
2902
+ }
2903
+ }
2594
2904
  }
2905
+ } catch {
2906
+ errors.push("attestation history could not be verified");
2595
2907
  }
2596
- return { enabled: envelope.filter((id) => enabled.includes(id)), rejected };
2597
- }
2598
- function isComponentAllowed(surface, componentType) {
2599
- const gate = surface.gate;
2600
- if (gate.kind === "none") return { allowed: true };
2601
- if (gate.kind === "framework") {
2602
- const missing = gate.unavailable[componentType];
2603
- return missing ? { allowed: false, reason: `${missing} is unavailable on ${gate.platform}` } : { allowed: true };
2908
+ if (attestation.app !== expectedApp) errors.push(`attestation app does not match ${expectedApp}`);
2909
+ if (attestation.contentDir !== storedContentDir(contentDir2)) errors.push("attestation contentDir does not match the requested content directory");
2910
+ if (releasePayloadHash(releasePayload) !== attestation.releaseHash) {
2911
+ errors.push("releaseHash does not match the current release payload");
2604
2912
  }
2605
- if (gate.kind === "projection") {
2606
- return WIDGET_VOCABULARY[componentType]?.[gate.family] ? { allowed: true } : {
2607
- allowed: false,
2608
- // Name the catalog. "No Android Auto template expresses himalaya.Gauge"
2609
- // tells an author it is unsupported; adding "(templates: list)" tells them
2610
- // WHAT the surface can express, which is the thing they can act on.
2611
- reason: `no ${gate.platform} template expresses ${componentType} (templates: ${gate.templates.join(", ")})`
2612
- };
2913
+ try {
2914
+ const liveDiffHash = currentGitDiffHash(contentDir2);
2915
+ if (liveDiffHash !== "unavailable" && liveDiffHash !== attestation.gitDiffHash) {
2916
+ errors.push("gitDiffHash does not match the live git diff");
2917
+ }
2918
+ } catch {
2919
+ errors.push("gitDiffHash could not be recomputed from the live git diff");
2613
2920
  }
2614
- const row = WIDGET_VOCABULARY[componentType];
2615
- if (!row) return { allowed: true };
2616
- return row[gate.family] ? { allowed: true } : { allowed: false, reason: `not in the ${gate.family} allowlist` };
2617
- }
2618
- var PREVIEW_EXEMPT_PLATFORM_KEYS, WATCH_UNAVAILABLE, CAR_TEMPLATES, PROJECTION_SNAPSHOT_SIZE, widgetSurface, PREVIEW_SURFACES, SURFACE_GROUPS, SURFACE_VIEW_LADDER;
2619
- var init_preview_surfaces = __esm({
2620
- "../../core/ts/src/preview-surfaces.ts"() {
2621
- init_widget_vocabulary();
2622
- PREVIEW_EXEMPT_PLATFORM_KEYS = ["astro"];
2623
- WATCH_UNAVAILABLE = {
2624
- "himalaya.WebView": "WebKit",
2625
- "himalaya.VideoPlayer": "AVKit",
2626
- "himalaya.Lottie": "Lottie",
2921
+ try {
2922
+ const liveUntrackedHash = currentGitUntrackedHash(contentDir2);
2923
+ if (liveUntrackedHash !== "unavailable" && liveUntrackedHash !== attestation.gitUntrackedHash) {
2924
+ errors.push("gitUntrackedHash does not match the live untracked source bytes");
2925
+ }
2926
+ } catch {
2927
+ errors.push("gitUntrackedHash could not be recomputed from the live untracked source bytes");
2928
+ }
2929
+ for (const [screen, expected] of Object.entries(attestation.descriptorHashes)) {
2930
+ const bytes2 = descriptors[screen];
2931
+ if (bytes2 === void 0) {
2932
+ errors.push(`missing baked descriptor for ${screen}`);
2933
+ continue;
2934
+ }
2935
+ if (sha2562(bytes2) !== expected) errors.push(`descriptor hash does not match for ${screen}`);
2936
+ }
2937
+ for (const [screen, expected] of Object.entries(attestation.screenshots)) {
2938
+ const path2 = screenshotArtifactPath(contentDir2, expected);
2939
+ if (!existsSync3(path2)) {
2940
+ errors.push(`missing screenshot for ${screen}: ${basename2(path2)}`);
2941
+ continue;
2942
+ }
2943
+ if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match for ${screen}: ${basename2(path2)}`);
2944
+ }
2945
+ return { ok: errors.length === 0, errors };
2946
+ }
2947
+ var DEFAULT_MAX_RUNS, ATTESTATION_VERSION, GIT_HASH_MAX_BUFFER, sha2562, NOT_ATTESTED_DIRS, stringRecord;
2948
+ var init_attestation = __esm({
2949
+ "src/attestation.ts"() {
2950
+ init_diff_hash();
2951
+ init_source_deny();
2952
+ init_widget_identity();
2953
+ DEFAULT_MAX_RUNS = 20;
2954
+ ATTESTATION_VERSION = 2;
2955
+ GIT_HASH_MAX_BUFFER = 128 * 1024 * 1024;
2956
+ sha2562 = (bytes2) => createHash3("sha256").update(bytes2).digest("hex");
2957
+ NOT_ATTESTED_DIRS = /* @__PURE__ */ new Set([".himi", ...AGENT_TOOLING_DIRS]);
2958
+ stringRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
2959
+ }
2960
+ });
2961
+
2962
+ // ../../core/admin-web/src/utils/qr.ts
2963
+ function rawDataModules(ver) {
2964
+ let result = (16 * ver + 128) * ver + 64;
2965
+ if (ver >= 2) {
2966
+ const numAlign = Math.floor(ver / 7) + 2;
2967
+ result -= (25 * numAlign - 10) * numAlign - 55;
2968
+ if (ver >= 7) result -= 36;
2969
+ }
2970
+ return result;
2971
+ }
2972
+ function byteCapacity(ver) {
2973
+ const lenBits = ver < 10 ? 8 : 16;
2974
+ return dataCodewords(ver) - Math.ceil((4 + lenBits) / 8);
2975
+ }
2976
+ function fitVersion(len) {
2977
+ for (let v = 1; v <= QR_MAX_VERSION; v++) if (byteCapacity(v) >= len) return v;
2978
+ return void 0;
2979
+ }
2980
+ function rsDivisor(degree) {
2981
+ const result = new Array(degree).fill(0);
2982
+ result[degree - 1] = 1;
2983
+ let root = 1;
2984
+ for (let i = 0; i < degree; i++) {
2985
+ for (let j = 0; j < degree; j++) {
2986
+ result[j] = gfMul(result[j], root);
2987
+ if (j + 1 < degree) result[j] ^= result[j + 1];
2988
+ }
2989
+ root = gfMul(root, 2);
2990
+ }
2991
+ return result;
2992
+ }
2993
+ function rsRemainder(data, degree) {
2994
+ const divisor = rsDivisor(degree);
2995
+ const res = new Array(degree).fill(0);
2996
+ for (const b of data) {
2997
+ const factor = b ^ res.shift();
2998
+ res.push(0);
2999
+ for (let i = 0; i < degree; i++) res[i] ^= gfMul(divisor[i], factor);
3000
+ }
3001
+ return res;
3002
+ }
3003
+ function encodeData(bytes2, ver) {
3004
+ const bits = [];
3005
+ const push = (val, len) => {
3006
+ for (let i = len - 1; i >= 0; i--) bits.push(val >>> i & 1);
3007
+ };
3008
+ push(4, 4);
3009
+ push(bytes2.length, ver < 10 ? 8 : 16);
3010
+ for (const b of bytes2) push(b, 8);
3011
+ const capacityBits = dataCodewords(ver) * 8;
3012
+ push(0, Math.min(4, capacityBits - bits.length));
3013
+ while (bits.length % 8 !== 0) bits.push(0);
3014
+ const out = [];
3015
+ for (let i = 0; i < bits.length; i += 8) {
3016
+ let byte2 = 0;
3017
+ for (let j = 0; j < 8; j++) byte2 = byte2 << 1 | bits[i + j];
3018
+ out.push(byte2);
3019
+ }
3020
+ for (let pad2 = 236; out.length < dataCodewords(ver); pad2 ^= 236 ^ 17) out.push(pad2);
3021
+ return out;
3022
+ }
3023
+ function interleave(data, ver) {
3024
+ const numBlocks = NUM_BLOCKS_L[ver];
3025
+ const eccLen = ECC_PER_BLOCK_L[ver];
3026
+ const shortLen = Math.floor(totalCodewords(ver) / numBlocks) - eccLen;
3027
+ const numShort = numBlocks - totalCodewords(ver) % numBlocks;
3028
+ const blocks = [];
3029
+ const eccs = [];
3030
+ let off = 0;
3031
+ for (let i = 0; i < numBlocks; i++) {
3032
+ const len = shortLen + (i < numShort ? 0 : 1);
3033
+ const blk = data.slice(off, off + len);
3034
+ off += len;
3035
+ blocks.push(blk);
3036
+ eccs.push(rsRemainder(blk, eccLen));
3037
+ }
3038
+ const out = [];
3039
+ for (let i = 0; i < shortLen + 1; i++) {
3040
+ for (let b = 0; b < numBlocks; b++) {
3041
+ const v = blocks[b][i];
3042
+ if (v !== void 0) out.push(v);
3043
+ }
3044
+ }
3045
+ for (let i = 0; i < eccLen; i++) for (let b = 0; b < numBlocks; b++) out.push(eccs[b][i]);
3046
+ return out;
3047
+ }
3048
+ function alignmentPositions(ver) {
3049
+ if (ver === 1) return [];
3050
+ const num3 = Math.floor(ver / 7) + 2;
3051
+ const size = ver * 4 + 17;
3052
+ const step = Math.ceil((ver * 4 + 4) / (num3 * 2 - 2)) * 2;
3053
+ const res = [6];
3054
+ for (let pos = size - 7; res.length < num3; pos -= step) res.splice(1, 0, pos);
3055
+ return res;
3056
+ }
3057
+ function bchFormat(data) {
3058
+ let rem = data;
3059
+ for (let i = 0; i < 10; i++) rem = rem << 1 ^ (rem >>> 9) * 1335;
3060
+ return (data << 10 | rem) ^ 21522;
3061
+ }
3062
+ function bchVersion(ver) {
3063
+ let rem = ver;
3064
+ for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >>> 11) * 7973;
3065
+ return ver << 12 | rem;
3066
+ }
3067
+ function penalty(m, size) {
3068
+ let score = 0;
3069
+ const run2 = (get) => {
3070
+ for (let i = 0; i < size; i++) {
3071
+ let last = get(i, 0), len = 1;
3072
+ for (let j = 1; j < size; j++) {
3073
+ const cur = get(i, j);
3074
+ if (cur === last) {
3075
+ len++;
3076
+ if (len === 5) score += 3;
3077
+ else if (len > 5) score++;
3078
+ } else {
3079
+ last = cur;
3080
+ len = 1;
3081
+ }
3082
+ }
3083
+ }
3084
+ };
3085
+ run2((i, j) => m[i][j]);
3086
+ run2((i, j) => m[j][i]);
3087
+ for (let y = 0; y < size - 1; y++)
3088
+ for (let x = 0; x < size - 1; x++) {
3089
+ const c = m[y][x];
3090
+ if (c === m[y][x + 1] && c === m[y + 1][x] && c === m[y + 1][x + 1]) score += 3;
3091
+ }
3092
+ let dark = 0;
3093
+ for (const row of m) for (const c of row) if (c) dark++;
3094
+ const total = size * size;
3095
+ score += Math.floor(Math.abs(dark * 20 - total * 10) / total) * 10;
3096
+ return score;
3097
+ }
3098
+ function encodeQr(text5) {
3099
+ const bytes2 = [...new TextEncoder().encode(text5)];
3100
+ const version2 = fitVersion(bytes2.length);
3101
+ if (!version2) {
3102
+ throw new RangeError(`QR payload too long: ${bytes2.length} bytes exceeds version ${QR_MAX_VERSION} at EC level L`);
3103
+ }
3104
+ const codewords = interleave(encodeData(bytes2, version2), version2);
3105
+ const size = version2 * 4 + 17;
3106
+ const modules = Array.from({ length: size }, () => new Array(size).fill(false));
3107
+ const reserved = Array.from({ length: size }, () => new Array(size).fill(false));
3108
+ const set = (x, y, dark) => {
3109
+ modules[y][x] = dark;
3110
+ reserved[y][x] = true;
3111
+ };
3112
+ const finder = (cx, cy) => {
3113
+ for (let dy = -4; dy <= 4; dy++)
3114
+ for (let dx = -4; dx <= 4; dx++) {
3115
+ const x = cx + dx, y = cy + dy;
3116
+ if (x < 0 || y < 0 || x >= size || y >= size) continue;
3117
+ const d = Math.max(Math.abs(dx), Math.abs(dy));
3118
+ set(x, y, d !== 2 && d !== 4);
3119
+ }
3120
+ };
3121
+ finder(3, 3);
3122
+ finder(size - 4, 3);
3123
+ finder(3, size - 4);
3124
+ for (let i = 8; i < size - 8; i++) {
3125
+ set(i, 6, i % 2 === 0);
3126
+ set(6, i, i % 2 === 0);
3127
+ }
3128
+ for (const ay of alignmentPositions(version2))
3129
+ for (const ax of alignmentPositions(version2)) {
3130
+ if (ax === 6 && ay === 6 || ax === 6 && ay === size - 7 || ax === size - 7 && ay === 6) continue;
3131
+ for (let dy = -2; dy <= 2; dy++)
3132
+ for (let dx = -2; dx <= 2; dx++) set(ax + dx, ay + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
3133
+ }
3134
+ for (let i = 0; i < 9; i++) {
3135
+ reserved[i][8] = true;
3136
+ reserved[8][i] = true;
3137
+ }
3138
+ for (let i = 0; i < 8; i++) {
3139
+ reserved[size - 1 - i][8] = true;
3140
+ reserved[8][size - 1 - i] = true;
3141
+ }
3142
+ set(8, size - 8, true);
3143
+ if (version2 >= 7)
3144
+ for (let i = 0; i < 18; i++) {
3145
+ const a = size - 11 + i % 3, b = Math.floor(i / 3);
3146
+ reserved[a][b] = true;
3147
+ reserved[b][a] = true;
3148
+ }
3149
+ let bit = 0;
3150
+ const totalBits = codewords.length * 8;
3151
+ for (let right = size - 1; right >= 1; right -= 2) {
3152
+ if (right === 6) right = 5;
3153
+ for (let vert = 0; vert < size; vert++) {
3154
+ for (let j = 0; j < 2; j++) {
3155
+ const x = right - j;
3156
+ const upward = (right + 1 & 2) === 0;
3157
+ const y = upward ? size - 1 - vert : vert;
3158
+ if (reserved[y][x]) continue;
3159
+ modules[y][x] = bit < totalBits ? (codewords[bit >>> 3] >>> 7 - (bit & 7) & 1) === 1 : false;
3160
+ bit++;
3161
+ }
3162
+ }
3163
+ }
3164
+ let best = null, bestMask = 0, bestScore = Infinity;
3165
+ for (let mask = 0; mask < 8; mask++) {
3166
+ const cand = modules.map((row) => [...row]);
3167
+ for (let y = 0; y < size; y++)
3168
+ for (let x = 0; x < size; x++) if (!reserved[y][x] && MASKS[mask](x, y)) cand[y][x] = !cand[y][x];
3169
+ applyFormat(cand, size, mask);
3170
+ if (version2 >= 7) applyVersion(cand, size, version2);
3171
+ const s = penalty(cand, size);
3172
+ if (s < bestScore) {
3173
+ bestScore = s;
3174
+ best = cand;
3175
+ bestMask = mask;
3176
+ }
3177
+ }
3178
+ return { size, version: version2, mask: bestMask, modules: best };
3179
+ }
3180
+ function applyFormat(m, size, mask) {
3181
+ const bits = bchFormat(1 << 3 | mask);
3182
+ const on3 = (i) => (bits >>> i & 1) === 1;
3183
+ const put = (x, y, dark) => {
3184
+ m[y][x] = dark;
3185
+ };
3186
+ for (let i = 0; i <= 5; i++) put(8, i, on3(i));
3187
+ put(8, 7, on3(6));
3188
+ put(8, 8, on3(7));
3189
+ put(7, 8, on3(8));
3190
+ for (let i = 9; i < 15; i++) put(14 - i, 8, on3(i));
3191
+ for (let i = 0; i < 8; i++) put(size - 1 - i, 8, on3(i));
3192
+ for (let i = 8; i < 15; i++) put(8, size - 15 + i, on3(i));
3193
+ put(8, size - 8, true);
3194
+ }
3195
+ function applyVersion(m, size, version2) {
3196
+ const bits = bchVersion(version2);
3197
+ for (let i = 0; i < 18; i++) {
3198
+ const on3 = (bits >>> i & 1) === 1;
3199
+ const a = size - 11 + i % 3, b = Math.floor(i / 3);
3200
+ m[a][b] = on3;
3201
+ m[b][a] = on3;
3202
+ }
3203
+ }
3204
+ var ECC_PER_BLOCK_L, NUM_BLOCKS_L, QR_MAX_VERSION, totalCodewords, dataCodewords, EXP, LOG, gfMul, MASKS;
3205
+ var init_qr = __esm({
3206
+ "../../core/admin-web/src/utils/qr.ts"() {
3207
+ ECC_PER_BLOCK_L = [0, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28];
3208
+ NUM_BLOCKS_L = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8];
3209
+ QR_MAX_VERSION = 20;
3210
+ totalCodewords = (ver) => Math.floor(rawDataModules(ver) / 8);
3211
+ dataCodewords = (ver) => totalCodewords(ver) - ECC_PER_BLOCK_L[ver] * NUM_BLOCKS_L[ver];
3212
+ EXP = new Uint8Array(512);
3213
+ LOG = new Uint8Array(256);
3214
+ (() => {
3215
+ let x = 1;
3216
+ for (let i = 0; i < 255; i++) {
3217
+ EXP[i] = x;
3218
+ LOG[x] = i;
3219
+ x <<= 1;
3220
+ if (x & 256) x ^= 285;
3221
+ }
3222
+ for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
3223
+ })();
3224
+ gfMul = (a, b) => a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]];
3225
+ MASKS = [
3226
+ (x, y) => (x + y) % 2 === 0,
3227
+ (_x, y) => y % 2 === 0,
3228
+ (x) => x % 3 === 0,
3229
+ (x, y) => (x + y) % 3 === 0,
3230
+ (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0,
3231
+ (x, y) => x * y % 2 + x * y % 3 === 0,
3232
+ (x, y) => (x * y % 2 + x * y % 3) % 2 === 0,
3233
+ (x, y) => ((x + y) % 2 + x * y % 3) % 2 === 0
3234
+ ];
3235
+ }
3236
+ });
3237
+
3238
+ // ../../core/dev-server/src/wix-placeholders.ts
3239
+ var PLACEHOLDER_CLIENT_ID;
3240
+ var init_wix_placeholders = __esm({
3241
+ "../../core/dev-server/src/wix-placeholders.ts"() {
3242
+ PLACEHOLDER_CLIENT_ID = "YOUR_WIX_CLIENT_ID";
3243
+ }
3244
+ });
3245
+
3246
+ // ../../core/ts/src/widget-vocabulary.ts
3247
+ function isComponentAllowedForFamily(key2, family) {
3248
+ return WIDGET_VOCABULARY[key2]?.[family] === true;
3249
+ }
3250
+ var WIDGET_VOCABULARY, WIDGET_COMPONENTS;
3251
+ var init_widget_vocabulary = __esm({
3252
+ "../../core/ts/src/widget-vocabulary.ts"() {
3253
+ WIDGET_VOCABULARY = {
3254
+ "himalaya.VStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: true },
3255
+ "himalaya.HStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: true },
3256
+ "himalaya.ZStack": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3257
+ "himalaya.Card": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
3258
+ "himalaya.Text": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3259
+ "himalaya.Image": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: true },
3260
+ "himalaya.Icon": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3261
+ "himalaya.Divider": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
3262
+ "himalaya.Spacer": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3263
+ "himalaya.Button": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: false },
3264
+ "himalaya.ProgressBar": { ios_home: true, ios_lock: true, android_home: true, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3265
+ "himalaya.Gauge": { ios_home: true, ios_lock: true, android_home: false, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3266
+ // Line/area/bar sparkline. No Glance Canvas → android_home:false (degrades), like Gauge.
3267
+ "himalaya.Chart": { ios_home: true, ios_lock: true, android_home: false, web_card: true, ios_live_activity: true, watch_complication: true, car_list: false },
3268
+ "himalaya.Label": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
3269
+ "himalaya.Chip": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: true, watch_complication: false, car_list: false },
3270
+ "himalaya.ListItem": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
3271
+ "himalaya.LazyList": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true },
3272
+ // ── RFC 0052 temporal vocabulary: a DECLARED divergence, not a gap ──────────
3273
+ // A 42-cell month grid and an hour-gutter day timetable are not "too big for a
3274
+ // widget" — they are the wrong SHAPE for one, and both are interactive (month
3275
+ // paging, tap-to-select, drag-to-reschedule) where a widget snapshot is frozen.
3276
+ // Shrinking either produces something illegible rather than something smaller,
3277
+ // so they are excluded from EVERY family, deliberately.
3278
+ "himalaya.MonthCalendar": { ios_home: false, ios_lock: false, android_home: false, web_card: false, ios_live_activity: false, watch_complication: false, car_list: false },
3279
+ "himalaya.TimeGrid": { ios_home: false, ios_lock: false, android_home: false, web_card: false, ios_live_activity: false, watch_complication: false, car_list: false },
3280
+ // Agenda IS admitted: it is list-shaped, so it degrades to "the next few things"
3281
+ // exactly the way LazyList/ListItem do — and "what's next today" is the single
3282
+ // most useful widget a calendar can produce. Its row mirrors LazyList's: the
3283
+ // accessory surfaces (lock / Live Activity / complication) drop lists entirely.
3284
+ "himalaya.Agenda": { ios_home: true, ios_lock: false, android_home: true, web_card: true, ios_live_activity: false, watch_complication: false, car_list: true }
3285
+ };
3286
+ WIDGET_COMPONENTS = new Set(Object.keys(WIDGET_VOCABULARY));
3287
+ }
3288
+ });
3289
+
3290
+ // ../../core/ts/src/preview-surfaces.ts
3291
+ function surfaceById(id) {
3292
+ return PREVIEW_SURFACES.find((s) => s.id === id);
3293
+ }
3294
+ function surfaceFrozenFamily(surface) {
3295
+ const gate = surface.gate;
3296
+ return gate.kind === "family" || gate.kind === "projection" ? gate.family : void 0;
3297
+ }
3298
+ function isWidgetFamilySurface(surface) {
3299
+ return surface.gate.kind === "family";
3300
+ }
3301
+ function surfaceRenderMode(surface) {
3302
+ const { kind } = surface.gate;
3303
+ return kind === "family" || kind === "projection" ? "frozen" : "interactive";
3304
+ }
3305
+ function isViewTarget(key2) {
3306
+ return SURFACE_GROUPS.includes(key2) || surfaceById(key2) !== void 0;
3307
+ }
3308
+ function viewTargetRenderMode(target) {
3309
+ if (target === "frozen") return "frozen";
3310
+ if (SURFACE_GROUPS.includes(target)) return "interactive";
3311
+ const surface = surfaceById(target);
3312
+ return surface ? surfaceRenderMode(surface) : void 0;
3313
+ }
3314
+ function resolveSurfaceViewKey(surfaceId, registered) {
3315
+ const ladder = SURFACE_VIEW_LADDER[surfaceId];
3316
+ if (!ladder) return void 0;
3317
+ const keys = registered instanceof Set ? registered : new Set(registered);
3318
+ return ladder.find((rung) => keys.has(rung));
3319
+ }
3320
+ function isTargeted(surface, platforms) {
3321
+ if (surface.platformKey === null) return true;
3322
+ if (surface.platformKey === "macos") return platforms.macos !== false;
3323
+ return platforms[surface.platformKey] === true;
3324
+ }
3325
+ function surfacesForApp(platforms, producedFamilies) {
3326
+ const produced = producedFamilies === void 0 ? void 0 : producedFamilies instanceof Set ? producedFamilies : new Set(producedFamilies);
3327
+ return PREVIEW_SURFACES.map((surface) => {
3328
+ const inEnvelope = isTargeted(surface, platforms);
3329
+ const family = isWidgetFamilySurface(surface) ? surfaceFrozenFamily(surface) : void 0;
3330
+ if (produced && family !== void 0) {
3331
+ const isProduced = produced.has(family);
3332
+ return inEnvelope && isProduced ? { surface, targeted: true, produced: true } : {
3333
+ surface,
3334
+ targeted: false,
3335
+ produced: isProduced,
3336
+ // The four native families carry no platform key, so there is nothing to
3337
+ // offer; `web_card` and `watch_complication` DO have one, and when that is
3338
+ // the half that is missing it is still the right advice. Hence the guard —
3339
+ // it is load-bearing for two families, not defensive padding.
3340
+ ...inEnvelope ? {} : surface.platformKey ? { optIn: `"${surface.platformKey}": true` } : {},
3341
+ ...isProduced ? {} : {
3342
+ // The rebuild half is not padding: the produced set is read off BUILT
3343
+ // bundles, so an author who has just written the registration and not
3344
+ // rebuilt lands here too, and "write defineWidget" alone would be advice
3345
+ // to write the code they are looking at.
3346
+ needs: `defineWidget({ family: "${family}", \u2026 }) in a Tier-5 bundle \u2014 and a rebuild, if you just added it`
3347
+ }
3348
+ };
3349
+ }
3350
+ if (inEnvelope) return { surface, targeted: true };
3351
+ return surface.platformKey ? { surface, targeted: false, optIn: `"${surface.platformKey}": true` } : { surface, targeted: false };
3352
+ });
3353
+ }
3354
+ function resolveSurfaceEnvelope(platforms, requested) {
3355
+ const envelope = PREVIEW_SURFACES.filter((s) => isTargeted(s, platforms)).map((s) => s.id);
3356
+ if (requested == null) return { enabled: envelope, rejected: [] };
3357
+ const inEnvelope = new Set(envelope);
3358
+ const enabled = [];
3359
+ const rejected = [];
3360
+ for (const id of requested) {
3361
+ if (inEnvelope.has(id)) {
3362
+ if (!enabled.includes(id)) enabled.push(id);
3363
+ } else if (!surfaceById(id)) {
3364
+ rejected.push({ id, reason: `"${id}" is not a surface` });
3365
+ } else {
3366
+ const key2 = surfaceById(id).platformKey;
3367
+ rejected.push({
3368
+ id,
3369
+ reason: `"${id}" is outside this binary's envelope \u2014 add ${key2 ? `"${key2}": true` : "the target"} to apps/<app>/platforms.json and ship a new binary`
3370
+ });
3371
+ }
3372
+ }
3373
+ return { enabled: envelope.filter((id) => enabled.includes(id)), rejected };
3374
+ }
3375
+ function isComponentAllowed(surface, componentType) {
3376
+ const gate = surface.gate;
3377
+ if (gate.kind === "none") return { allowed: true };
3378
+ if (gate.kind === "framework") {
3379
+ const missing = gate.unavailable[componentType];
3380
+ return missing ? { allowed: false, reason: `${missing} is unavailable on ${gate.platform}` } : { allowed: true };
3381
+ }
3382
+ if (gate.kind === "projection") {
3383
+ return WIDGET_VOCABULARY[componentType]?.[gate.family] ? { allowed: true } : {
3384
+ allowed: false,
3385
+ // Name the catalog. "No Android Auto template expresses himalaya.Gauge"
3386
+ // tells an author it is unsupported; adding "(templates: list)" tells them
3387
+ // WHAT the surface can express, which is the thing they can act on.
3388
+ reason: `no ${gate.platform} template expresses ${componentType} (templates: ${gate.templates.join(", ")})`
3389
+ };
3390
+ }
3391
+ const row = WIDGET_VOCABULARY[componentType];
3392
+ if (!row) return { allowed: true };
3393
+ return row[gate.family] ? { allowed: true } : { allowed: false, reason: `not in the ${gate.family} allowlist` };
3394
+ }
3395
+ var PREVIEW_EXEMPT_PLATFORM_KEYS, WATCH_UNAVAILABLE, CAR_TEMPLATES, PROJECTION_SNAPSHOT_SIZE, widgetSurface, PREVIEW_SURFACES, SURFACE_GROUPS, SURFACE_VIEW_LADDER;
3396
+ var init_preview_surfaces = __esm({
3397
+ "../../core/ts/src/preview-surfaces.ts"() {
3398
+ init_widget_vocabulary();
3399
+ PREVIEW_EXEMPT_PLATFORM_KEYS = ["astro"];
3400
+ WATCH_UNAVAILABLE = {
3401
+ "himalaya.WebView": "WebKit",
3402
+ "himalaya.VideoPlayer": "AVKit",
3403
+ "himalaya.Lottie": "Lottie",
2627
3404
  "himalaya.Rive": "RiveRuntime"
2628
3405
  };
2629
3406
  CAR_TEMPLATES = ["list"];
@@ -3224,7 +4001,7 @@ function appConfigTs(app) {
3224
4001
  return join10(appDir(app), "config.ts");
3225
4002
  }
3226
4003
  var REPO_ROOT3, withoutExt;
3227
- var init_paths = __esm({
4004
+ var init_paths2 = __esm({
3228
4005
  "../store-release/paths.ts"() {
3229
4006
  REPO_ROOT3 = resolveRepoRoot2();
3230
4007
  withoutExt = (p) => p.replace(/\\/g, "/").replace(/\.(?:[mc]?ts|[mc]?js)$/, "");
@@ -3418,7 +4195,7 @@ function splashPlan(app, configTs, manifestPath, hasIosMarkers) {
3418
4195
  var LABEL_IOS_SPLASH, IOS_COLORSET_NAME, ANDROID_COLOR_RES, ANDROID_STYLE_NAME, ANDROID_SPLASH_FILE, ANDROID_SPLASH_PARENT, DEFAULT_BACKGROUND_TOKEN, GENERATED_XML_NOTE;
3419
4196
  var init_splash = __esm({
3420
4197
  "../native-config/splash.ts"() {
3421
- init_paths();
4198
+ init_paths2();
3422
4199
  LABEL_IOS_SPLASH = "himalaya:splash:info";
3423
4200
  IOS_COLORSET_NAME = "HimalayaLaunchBackground";
3424
4201
  ANDROID_COLOR_RES = "himalaya_splash_background";
@@ -14629,11 +15406,12 @@ function uxEvidenceForRelease(content, release) {
14629
15406
  const failedPlatforms = forRelease.filter(([, entry]) => entry.status === "failed").map(([platform]) => platform);
14630
15407
  return failedPlatforms.length > 0 ? { ...base, state: "failed", platforms, failedPlatforms, malformed } : { ...base, state: "recorded", platforms, malformed };
14631
15408
  }
14632
- if (recorded.some(([, entry]) => entry.releaseId === releaseId)) {
15409
+ const sameCommit = recorded.filter(([, entry]) => sameReleaseCommit(entry.releaseId, releaseId));
15410
+ if (sameCommit.length > 0) {
14633
15411
  return {
14634
15412
  ...base,
14635
15413
  state: "outdated",
14636
- platforms: recorded.filter(([, entry]) => entry.releaseId === releaseId).map(([platform]) => platform),
15414
+ platforms: sameCommit.map(([platform]) => platform),
14637
15415
  malformed
14638
15416
  };
14639
15417
  }
@@ -14650,6 +15428,7 @@ var NATIVE_PLATFORMS, NATIVE_AUDIT_STATUSES;
14650
15428
  var init_ux_evidence = __esm({
14651
15429
  "src/ux-evidence.ts"() {
14652
15430
  init_mobile_ux_contract();
15431
+ init_attestation();
14653
15432
  NATIVE_PLATFORMS = ["ios", "android"];
14654
15433
  NATIVE_AUDIT_STATUSES = ["passed", "failed", "unavailable"];
14655
15434
  }
@@ -15947,7 +16726,7 @@ function generationAllowed(app, configTs, opts = {}) {
15947
16726
  var DEFAULT_ACCENT_TOKEN, DEFAULT_GLYPH_SCALE, GLYPH_SCALE_MIN, GLYPH_SCALE_MAX, LEGACY_PLACEHOLDER_SHA256;
15948
16727
  var init_plan2 = __esm({
15949
16728
  "../app-icons/plan.ts"() {
15950
- init_paths();
16729
+ init_paths2();
15951
16730
  init_glyphs();
15952
16731
  DEFAULT_ACCENT_TOKEN = "accentPrimary";
15953
16732
  DEFAULT_GLYPH_SCALE = 0.62;
@@ -16015,7 +16794,7 @@ var ANDROID_ICON_BACKGROUND_RES, ANDROID_ADAPTIVE_FILES, ANDROID_ICON_BACKGROUND
16015
16794
  var init_icon2 = __esm({
16016
16795
  "../native-config/icon.ts"() {
16017
16796
  init_plan2();
16018
- init_paths();
16797
+ init_paths2();
16019
16798
  ANDROID_ICON_BACKGROUND_RES = "ic_launcher_background";
16020
16799
  ANDROID_ADAPTIVE_FILES = ["ic_launcher.xml", "ic_launcher_round.xml"];
16021
16800
  ANDROID_ICON_BACKGROUND_FILE = "ic_launcher_background.xml";
@@ -16524,7 +17303,7 @@ var init_gen_native_config = __esm({
16524
17303
  "../native-config/gen-native-config.ts"() {
16525
17304
  init_src2();
16526
17305
  init_src2();
16527
- init_paths();
17306
+ init_paths2();
16528
17307
  init_splash();
16529
17308
  init_icon2();
16530
17309
  init_live_activity();
@@ -22033,7 +22812,6 @@ function shouldRevealError(args) {
22033
22812
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
22034
22813
  var init_validation = __esm({
22035
22814
  "../../core/ts/src/validation.ts"() {
22036
- "use strict";
22037
22815
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
22038
22816
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
22039
22817
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -25634,7 +26412,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
25634
26412
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
25635
26413
  var init_worker_subtree = __esm({
25636
26414
  "../../core/ts/src/worker-subtree.ts"() {
25637
- "use strict";
25638
26415
  init_worker_subtree_safe_types_generated();
25639
26416
  WORKER_SUBTREE_CEILINGS = Object.freeze({
25640
26417
  maxEncodedBytes: 65536,
@@ -26948,7 +27725,7 @@ function ensureInitialFocusRuntime() {
26948
27725
  initialFocusScheduled = false;
26949
27726
  const targets = Array.from(document.querySelectorAll("[data-himalaya-initial-focus]"));
26950
27727
  const active = document.activeElement;
26951
- const unplaced = active == null || active === document.body;
27728
+ const unplaced = active == null || active === document.body || active.hasAttribute?.("data-himalaya-focus-landing") === true;
26952
27729
  const fresh = targets.filter((t) => !initialFocusSeen.has(t));
26953
27730
  let presented;
26954
27731
  let presentedScope = null;
@@ -37209,6 +37986,44 @@ var init_src6 = __esm({
37209
37986
  }
37210
37987
  });
37211
37988
 
37989
+ // ../../core/serve-storage/src/release-patch.ts
37990
+ import { createHash as createHash17 } from "node:crypto";
37991
+ function sha256hex(b) {
37992
+ return createHash17("sha256").update(b).digest("hex");
37993
+ }
37994
+ function stableStringify(value) {
37995
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
37996
+ if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
37997
+ const object = value;
37998
+ return "{" + Object.keys(object).sort().map((key2) => JSON.stringify(key2) + ":" + stableStringify(object[key2])).join(",") + "}";
37999
+ }
38000
+ function manifestContent(m) {
38001
+ const {
38002
+ releaseId: _releaseId,
38003
+ gitSha: _gitSha,
38004
+ shellSha: _shellSha,
38005
+ label: _label,
38006
+ notes: _notes,
38007
+ publishedAt: _publishedAt,
38008
+ publishedBy: _publishedBy,
38009
+ attestation: _attestation,
38010
+ forcedPastErrors: _forcedPastErrors,
38011
+ ...content
38012
+ } = m;
38013
+ return content;
38014
+ }
38015
+ function releaseContentId(m) {
38016
+ const content = manifestContent(m);
38017
+ const widgets = content.widgets;
38018
+ if (widgets && typeof widgets === "object" && !Array.isArray(widgets)) content.widgets = stableWidgets(widgets);
38019
+ return sha256hex(stableStringify(content));
38020
+ }
38021
+ var init_release_patch = __esm({
38022
+ "../../core/serve-storage/src/release-patch.ts"() {
38023
+ init_widget_identity();
38024
+ }
38025
+ });
38026
+
37212
38027
  // ../../core/dev-server/src/net-scenario.ts
37213
38028
  function resolveScenario(rules, req, upstream) {
37214
38029
  const ctx = {
@@ -39922,11 +40737,11 @@ var init_dependencies = __esm({
39922
40737
  });
39923
40738
 
39924
40739
  // ../../core/functions-build/src/sbom.ts
39925
- import { createHash as createHash17 } from "node:crypto";
40740
+ import { createHash as createHash18 } from "node:crypto";
39926
40741
  function createFunctionSbom(packages) {
39927
40742
  const components2 = [...packages].sort((a, b) => a.name.localeCompare(b.name)).map(({ name, version: version2, integrity: integrity2, license }) => ({ name, version: version2, integrity: integrity2, license }));
39928
40743
  const body = JSON.stringify({ format: "himalaya-sbom-v1", components: components2 });
39929
- return { format: "himalaya-sbom-v1", components: components2, hash: createHash17("sha256").update(body).digest("hex") };
40744
+ return { format: "himalaya-sbom-v1", components: components2, hash: createHash18("sha256").update(body).digest("hex") };
39930
40745
  }
39931
40746
  var init_sbom = __esm({
39932
40747
  "../../core/functions-build/src/sbom.ts"() {
@@ -39934,12 +40749,12 @@ var init_sbom = __esm({
39934
40749
  });
39935
40750
 
39936
40751
  // ../../core/functions-build/src/build.ts
39937
- import { createHash as createHash18 } from "node:crypto";
40752
+ import { createHash as createHash19 } from "node:crypto";
39938
40753
  function sha2563(value) {
39939
- return createHash18("sha256").update(value).digest("hex");
40754
+ return createHash19("sha256").update(value).digest("hex");
39940
40755
  }
39941
40756
  function sri(value) {
39942
- return `sha256-${createHash18("sha256").update(value).digest("base64")}`;
40757
+ return `sha256-${createHash19("sha256").update(value).digest("base64")}`;
39943
40758
  }
39944
40759
  function schemaHash(schema) {
39945
40760
  if (!schema) return "";
@@ -40120,6 +40935,7 @@ __export(build_exports2, {
40120
40935
  pinBakedServerBaseUrl: () => pinBakedServerBaseUrl,
40121
40936
  pinnedReleaseFromEnv: () => pinnedReleaseFromEnv,
40122
40937
  publishableFontFaces: () => publishableFontFaces,
40938
+ rebindReleaseId: () => rebindReleaseId,
40123
40939
  releaseIdFor: () => releaseIdFor,
40124
40940
  servicesFromBundleJs: () => servicesFromBundleJs,
40125
40941
  shellSha: () => shellSha,
@@ -40129,7 +40945,7 @@ __export(build_exports2, {
40129
40945
  withStoreNavBundle: () => withStoreNavBundle,
40130
40946
  withoutDevOnlyAppConfig: () => withoutDevOnlyAppConfig
40131
40947
  });
40132
- import { createHash as createHash19 } from "node:crypto";
40948
+ import { createHash as createHash20 } from "node:crypto";
40133
40949
  import { readFileSync as readFileSync42, readdirSync as readdirSync21, existsSync as existsSync39 } from "node:fs";
40134
40950
  import { dirname as dirname23, join as join37, resolve as resolve32 } from "node:path";
40135
40951
  import { fileURLToPath as fileURLToPath19 } from "node:url";
@@ -40199,10 +41015,10 @@ async function bakeCatalogs(app, locale, fetchCatalog, putDoc) {
40199
41015
  return out;
40200
41016
  }
40201
41017
  function sha2564(buf) {
40202
- return createHash19("sha256").update(buf).digest("hex");
41018
+ return createHash20("sha256").update(buf).digest("hex");
40203
41019
  }
40204
41020
  function sri2(buf) {
40205
- return "sha256-" + createHash19("sha256").update(buf).digest("base64");
41021
+ return "sha256-" + createHash20("sha256").update(buf).digest("base64");
40206
41022
  }
40207
41023
  async function widgetFailureDetail(res) {
40208
41024
  try {
@@ -40298,14 +41114,14 @@ function pinBakedServerBaseUrl(json5, base) {
40298
41114
  const boundary = /:\d+$/.test(base) ? "(?!\\d)" : "(?![\\w.-])(?!:\\d)";
40299
41115
  return json5.replace(new RegExp(`${escaped}${boundary}`, "g"), BAKED_SERVER_BASE_URL);
40300
41116
  }
40301
- function stableStringify(v) {
41117
+ function stableStringify2(v) {
40302
41118
  if (v === null || typeof v !== "object") return JSON.stringify(v);
40303
- if (Array.isArray(v)) return "[" + v.map(stableStringify).join(",") + "]";
41119
+ if (Array.isArray(v)) return "[" + v.map(stableStringify2).join(",") + "]";
40304
41120
  const o = v;
40305
- return "{" + Object.keys(o).sort().map((k) => JSON.stringify(k) + ":" + stableStringify(o[k])).join(",") + "}";
41121
+ return "{" + Object.keys(o).sort().map((k) => JSON.stringify(k) + ":" + stableStringify2(o[k])).join(",") + "}";
40306
41122
  }
40307
41123
  function contentReleaseId(content) {
40308
- return sha2564(stableStringify(content));
41124
+ return sha2564(stableStringify2(content));
40309
41125
  }
40310
41126
  function servicesFromBundleJs(bundleJs, input = {}) {
40311
41127
  try {
@@ -40320,7 +41136,13 @@ function hydrationServices(tier5Dir, bundleName, input = {}) {
40320
41136
  return js ? servicesFromBundleJs(js, input) : null;
40321
41137
  }
40322
41138
  function releaseIdFor(sha, content) {
40323
- return sha !== "dev" ? sha : contentReleaseId(content);
41139
+ if (sha === "dev") return contentReleaseId(content);
41140
+ const stored = JSON.parse(JSON.stringify(content));
41141
+ return `${sha}-${releaseContentId(stored).slice(0, 12)}`;
41142
+ }
41143
+ function rebindReleaseId(manifest) {
41144
+ const { releaseId: _releaseId, gitSha: gitSha2, shellSha: _shellSha, ...content } = manifest;
41145
+ return { ...manifest, releaseId: releaseIdFor(gitSha2, content) };
40324
41146
  }
40325
41147
  function shellSha(root = REPO_ROOT7) {
40326
41148
  if (!isMonorepoCheckout(root)) return "";
@@ -41265,6 +42087,7 @@ var init_build3 = __esm({
41265
42087
  init_src6();
41266
42088
  init_check();
41267
42089
  init_file_store();
42090
+ init_release_patch();
41268
42091
  init_validate_descriptor();
41269
42092
  init_wix_member_login();
41270
42093
  init_wix_placeholders();
@@ -41927,7 +42750,7 @@ __export(studio_exports, {
41927
42750
  renderStudioHtml: () => renderStudioHtml,
41928
42751
  writeStudioRecord: () => writeStudioRecord
41929
42752
  });
41930
- import { createHash as createHash20 } from "node:crypto";
42753
+ import { createHash as createHash21 } from "node:crypto";
41931
42754
  import { existsSync as existsSync41, readdirSync as readdirSync22, readFileSync as readFileSync44, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
41932
42755
  import { basename as basename10, extname as extname3, join as join39, relative as relative7, resolve as resolve33, sep as sep9 } from "node:path";
41933
42756
  import { pathToFileURL as pathToFileURL7 } from "node:url";
@@ -42165,7 +42988,7 @@ function buildView(dir, memory, now) {
42165
42988
  reveal: memory.reveal ?? null
42166
42989
  };
42167
42990
  const { generatedAt: _ignored, timelineFrames: _count, ...stable } = view;
42168
- const v = createHash20("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, 16);
42991
+ const v = createHash21("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, 16);
42169
42992
  return { v, ...view };
42170
42993
  }
42171
42994
  function writeAtomic(file, content) {
@@ -42663,7 +43486,7 @@ function patchHonesty(opts) {
42663
43486
  return { attestation, source };
42664
43487
  }
42665
43488
  var SELECTOR_KINDS;
42666
- var init_release_patch = __esm({
43489
+ var init_release_patch2 = __esm({
42667
43490
  "src/release-patch.ts"() {
42668
43491
  init_widget_identity();
42669
43492
  SELECTOR_KINDS = ["changed", "screen:<id|glob>", "bundle:<name>", "tokens", "app-config"];
@@ -45409,7 +46232,7 @@ __export(pack_exports, {
45409
46232
  unpackTo: () => unpackTo,
45410
46233
  writeZip: () => writeZip
45411
46234
  });
45412
- import { createHash as createHash21 } from "node:crypto";
46235
+ import { createHash as createHash22 } from "node:crypto";
45413
46236
  import { deflateRawSync, inflateRawSync } from "node:zlib";
45414
46237
  import { existsSync as existsSync44, mkdirSync as mkdirSync21, readFileSync as readFileSync48, readdirSync as readdirSync24, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
45415
46238
  import { dirname as dirname26, join as join42, resolve as resolve37 } from "node:path";
@@ -45559,11 +46382,11 @@ function gatherReleaseEntries(appDir2) {
45559
46382
  return { entries, manifest, skipped };
45560
46383
  }
45561
46384
  function sha256Hex4(bytes2) {
45562
- return createHash21("sha256").update(bytes2).digest("hex");
46385
+ return createHash22("sha256").update(bytes2).digest("hex");
45563
46386
  }
45564
46387
  function computeContentHash(entries) {
45565
46388
  const input = packageHashInput(entries.map((e) => ({ path: e.path, hash: sha256Hex4(e.bytes) })));
45566
- return "sha256-" + createHash21("sha256").update(input).digest("base64");
46389
+ return "sha256-" + createHash22("sha256").update(input).digest("base64");
45567
46390
  }
45568
46391
  function buildEntries(appDir2, app, opts) {
45569
46392
  const { entries: released, manifest, skipped } = gatherReleaseEntries(appDir2);
@@ -47772,10 +48595,10 @@ var init_calls = __esm({
47772
48595
  });
47773
48596
 
47774
48597
  // ../../core/functions-runtime/src/queue.ts
47775
- import { createHash as createHash22 } from "node:crypto";
48598
+ import { createHash as createHash23 } from "node:crypto";
47776
48599
  function inputSchemaHash(definition) {
47777
48600
  const schema = definition.input;
47778
- return schema ? createHash22("sha256").update(JSON.stringify(schema.descriptor ?? { kind: schema.kind, optional: schema.optional === true })).digest("hex") : "";
48601
+ return schema ? createHash23("sha256").update(JSON.stringify(schema.descriptor ?? { kind: schema.kind, optional: schema.optional === true })).digest("hex") : "";
47779
48602
  }
47780
48603
  function createFunctionEnqueueCapability(options) {
47781
48604
  return {
@@ -47841,14 +48664,14 @@ var init_queue = __esm({
47841
48664
  });
47842
48665
 
47843
48666
  // ../../core/data-resources/src/plan.ts
47844
- import { createHash as createHash23 } from "node:crypto";
48667
+ import { createHash as createHash24 } from "node:crypto";
47845
48668
  function canonical2(value) {
47846
48669
  if (Array.isArray(value)) return `[${value.map(canonical2).join(",")}]`;
47847
48670
  if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonical2(item)}`).join(",")}}`;
47848
48671
  return JSON.stringify(value);
47849
48672
  }
47850
48673
  function hashResourcePlan(plan) {
47851
- return createHash23("sha256").update(canonical2(plan)).digest("hex");
48674
+ return createHash24("sha256").update(canonical2(plan)).digest("hex");
47852
48675
  }
47853
48676
  function finalize(input) {
47854
48677
  return { ...input, hash: hashResourcePlan(input) };
@@ -48784,7 +49607,7 @@ __export(studio_reel_exports, {
48784
49607
  thin: () => thin,
48785
49608
  utcDay: () => utcDay
48786
49609
  });
48787
- import { createHash as createHash24 } from "node:crypto";
49610
+ import { createHash as createHash25 } from "node:crypto";
48788
49611
  import { lstatSync as lstatSync9, readFileSync as readFileSync55 } from "node:fs";
48789
49612
  import { basename as basename12, join as join49 } from "node:path";
48790
49613
  function loadMemory2(dir) {
@@ -48872,7 +49695,7 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
48872
49695
  const st = lstatSync9(path2);
48873
49696
  if (!st.isFile() || st.nlink !== 1) return null;
48874
49697
  const bytes2 = readFileSync55(path2);
48875
- return createHash24("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
49698
+ return createHash25("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
48876
49699
  } catch {
48877
49700
  return null;
48878
49701
  }
@@ -49292,7 +50115,7 @@ __export(browser_authoring_exports, {
49292
50115
  sdkSubpathReader: () => sdkSubpathReader
49293
50116
  });
49294
50117
  import { createServer as createServer3 } from "node:http";
49295
- import { createHash as createHash25, randomBytes as randomBytes4 } from "node:crypto";
50118
+ import { createHash as createHash26, randomBytes as randomBytes4 } from "node:crypto";
49296
50119
  import { existsSync as existsSync52, readdirSync as readdirSync26, readFileSync as readFileSync57, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
49297
50120
  import { join as join51, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
49298
50121
  function humanMs(ms) {
@@ -49391,7 +50214,7 @@ function encodeJob(job) {
49391
50214
  return Buffer.from(JSON.stringify(job), "utf8").toString("base64");
49392
50215
  }
49393
50216
  function mintToken2(payload, nonce = randomBytes4(16).toString("hex")) {
49394
- const digest2 = createHash25("sha256").update(payload, "utf8").digest("base64url");
50217
+ const digest2 = createHash26("sha256").update(payload, "utf8").digest("base64url");
49395
50218
  return `${nonce}.${digest2}`;
49396
50219
  }
49397
50220
  function defaultOpenBrowser2(url) {
@@ -50314,7 +51137,7 @@ function makeServeHandler(opts) {
50314
51137
  }
50315
51138
  const PINNED_MANIFEST_CACHE_CAP = 100;
50316
51139
  const pinnedManifestCache = /* @__PURE__ */ new Map();
50317
- const sha256hex = (b) => sha256Hex(b);
51140
+ const sha256hex2 = (b) => sha256Hex(b);
50318
51141
  function resolvePin(request, url) {
50319
51142
  return request.headers.get("x-himalaya-release") || url.searchParams.get("release") || null;
50320
51143
  }
@@ -50367,7 +51190,7 @@ ${releaseId}`;
50367
51190
  const doc = store.getDoc ? await store.getDoc(app, hash) : null;
50368
51191
  if (doc) return doc;
50369
51192
  const bytes2 = await slot();
50370
- return bytes2 && sha256hex(bytes2) === hash ? bytes2 : null;
51193
+ return bytes2 && sha256hex2(bytes2) === hash ? bytes2 : null;
50371
51194
  }
50372
51195
  function callbackUrlFor(url) {
50373
51196
  const base = ((typeof opts.publicBaseUrl === "function" ? opts.publicBaseUrl(url) : opts.publicBaseUrl) ?? url.origin).replace(/\/$/, "");
@@ -51031,7 +51854,7 @@ ${releaseId}`;
51031
51854
  }
51032
51855
  }
51033
51856
  }
51034
- const cfgTag = ringPinned ? sha256hex(JSON.stringify(appConfig)) : mm.appConfigHash ?? "";
51857
+ const cfgTag = ringPinned ? sha256hex2(JSON.stringify(appConfig)) : mm.appConfigHash ?? "";
51035
51858
  const etag = strongEtag(`${mm.releaseId}:${cfgTag}:${mm.tokensHash ?? ""}:${screenHash}:${homeBundle?.sha256 ?? ""}`);
51036
51859
  const cache = pin && !ringPinned ? PINNED_CACHE_CONTROL : DOC_CACHE_CONTROL;
51037
51860
  const vary = pin ? { vary: ringPinned ? `x-himalaya-release, ${RELEASE_SOURCE_HEADER}` : "x-himalaya-release" } : {};
@@ -51634,21 +52457,21 @@ var init_preview_assets_generated = __esm({
51634
52457
  "../../packages/serve/src/preview-assets.generated.ts"() {
51635
52458
  init_preview_assets_format();
51636
52459
  PREVIEW_ASSET_MANIFEST = [
51637
- { key: "assets/index-Bh3PhhIv.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1056814, sha256: "ca4ec59601c2b573ed7e8bf193c05412a2c1eb385afc34a5fc0fc3272e0ab4b8" },
51638
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1056822, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
51639
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1061432, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
51640
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1550016, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
51641
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1640940, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
51642
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1731924, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
51643
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1822904, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
51644
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1913776, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
51645
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2004756, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
51646
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2113392, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
51647
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2222476, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
51648
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2331524, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
51649
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2439224, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
51650
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2527332, length: 4947, sha256: "f2505c929e9df4474bcfb0e0ea3d52ede073bf70f3a87d5c8a358b41599fda69" },
51651
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2532279, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
52460
+ { key: "assets/index-BpZ7D4Bh.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1058524, sha256: "cc7f9edeb0a351f0d1fbc18d622044afaf3112c567893f04135a5cf846e2c4d2" },
52461
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1058532, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
52462
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1063142, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
52463
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1551726, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
52464
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1642650, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
52465
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1733634, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
52466
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1824614, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
52467
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1915486, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
52468
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2006466, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
52469
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2115102, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
52470
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2224186, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
52471
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2333234, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
52472
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2440934, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
52473
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2529042, length: 4947, sha256: "bed413299e95d197f054aae695914a18403bef8f893a8ed89a87068cbd0cab80" },
52474
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2533989, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
51652
52475
  ];
51653
52476
  }
51654
52477
  });
@@ -52778,7 +53601,7 @@ function loadStoreConfig(app) {
52778
53601
  }
52779
53602
  var init_preflight = __esm({
52780
53603
  "../store-release/preflight.ts"() {
52781
- init_paths();
53604
+ init_paths2();
52782
53605
  }
52783
53606
  });
52784
53607
 
@@ -52918,7 +53741,7 @@ __export(preflight_app_exports, {
52918
53741
  profileAccount: () => profileAccount
52919
53742
  });
52920
53743
  import { readFileSync as readFileSync66, existsSync as existsSync60, readdirSync as readdirSync28, statSync as statSync15 } from "node:fs";
52921
- import { createHash as createHash26 } from "node:crypto";
53744
+ import { createHash as createHash27 } from "node:crypto";
52922
53745
  import { join as join57 } from "node:path";
52923
53746
  function analyze(projectYml, storeConfig, icon, account) {
52924
53747
  const f = [];
@@ -53207,7 +54030,7 @@ function placeholderHashes2() {
53207
54030
  const p = join57(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
53208
54031
  try {
53209
54032
  if (!statSync15(p).isFile()) continue;
53210
- return [LEGACY_PLACEHOLDER_SHA2562, createHash26("sha256").update(readFileSync66(p)).digest("hex")];
54033
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync66(p)).digest("hex")];
53211
54034
  } catch {
53212
54035
  }
53213
54036
  }
@@ -53244,7 +54067,7 @@ async function appIconState(iosDir) {
53244
54067
  try {
53245
54068
  if (!statSync15(p).isFile()) continue;
53246
54069
  const bytes2 = readFileSync66(p);
53247
- const hash = createHash26("sha256").update(bytes2).digest("hex");
54070
+ const hash = createHash27("sha256").update(bytes2).digest("hex");
53248
54071
  const pixels = await pixelIssues(bytes2);
53249
54072
  return {
53250
54073
  exists: true,
@@ -53279,7 +54102,7 @@ ${app}: ${errors.length} error(s), ${warns.length} warning(s).`);
53279
54102
  var APP_STORE_SIZES, PNG_COLOR_TYPE_RGB, PLAY_FEATURE_GRAPHIC, LEGACY_PLACEHOLDER_SHA2562;
53280
54103
  var init_preflight_app = __esm({
53281
54104
  "../store-release/preflight-app.ts"() {
53282
- init_paths();
54105
+ init_paths2();
53283
54106
  init_preflight();
53284
54107
  init_credential_profile();
53285
54108
  init_app_icon_rules();
@@ -53386,7 +54209,7 @@ function bumpVersion(app, opts) {
53386
54209
  }
53387
54210
  var init_version = __esm({
53388
54211
  "../store-release/version.ts"() {
53389
- init_paths();
54212
+ init_paths2();
53390
54213
  }
53391
54214
  });
53392
54215
 
@@ -53445,7 +54268,7 @@ async function main2() {
53445
54268
  var ASC_API;
53446
54269
  var init_register_bundle = __esm({
53447
54270
  "../store-release/register-bundle.ts"() {
53448
- init_paths();
54271
+ init_paths2();
53449
54272
  init_preflight();
53450
54273
  ASC_API = "https://api.appstoreconnect.apple.com";
53451
54274
  if (runningAsScript("store-release/register-bundle.ts")) {
@@ -53510,7 +54333,7 @@ async function main3() {
53510
54333
  var ASC;
53511
54334
  var init_asc_status = __esm({
53512
54335
  "../store-release/asc-status.ts"() {
53513
- init_paths();
54336
+ init_paths2();
53514
54337
  init_register_bundle();
53515
54338
  init_preflight();
53516
54339
  ASC = "https://api.appstoreconnect.apple.com";
@@ -63450,7 +64273,7 @@ var init_context = __esm({
63450
64273
  // src/cli.ts
63451
64274
  import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync75, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync63, rmSync as rmSync14, readdirSync as readdirSync29, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
63452
64275
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
63453
- import { createHash as createHash27, randomUUID as randomUUID5 } from "node:crypto";
64276
+ import { createHash as createHash28, randomUUID as randomUUID5 } from "node:crypto";
63454
64277
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
63455
64278
  import { join as join61, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
63456
64279
  import { fileURLToPath as fileURLToPath24 } from "node:url";
@@ -64050,743 +64873,7 @@ init_store();
64050
64873
  init_contract();
64051
64874
  init_app_link();
64052
64875
  init_himi_report();
64053
-
64054
- // src/attestation.ts
64055
- import { execFileSync as execFileSync2 } from "node:child_process";
64056
- import { createHash as createHash3, randomBytes } from "node:crypto";
64057
- import {
64058
- existsSync as existsSync3,
64059
- lstatSync as lstatSync3,
64060
- mkdirSync as mkdirSync5,
64061
- realpathSync,
64062
- readlinkSync as readlinkSync2,
64063
- readFileSync as readFileSync5,
64064
- readdirSync as readdirSync3,
64065
- unlinkSync,
64066
- writeFileSync as writeFileSync5
64067
- } from "node:fs";
64068
- import { basename as basename2, join as join6, relative, resolve as resolve6 } from "node:path";
64069
-
64070
- // ../verify-gate/diff-hash.mjs
64071
- import {
64072
- lstatSync as lstatSync2,
64073
- mkdirSync as mkdirSync4,
64074
- mkdtempSync,
64075
- readFileSync as readFileSync4,
64076
- readlinkSync,
64077
- rmSync as rmSync2,
64078
- writeFileSync as writeFileSync4
64079
- } from "node:fs";
64080
- import { createHash as createHash2 } from "node:crypto";
64081
- import { tmpdir } from "node:os";
64082
- import { basename, dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
64083
- import { pathToFileURL } from "node:url";
64084
-
64085
- // ../pre-pr-review/scope.mjs
64086
- import { execFileSync } from "node:child_process";
64087
- import { createHash } from "node:crypto";
64088
- import { resolve as resolve4, dirname as dirname4, isAbsolute, join as join4 } from "node:path";
64089
- import { fileURLToPath as fileURLToPath3 } from "node:url";
64090
-
64091
- // ../ai-review-gate/paths.mjs
64092
- import { resolve as resolve3, dirname as dirname3 } from "node:path";
64093
- import { fileURLToPath as fileURLToPath2 } from "node:url";
64094
- var REPO_ROOT = resolve3(dirname3(fileURLToPath2(import.meta.url)), "../..");
64095
-
64096
- // ../pre-pr-review/command.mjs
64097
- var SHELL_NAMES = "sh|bash|zsh|ksh|dash|ash|fish|csh|tcsh|pwsh|xonsh|nu";
64098
- var SHELL_TOKEN = new RegExp(`^(?:.*/)?(?:${SHELL_NAMES})$`);
64099
-
64100
- // ../pre-pr-review/scope.mjs
64101
- var REPO_ROOT2 = resolve4(dirname4(fileURLToPath3(import.meta.url)), "../..");
64102
- var BASE_REF = process.env.PRE_PR_REVIEW_BASE || "origin/master";
64103
- function resolveRepoRoot(cwd = process.cwd()) {
64104
- let top;
64105
- try {
64106
- top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
64107
- cwd,
64108
- encoding: "utf8",
64109
- stdio: ["ignore", "pipe", "pipe"]
64110
- }).toString().trim();
64111
- } catch (err) {
64112
- throw new Error(`cannot resolve a repo root from ${cwd}: ${err?.message || err}`);
64113
- }
64114
- if (!top) throw new Error(`cannot resolve a repo root from ${cwd}: git named no toplevel`);
64115
- return top;
64116
- }
64117
- function git(args, opts = {}) {
64118
- return execFileSync("git", args, {
64119
- cwd: opts.cwd || REPO_ROOT2,
64120
- encoding: "utf8",
64121
- // A full PR diff can be megabytes; the default 1MB cap would truncate mid-hunk and the
64122
- // delta parser would then read a malformed diff as "unsupported shape".
64123
- maxBuffer: opts.maxBuffer ?? 256 * 1024 * 1024,
64124
- stdio: ["ignore", "pipe", "pipe"]
64125
- }).toString();
64126
- }
64127
- function diffHash(diff) {
64128
- return createHash("sha256").update(diff).digest("hex");
64129
- }
64130
-
64131
- // ../verify-gate/diff-hash.mjs
64132
- var VALIDATION_METADATA = /* @__PURE__ */ new Set([
64133
- ".claude/.validation-baseline.json",
64134
- ".claude/.validation-evidence.json"
64135
- ]);
64136
- function isValidationMetadata(path2) {
64137
- return VALIDATION_METADATA.has(path2) || /(^|[\\/])\.himi([\\/]|$)/.test(path2);
64138
- }
64139
- function tryGit(args, root) {
64140
- try {
64141
- return git(args, { cwd: root });
64142
- } catch {
64143
- return null;
64144
- }
64145
- }
64146
- function committedRecords(raw) {
64147
- const fields = raw.split("\0");
64148
- if (fields.length > 0 && fields[fields.length - 1] === "") fields.pop();
64149
- if (fields.length % 2 !== 0) return null;
64150
- const records = [];
64151
- for (let i = 0; i < fields.length; i += 2) {
64152
- const metadata2 = fields[i];
64153
- const path2 = fields[i + 1];
64154
- if (!metadata2.startsWith(":") || path2 === "") return null;
64155
- if (isValidationMetadata(path2)) continue;
64156
- records.push(`${Buffer.byteLength(path2)}:${path2}\0${metadata2}\0`);
64157
- }
64158
- return records.sort().join("");
64159
- }
64160
- function committedIdentity(root) {
64161
- const head = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root)?.trim();
64162
- if (!head) return "unborn\0";
64163
- const base = tryGit(["merge-base", BASE_REF, "HEAD"], root)?.trim();
64164
- if (base) {
64165
- const raw = tryGit(["diff", "--raw", "--no-renames", "-z", "--no-abbrev", base, "HEAD"], root);
64166
- const records = raw === null ? null : committedRecords(raw);
64167
- if (records !== null) return `base\0${base}\0${records}`;
64168
- }
64169
- const listing = tryGit(["ls-tree", "-rz", "--full-tree", "HEAD"], root);
64170
- const tree = listing === null ? null : treeRecords(listing);
64171
- return tree !== null ? `tree-listing\0${tree}` : `commit\0${head}\0`;
64172
- }
64173
- function treeRecords(listing) {
64174
- const records = [];
64175
- for (const record2 of listing.split("\0")) {
64176
- if (record2 === "") continue;
64177
- const tab = record2.indexOf(" ");
64178
- if (tab <= 0 || tab === record2.length - 1) return null;
64179
- const path2 = record2.slice(tab + 1);
64180
- if (isValidationMetadata(path2)) continue;
64181
- records.push(`${Buffer.byteLength(path2)}:${path2}\0${record2.slice(0, tab)}\0`);
64182
- }
64183
- return records.sort().join("");
64184
- }
64185
- function treeEntries(output, index = false) {
64186
- const entries = /* @__PURE__ */ new Map();
64187
- for (const record2 of output.split("\0").filter(Boolean)) {
64188
- const tab = record2.indexOf(" ");
64189
- if (tab === -1) continue;
64190
- const metadata2 = record2.slice(0, tab).split(" ");
64191
- if (index && metadata2[2] !== "0") continue;
64192
- entries.set(record2.slice(tab + 1), {
64193
- mode: metadata2[0],
64194
- oid: index ? metadata2[1] : metadata2[2]
64195
- });
64196
- }
64197
- return entries;
64198
- }
64199
- function sameEntry(left, right) {
64200
- if (!left || !right) return left === right;
64201
- return left.mode === right.mode && left.oid === right.oid;
64202
- }
64203
- function stagedIdentity(head, index, worktree) {
64204
- if (sameEntry(index, head) || sameEntry(index, worktree)) return "";
64205
- if (!index) return head ? "deleted" : "";
64206
- return `${index.mode}:${index.oid}`;
64207
- }
64208
- function blobOid(contents, algorithm) {
64209
- const body = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
64210
- return createHash2(algorithm).update(Buffer.from(`blob ${body.byteLength}\0`)).update(body).digest("hex");
64211
- }
64212
- function validationDiffHash(cwd = process.cwd()) {
64213
- const root = resolveRepoRoot(cwd);
64214
- const born = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root) !== null;
64215
- const staged = git(["diff", "--cached", "--name-only", "--no-renames", "-z", ...born ? ["HEAD"] : []], {
64216
- cwd: root
64217
- }).split("\0").filter(Boolean);
64218
- const unstaged = git(["diff", "--name-only", "--no-renames", "-z"], { cwd: root }).split("\0").filter(Boolean);
64219
- const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], {
64220
- cwd: root
64221
- }).split("\0").filter(Boolean);
64222
- const headEntries = born ? treeEntries(git(["ls-tree", "-rz", "--full-tree", "HEAD"], { cwd: root })) : /* @__PURE__ */ new Map();
64223
- const indexEntries = treeEntries(git(["ls-files", "--stage", "-z"], { cwd: root }), true);
64224
- const objectAlgorithm = git(["rev-parse", "--show-object-format"], { cwd: root }).trim();
64225
- let trustFilesystemMode = true;
64226
- try {
64227
- trustFilesystemMode = git(["config", "--bool", "--get", "core.fileMode"], { cwd: root }).trim() !== "false";
64228
- } catch {
64229
- }
64230
- const paths = [.../* @__PURE__ */ new Set([...staged, ...unstaged, ...untracked])].filter((path2) => !isValidationMetadata(path2)).sort();
64231
- const identity = paths.map((path2) => {
64232
- const absolute = resolve5(root, path2);
64233
- const headEntry = headEntries.get(path2);
64234
- const indexEntry = indexEntries.get(path2);
64235
- const deletedRecord = () => [`${Buffer.byteLength(path2)}:${path2}`, "deleted", stagedIdentity(headEntry, indexEntry, void 0), ""].join("\0");
64236
- let stat2;
64237
- try {
64238
- stat2 = lstatSync2(absolute);
64239
- } catch (error) {
64240
- if (error?.code === "ENOENT") return deletedRecord();
64241
- throw error;
64242
- }
64243
- try {
64244
- if (stat2.isSymbolicLink()) {
64245
- const target = readlinkSync(absolute);
64246
- const worktreeEntry = { mode: "120000", oid: blobOid(target, objectAlgorithm) };
64247
- const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
64248
- return [
64249
- `${Buffer.byteLength(path2)}:${path2}`,
64250
- "symlink",
64251
- "120000",
64252
- diffHash(target),
64253
- stagedState2,
64254
- ""
64255
- ].join("\0");
64256
- }
64257
- if (stat2.isFile()) {
64258
- const filesystemMode = stat2.mode & 73 ? "100755" : "100644";
64259
- const worktreeMode = trustFilesystemMode ? filesystemMode : indexEntry?.mode ?? headEntry?.mode ?? "100644";
64260
- const worktreeOid = git(["hash-object", `--path=${path2}`, "--", path2], { cwd: root }).trim();
64261
- const worktreeEntry = { mode: worktreeMode, oid: worktreeOid };
64262
- const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
64263
- return [
64264
- `${Buffer.byteLength(path2)}:${path2}`,
64265
- "file",
64266
- worktreeMode,
64267
- diffHash(readFileSync4(absolute)),
64268
- stagedState2,
64269
- ""
64270
- ].join("\0");
64271
- }
64272
- } catch (error) {
64273
- try {
64274
- lstatSync2(absolute);
64275
- } catch (recheck) {
64276
- if (recheck?.code === "ENOENT") return deletedRecord();
64277
- }
64278
- throw error;
64279
- }
64280
- const type = stat2.isDirectory() ? "directory" : "other";
64281
- const stagedState = stagedIdentity(headEntry, indexEntry, void 0);
64282
- return [
64283
- `${Buffer.byteLength(path2)}:${path2}`,
64284
- type,
64285
- stat2.mode.toString(8),
64286
- stagedState,
64287
- ""
64288
- ].join("\0");
64289
- }).join("");
64290
- const committed = committedIdentity(root);
64291
- return diffHash(
64292
- `validation-change-v3\0${Buffer.byteLength(committed)}\0${committed}${identity}`
64293
- );
64294
- }
64295
- function seed(repo, commands) {
64296
- for (const args of commands) git(args, { cwd: repo });
64297
- }
64298
- function write(repo, rel, body) {
64299
- const absolute = resolve5(repo, rel);
64300
- mkdirSync4(dirname5(absolute), { recursive: true });
64301
- writeFileSync4(absolute, body);
64302
- }
64303
- function selfTestRepo({ withBase }) {
64304
- const repo = mkdtempSync(join5(tmpdir(), "diff-hash-self-test-"));
64305
- seed(repo, [
64306
- ["init", "-q", repo],
64307
- ["-C", repo, "config", "user.email", "self-test@test.invalid"],
64308
- ["-C", repo, "config", "user.name", "diff-hash-self-test"]
64309
- ]);
64310
- write(repo, "src/base.txt", "base\n");
64311
- seed(repo, [["-C", repo, "add", "-A"], ["-C", repo, "commit", "-qm", "base"]]);
64312
- const base = git(["-C", repo, "rev-parse", "HEAD"], { cwd: repo }).trim();
64313
- if (withBase) seed(repo, [["-C", repo, "update-ref", "refs/remotes/origin/master", base]]);
64314
- return { repo, base };
64315
- }
64316
- function commitChange(repo, base, branch, rel, body) {
64317
- seed(repo, [["-C", repo, "checkout", "-q", base], ["-C", repo, "checkout", "-q", "-b", branch]]);
64318
- write(repo, rel, body);
64319
- seed(repo, [["-C", repo, "add", rel], ["-C", repo, "commit", "-qm", `commit ${rel}`]]);
64320
- return validationDiffHash(repo);
64321
- }
64322
- var SELF_TEST_CASES = [
64323
- {
64324
- name: "committed diffs are distinguished (the #1831 regression)",
64325
- reason: "two unrelated COMMITTED diffs share one identity \u2014 a record for either would satisfy the gate for the other",
64326
- run: () => {
64327
- const { repo, base } = selfTestRepo({ withBase: true });
64328
- try {
64329
- return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
64330
- } finally {
64331
- rmSync2(repo, { recursive: true, force: true });
64332
- }
64333
- }
64334
- },
64335
- {
64336
- name: "committed diffs are distinguished with NO base ref (fallback ladder)",
64337
- reason: "without origin/master the committed identity collapses to a constant",
64338
- run: () => {
64339
- const { repo, base } = selfTestRepo({ withBase: false });
64340
- try {
64341
- return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
64342
- } finally {
64343
- rmSync2(repo, { recursive: true, force: true });
64344
- }
64345
- }
64346
- },
64347
- {
64348
- name: "a clean tree is not the digest of an empty path list",
64349
- reason: 'a clean tree still hashes to sha256("validation-worktree-v2\\0"), the constant measured in #1831',
64350
- run: () => {
64351
- const { repo } = selfTestRepo({ withBase: true });
64352
- try {
64353
- return validationDiffHash(repo) !== createHash2("sha256").update("validation-worktree-v2\0").digest("hex");
64354
- } finally {
64355
- rmSync2(repo, { recursive: true, force: true });
64356
- }
64357
- }
64358
- },
64359
- {
64360
- name: "an uncommitted edit still changes the identity",
64361
- reason: "the working-tree half of the identity stopped responding to edits",
64362
- run: () => {
64363
- const { repo } = selfTestRepo({ withBase: true });
64364
- try {
64365
- const before = validationDiffHash(repo);
64366
- write(repo, "src/base.txt", "edited in the working tree\n");
64367
- return validationDiffHash(repo) !== before;
64368
- } finally {
64369
- rmSync2(repo, { recursive: true, force: true });
64370
- }
64371
- }
64372
- },
64373
- {
64374
- // The "can pass" half. Without it every case above is satisfied by a hash that simply
64375
- // changes every call, which would identify nothing and invalidate every record.
64376
- name: "one unchanged state hashes to one value",
64377
- reason: "the identity is not stable for an unchanged state \u2014 every record would read stale",
64378
- run: () => {
64379
- const { repo } = selfTestRepo({ withBase: true });
64380
- try {
64381
- write(repo, "src/base.txt", "edited in the working tree\n");
64382
- return validationDiffHash(repo) === validationDiffHash(repo);
64383
- } finally {
64384
- rmSync2(repo, { recursive: true, force: true });
64385
- }
64386
- }
64387
- }
64388
- ];
64389
- function runSelfTest() {
64390
- let failed2 = 0;
64391
- for (const testCase of SELF_TEST_CASES) {
64392
- let ok = false;
64393
- let thrown = null;
64394
- try {
64395
- ok = testCase.run() === true;
64396
- } catch (error) {
64397
- thrown = error?.message || String(error);
64398
- }
64399
- if (ok) {
64400
- process.stdout.write(` \u2713 ${testCase.name}
64401
- `);
64402
- continue;
64403
- }
64404
- failed2++;
64405
- process.stderr.write(` \u2717 ${testCase.name}
64406
- ${thrown ?? testCase.reason}
64407
- `);
64408
- }
64409
- return failed2;
64410
- }
64411
- if (process.argv[1] && basename(process.argv[1]) === "diff-hash.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) {
64412
- if (process.argv.slice(2).includes("--self-test")) {
64413
- process.stdout.write("\u25B8 diff-hash --self-test: proving the identity can change, and can hold\n");
64414
- const failed2 = runSelfTest();
64415
- if (failed2) {
64416
- process.stderr.write(
64417
- `\u2717 diff-hash self-test FAILED (${failed2} case(s)) \u2014 the evidence record's diff identity cannot be trusted
64418
- `
64419
- );
64420
- process.exit(1);
64421
- }
64422
- process.stdout.write(`\u2713 diff-hash self-test passed (${SELF_TEST_CASES.length} cases)
64423
- `);
64424
- } else {
64425
- process.stdout.write(`${validationDiffHash()}
64426
- `);
64427
- }
64428
- }
64429
-
64430
- // src/attestation.ts
64431
- init_source_deny();
64432
- init_widget_identity();
64433
- var DEFAULT_MAX_RUNS = 20;
64434
- var ATTESTATION_VERSION = 2;
64435
- function attestationVersionOf(value) {
64436
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
64437
- const version2 = value.attestation;
64438
- return typeof version2 === "number" && Number.isInteger(version2) && version2 > 0 ? version2 : null;
64439
- }
64440
- function classifyRetainedRun(dir, name) {
64441
- let value;
64442
- try {
64443
- value = JSON.parse(readFileSync5(join6(dir, name), "utf8"));
64444
- } catch {
64445
- return { kind: "unreadable" };
64446
- }
64447
- if (isAttestationV2(value)) return { kind: "current", run: value };
64448
- const version2 = attestationVersionOf(value);
64449
- if (version2 === null || version2 === ATTESTATION_VERSION) return { kind: "malformed" };
64450
- return { kind: version2 < ATTESTATION_VERSION ? "legacy" : "future" };
64451
- }
64452
- function screenshotArtifactPath(contentDir2, hash) {
64453
- return join6(resolve6(contentDir2), ".himi", "attestations", "artifacts", `${hash}.png`);
64454
- }
64455
- var GIT_HASH_MAX_BUFFER = 128 * 1024 * 1024;
64456
- var sha2562 = (bytes2) => createHash3("sha256").update(bytes2).digest("hex");
64457
- function canonical(value) {
64458
- if (value === null || typeof value !== "object") return JSON.stringify(value);
64459
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
64460
- const record2 = value;
64461
- return `{${Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${canonical(record2[key2])}`).join(",")}}`;
64462
- }
64463
- function releasePayloadHash(payload) {
64464
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return sha2562(canonical(payload));
64465
- const record2 = payload;
64466
- const manifest = record2.manifest;
64467
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return sha2562(canonical(payload));
64468
- const manifestRecord = manifest;
64469
- const widgets = manifestRecord.widgets;
64470
- if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return sha2562(canonical(payload));
64471
- return sha2562(canonical({ ...record2, manifest: { ...manifestRecord, widgets: stableWidgets(widgets) } }));
64472
- }
64473
- function withoutSelfHash(attestation) {
64474
- const { selfHash: _selfHash, ...body } = attestation;
64475
- return body;
64476
- }
64477
- function selfHashFor(attestation) {
64478
- return sha2562(canonical(withoutSelfHash(attestation)));
64479
- }
64480
- function repoRoot(contentDir2) {
64481
- try {
64482
- const root = execFileSync2("git", ["-C", contentDir2, "rev-parse", "--show-toplevel"], {
64483
- encoding: "utf8",
64484
- stdio: ["ignore", "pipe", "ignore"]
64485
- }).trim();
64486
- if (!root) return null;
64487
- execFileSync2("git", ["-C", root, "rev-parse", "--verify", "--quiet", "HEAD^{commit}"], { stdio: "ignore" });
64488
- return root;
64489
- } catch {
64490
- return null;
64491
- }
64492
- }
64493
- function storedContentDir(contentDir2) {
64494
- const absolute = resolve6(contentDir2);
64495
- const root = repoRoot(absolute);
64496
- return root ? relative(root, absolute) || "." : ".";
64497
- }
64498
- function currentGitDiffHash(contentDir2) {
64499
- const root = repoRoot(resolve6(contentDir2));
64500
- if (!root) return "unavailable";
64501
- return validationDiffHash(root);
64502
- }
64503
- function currentGitUntrackedHash(contentDir2) {
64504
- const unresolved = resolve6(contentDir2);
64505
- const absolute = realpathSync.native(unresolved);
64506
- const root = repoRoot(absolute);
64507
- if (!root) return sourceTreeHash(absolute);
64508
- const scope = relative(root, absolute) || ".";
64509
- const listed = execFileSync2("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z", "--", scope], {
64510
- encoding: "utf8",
64511
- maxBuffer: GIT_HASH_MAX_BUFFER
64512
- });
64513
- const hash = createHash3("sha256");
64514
- for (const path2 of listed.split("\0").filter(Boolean).sort()) {
64515
- const local = relative(absolute, join6(root, path2));
64516
- const segs = local.split(/[\\/]/);
64517
- if (NOT_ATTESTED_DIRS.has(segs[0])) continue;
64518
- if (segs.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(segs[0])) continue;
64519
- const full = join6(root, path2);
64520
- const stats = lstatSync3(full, { throwIfNoEntry: false });
64521
- if (!stats) continue;
64522
- const kind = stats.isSymbolicLink() ? "link" : stats.isDirectory() ? "dir" : stats.isFile() ? "file" : null;
64523
- if (!kind) continue;
64524
- const payload = kind === "link" ? Buffer.from(readlinkSync2(full)) : kind === "file" ? readFileSync5(full) : Buffer.alloc(0);
64525
- const pathBytes = Buffer.from(path2);
64526
- hash.update(`${kind} ${pathBytes.length} ${payload.length}
64527
- `).update(pathBytes).update(payload);
64528
- }
64529
- return hash.digest("hex");
64530
- }
64531
- var NOT_ATTESTED_DIRS = /* @__PURE__ */ new Set([".himi", ...AGENT_TOOLING_DIRS]);
64532
- function sourceTreeHash(root) {
64533
- const hash = createHash3("sha256");
64534
- const visit = (dir) => {
64535
- for (const entry of readdirSync3(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
64536
- if (entry.isDirectory() && (entry.name === ".git" || entry.name === "node_modules" || NOT_ATTESTED_DIRS.has(entry.name))) continue;
64537
- const path2 = join6(dir, entry.name);
64538
- const local = relative(root, path2);
64539
- if (dir === root && entry.isFile() && AGENT_TOOLING_ROOT_FILES.includes(entry.name)) continue;
64540
- if (entry.isDirectory()) visit(path2);
64541
- else if (entry.isFile()) hash.update(local).update("\0").update(readFileSync5(path2)).update("\0");
64542
- else if (entry.isSymbolicLink()) hash.update(local).update("\0link\0").update(readlinkSync2(path2)).update("\0");
64543
- }
64544
- };
64545
- visit(root);
64546
- return hash.digest("hex");
64547
- }
64548
- var stringRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
64549
- function isAttestationV2(value) {
64550
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
64551
- const a = value;
64552
- const coverage = a.coverage;
64553
- const report2 = a.report;
64554
- return a.attestation === 2 && typeof a.runId === "string" && ["himi-test", "himi-render", "himi-shots"].includes(a.tool) && [a.app, a.contentDir, a.gitDiffHash, a.gitUntrackedHash, a.releaseId, a.releaseHash, a.selfHash].every((item) => typeof item === "string" && item.length > 0) && stringRecord(a.descriptorHashes) && stringRecord(a.screenshots) && !!coverage && [coverage.screens, coverage.actionsDispatched, coverage.uncoveredFns, coverage.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && typeof report2?.ok === "boolean" && !!report2.counts && [report2.counts.errors, report2.counts.warnings, report2.counts.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && (a.prev === null || typeof a.prev === "string");
64555
- }
64556
- function readLatestAttestation(contentDir2) {
64557
- try {
64558
- const parsed = JSON.parse(readFileSync5(join6(resolve6(contentDir2), ".himi", "attestations", "latest.json"), "utf8"));
64559
- return isAttestationV2(parsed) ? parsed : null;
64560
- } catch {
64561
- return null;
64562
- }
64563
- }
64564
- function readRetainedAttestations(contentDir2) {
64565
- const dir = join6(resolve6(contentDir2), ".himi", "attestations");
64566
- if (!existsSync3(dir)) return { runs: [], errors: [] };
64567
- try {
64568
- const names = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
64569
- const parsedRuns = [];
64570
- const errors = [];
64571
- for (const name of names) {
64572
- const classified = classifyRetainedRun(dir, name);
64573
- if (classified.kind === "current") parsedRuns.push(classified.run);
64574
- else if (classified.kind === "legacy") {
64575
- errors.push(`attestation history contains retained run ${name} from an older attestation version \u2014 re-run himi test/render to start a v${ATTESTATION_VERSION} chain`);
64576
- } else if (classified.kind === "future") {
64577
- errors.push(`attestation history contains retained run ${name} from a newer attestation version \u2014 upgrade himi rather than deleting it`);
64578
- } else if (classified.kind === "unreadable") {
64579
- errors.push(`attestation history contains unreadable retained run ${name}`);
64580
- } else errors.push(`attestation history contains malformed retained run ${name}`);
64581
- }
64582
- const latest = readLatestAttestation(contentDir2);
64583
- if (existsSync3(join6(dir, "latest.json")) && !latest) errors.push("attestation history has no valid latest.json");
64584
- else if (names.length > 0 && !latest) errors.push("attestation history has no latest.json");
64585
- const byHash = new Map(parsedRuns.map((run2) => [run2.selfHash, run2]));
64586
- if (byHash.size !== parsedRuns.length) errors.push("attestation history contains duplicate self hashes");
64587
- for (const run2 of parsedRuns) {
64588
- if (selfHashFor(run2) !== run2.selfHash) errors.push(`selfHash does not match retained run ${run2.runId}`);
64589
- for (const expected of Object.values(run2.screenshots)) {
64590
- const path2 = screenshotArtifactPath(contentDir2, expected);
64591
- if (!existsSync3(path2)) errors.push(`missing screenshot for retained run ${run2.runId}: ${basename2(path2)}`);
64592
- else if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match retained run ${run2.runId}: ${basename2(path2)}`);
64593
- }
64594
- }
64595
- const runs = [];
64596
- const seen = /* @__PURE__ */ new Set();
64597
- let cursor = latest ?? void 0;
64598
- while (cursor && !seen.has(cursor.selfHash)) {
64599
- runs.push(cursor);
64600
- seen.add(cursor.selfHash);
64601
- cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
64602
- }
64603
- if (latest && !byHash.has(latest.selfHash)) errors.push("latest.json has no matching retained run");
64604
- if (seen.size !== parsedRuns.length) errors.push("attestation hash chain does not reach every retained run");
64605
- runs.push(...parsedRuns.filter((run2) => !seen.has(run2.selfHash)).sort((a, b) => a.runId < b.runId ? 1 : -1));
64606
- return {
64607
- runs,
64608
- errors
64609
- };
64610
- } catch {
64611
- return { runs: [], errors: ["attestation history could not be read"] };
64612
- }
64613
- }
64614
- function selectAttestationForPush(runs) {
64615
- const newestBehavioral = runs.find((run2) => run2.tool === "himi-test");
64616
- if (!newestBehavioral) return runs[0];
64617
- const behavioralIndex = runs.indexOf(newestBehavioral);
64618
- const newerFailure = runs.slice(0, behavioralIndex).find((run2) => run2.report.ok === false);
64619
- return newerFailure ?? newestBehavioral;
64620
- }
64621
- function pushEvidence(contentDir2, releaseId, verify) {
64622
- const retained = readRetainedAttestations(contentDir2);
64623
- const forRelease = retained.runs.filter((candidate) => candidate.releaseId === releaseId);
64624
- const verified = forRelease.filter(verify);
64625
- const selected = selectAttestationForPush(verified);
64626
- return {
64627
- ...selected ? { selected } : {},
64628
- historyErrors: retained.errors,
64629
- staleRuns: verified.length === 0 ? forRelease.length : 0
64630
- };
64631
- }
64632
- function summaryOf(attestation) {
64633
- return {
64634
- runId: attestation.runId,
64635
- selfHash: attestation.selfHash,
64636
- tool: attestation.tool,
64637
- screenIds: Object.keys(attestation.descriptorHashes).sort(),
64638
- coverage: attestation.coverage,
64639
- report: { ok: attestation.report.ok, counts: attestation.report.counts },
64640
- gitDiffHash: attestation.gitDiffHash,
64641
- releaseHash: attestation.releaseHash
64642
- };
64643
- }
64644
- function writeAttestation(input, opts = {}) {
64645
- const contentDir2 = resolve6(input.contentDir);
64646
- const dir = join6(contentDir2, ".himi", "attestations");
64647
- mkdirSync5(dir, { recursive: true });
64648
- const previous = readLatestAttestation(contentDir2);
64649
- const runId = `${(opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()}-${(opts.randomSuffix ?? (() => randomBytes(3).toString("hex")))()}`;
64650
- const descriptorHashes = Object.fromEntries(
64651
- Object.entries(input.descriptors).sort(([a], [b]) => a.localeCompare(b)).map(([screen, bytes2]) => [screen, sha2562(bytes2)])
64652
- );
64653
- const screenshotEntries = Object.entries(input.screenshots ?? {}).sort(([a], [b]) => a.localeCompare(b)).filter(([, path2]) => existsSync3(path2)).map(([screen, path2]) => {
64654
- const bytes2 = readFileSync5(path2);
64655
- return { screen, bytes: bytes2, hash: sha2562(bytes2) };
64656
- });
64657
- const screenshots = Object.fromEntries(screenshotEntries.map(({ screen, hash }) => [screen, hash]));
64658
- for (const { bytes: bytes2, hash } of screenshotEntries) {
64659
- const artifact = screenshotArtifactPath(contentDir2, hash);
64660
- mkdirSync5(join6(dir, "artifacts"), { recursive: true });
64661
- if (!existsSync3(artifact)) writeFileSync5(artifact, bytes2);
64662
- }
64663
- const unsigned = {
64664
- attestation: 2,
64665
- runId,
64666
- tool: input.tool,
64667
- app: input.app,
64668
- contentDir: storedContentDir(contentDir2),
64669
- gitDiffHash: input.gitDiffHash,
64670
- releaseId: input.releaseId,
64671
- releaseHash: releasePayloadHash(input.releasePayload),
64672
- gitUntrackedHash: currentGitUntrackedHash(contentDir2),
64673
- descriptorHashes,
64674
- screenshots,
64675
- coverage: input.coverage,
64676
- report: input.report,
64677
- prev: previous?.selfHash ?? null
64678
- };
64679
- const attestation = { ...unsigned, selfHash: "" };
64680
- attestation.selfHash = selfHashFor(attestation);
64681
- const json5 = JSON.stringify(attestation, null, 2) + "\n";
64682
- const runFile = `${runId.replace(/:/g, "-")}.json`;
64683
- writeFileSync5(join6(dir, runFile), json5);
64684
- writeFileSync5(join6(dir, "latest.json"), json5);
64685
- const keep = Math.max(1, opts.maxRuns ?? DEFAULT_MAX_RUNS);
64686
- const allPriorRuns = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").filter((name) => name !== runFile).sort().reverse();
64687
- const priorRuns = allPriorRuns.filter((name) => {
64688
- const kind = classifyRetainedRun(dir, name).kind;
64689
- if (kind === "legacy") {
64690
- unlinkSync(join6(dir, name));
64691
- return false;
64692
- }
64693
- return kind !== "future";
64694
- });
64695
- for (const name of priorRuns.slice(Math.max(0, keep - 1))) unlinkSync(join6(dir, name));
64696
- const retainedFiles = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
64697
- const retainedRuns = retainedFiles.map((name) => {
64698
- const classified = classifyRetainedRun(dir, name);
64699
- return classified.kind === "current" ? classified.run : null;
64700
- });
64701
- if (retainedRuns.every((run2) => run2 !== null)) {
64702
- const retainedScreenshotHashes = new Set(
64703
- retainedRuns.flatMap((run2) => Object.values(run2.screenshots))
64704
- );
64705
- const artifactDir = join6(dir, "artifacts");
64706
- if (existsSync3(artifactDir)) {
64707
- for (const name of readdirSync3(artifactDir).filter((candidate) => candidate.endsWith(".png"))) {
64708
- if (!retainedScreenshotHashes.has(name.slice(0, -4))) unlinkSync(join6(artifactDir, name));
64709
- }
64710
- }
64711
- }
64712
- return attestation;
64713
- }
64714
- function verifyAttestation(attestation, contentDir2, descriptors, expectedApp, releasePayload, opts = {}) {
64715
- const errors = [];
64716
- if (selfHashFor(attestation) !== attestation.selfHash) errors.push("selfHash does not match the attestation bytes");
64717
- try {
64718
- const dir = join6(resolve6(contentDir2), ".himi", "attestations");
64719
- const latest = readLatestAttestation(contentDir2);
64720
- if (!latest || opts.requireLatest !== false && latest.selfHash !== attestation.selfHash) {
64721
- errors.push("attestation does not match latest.json");
64722
- } else {
64723
- const classified = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").map((name) => classifyRetainedRun(dir, name)).filter((entry) => entry.kind !== "future");
64724
- const retained = classified.map((entry) => entry.kind === "current" ? entry.run : null);
64725
- if (retained.some((run2) => run2 === null)) {
64726
- errors.push("attestation history contains a malformed retained run");
64727
- } else {
64728
- const runs = retained;
64729
- const byHash = new Map(runs.map((run2) => [run2.selfHash, run2]));
64730
- if (!byHash.has(attestation.selfHash)) errors.push("attestation has no matching retained run");
64731
- if (!byHash.has(latest.selfHash)) {
64732
- errors.push("latest.json has no matching retained run");
64733
- } else {
64734
- const seen = /* @__PURE__ */ new Set();
64735
- let cursor = latest;
64736
- while (cursor && !seen.has(cursor.selfHash)) {
64737
- seen.add(cursor.selfHash);
64738
- if (selfHashFor(cursor) !== cursor.selfHash) {
64739
- errors.push(`selfHash does not match retained run ${cursor.runId}`);
64740
- break;
64741
- }
64742
- cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
64743
- }
64744
- if (seen.size !== runs.length) errors.push("attestation hash chain does not reach every retained run");
64745
- }
64746
- }
64747
- }
64748
- } catch {
64749
- errors.push("attestation history could not be verified");
64750
- }
64751
- if (attestation.app !== expectedApp) errors.push(`attestation app does not match ${expectedApp}`);
64752
- if (attestation.contentDir !== storedContentDir(contentDir2)) errors.push("attestation contentDir does not match the requested content directory");
64753
- if (releasePayloadHash(releasePayload) !== attestation.releaseHash) {
64754
- errors.push("releaseHash does not match the current release payload");
64755
- }
64756
- try {
64757
- const liveDiffHash = currentGitDiffHash(contentDir2);
64758
- if (liveDiffHash !== "unavailable" && liveDiffHash !== attestation.gitDiffHash) {
64759
- errors.push("gitDiffHash does not match the live git diff");
64760
- }
64761
- } catch {
64762
- errors.push("gitDiffHash could not be recomputed from the live git diff");
64763
- }
64764
- try {
64765
- const liveUntrackedHash = currentGitUntrackedHash(contentDir2);
64766
- if (liveUntrackedHash !== "unavailable" && liveUntrackedHash !== attestation.gitUntrackedHash) {
64767
- errors.push("gitUntrackedHash does not match the live untracked source bytes");
64768
- }
64769
- } catch {
64770
- errors.push("gitUntrackedHash could not be recomputed from the live untracked source bytes");
64771
- }
64772
- for (const [screen, expected] of Object.entries(attestation.descriptorHashes)) {
64773
- const bytes2 = descriptors[screen];
64774
- if (bytes2 === void 0) {
64775
- errors.push(`missing baked descriptor for ${screen}`);
64776
- continue;
64777
- }
64778
- if (sha2562(bytes2) !== expected) errors.push(`descriptor hash does not match for ${screen}`);
64779
- }
64780
- for (const [screen, expected] of Object.entries(attestation.screenshots)) {
64781
- const path2 = screenshotArtifactPath(contentDir2, expected);
64782
- if (!existsSync3(path2)) {
64783
- errors.push(`missing screenshot for ${screen}: ${basename2(path2)}`);
64784
- continue;
64785
- }
64786
- if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match for ${screen}: ${basename2(path2)}`);
64787
- }
64788
- return { ok: errors.length === 0, errors };
64789
- }
64876
+ init_attestation();
64790
64877
 
64791
64878
  // ../../core/admin-web/src/utils/qr-terminal.ts
64792
64879
  init_qr();
@@ -68005,7 +68092,7 @@ function nativeConfigTokenNotice(configSource, optedIn) {
68005
68092
  }
68006
68093
  async function nativeConfigAdvisories(app, configSource) {
68007
68094
  try {
68008
- const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
68095
+ const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
68009
68096
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
68010
68097
  const iosYmlPath = iosProjectYml2(app);
68011
68098
  const iosYml = existsSync63(iosYmlPath) ? readFileSync75(iosYmlPath, "utf8") : "";
@@ -68526,7 +68613,7 @@ function uxHandoverHint(uxEvidence, ctx) {
68526
68613
  if (uxEvidence.state === "failed") {
68527
68614
  return `a mobile-UX drive for ${releaseId} IS recorded, and it FAILED on ${uxEvidence.failedPlatforms.join(", ")} (see ${uxEvidence.path}). A published draft is the owner-private preview a customer is handed, so fix what the drive found and re-record it \u2014 re-running \`himi ux record\` over a failure without changing the app just writes the same failure down again. Only if you intend to hand a customer a preview whose UX you already know is broken, \`${door} --force\`.`;
68528
68615
  }
68529
- const why = uxEvidence.state === "absent" ? `nothing has recorded a mobile-UX drive for this package \u2014 ${uxEvidence.path} does not exist` : uxEvidence.state === "unrecorded" ? uxEvidence.malformed.length ? `${uxEvidence.path} has no iOS or Android drive this gate can read \u2014 ${uxEvidence.malformed.join("; ")}` : `${uxEvidence.path} exists but carries no iOS or Android drive (\`himi render\` writes its rendered widths there too, so the file alone proves nothing)` : uxEvidence.state === "unreadable" ? `${ctx.quote(`${ctx.contentDir}/${uxEvidence.path}`)} ${uxEvidence.error} \u2014 delete it and record again, because re-recording over a malformed store does not repair it` : uxEvidence.state === "outdated" && ctx.nondeterministicWorkers?.length ? `a mobile-UX drive for ${releaseId} is recorded, but it describes different content from the release this push just built \u2014 an uncommitted edit since the record would do that, and so does the worker named below` : uxEvidence.state === "outdated" ? `a mobile-UX drive for ${releaseId} is recorded, but the package has CHANGED since \u2014 in-repo a release id is the git HEAD sha, so editing a screen, a token or a worker without committing keeps the id while replacing everything it identifies (a record written before this check also lands here, carrying no content hash)` : `the recorded mobile-UX drive is about ${uxEvidence.otherReleases.length ? `release ${uxEvidence.otherReleases.join(", ")}` : "no release at all"}, not ${releaseId}${uxEvidence.unstamped ? ` (${uxEvidence.unstamped} drive(s) name no release)` : ""}`;
68616
+ const why = uxEvidence.state === "absent" ? `nothing has recorded a mobile-UX drive for this package \u2014 ${uxEvidence.path} does not exist` : uxEvidence.state === "unrecorded" ? uxEvidence.malformed.length ? `${uxEvidence.path} has no iOS or Android drive this gate can read \u2014 ${uxEvidence.malformed.join("; ")}` : `${uxEvidence.path} exists but carries no iOS or Android drive (\`himi render\` writes its rendered widths there too, so the file alone proves nothing)` : uxEvidence.state === "unreadable" ? `${ctx.quote(`${ctx.contentDir}/${uxEvidence.path}`)} ${uxEvidence.error} \u2014 delete it and record again, because re-recording over a malformed store does not repair it` : uxEvidence.state === "outdated" && ctx.nondeterministicWorkers?.length ? `a mobile-UX drive for an earlier build of this commit is recorded, but it describes different content from the release this push just built (${releaseId}) \u2014 an uncommitted edit since the record would do that, and so does the worker named below` : uxEvidence.state === "outdated" ? `a mobile-UX drive for an earlier build of this commit is recorded, but the package has CHANGED since \u2014 editing a screen, a token or a worker without committing makes a new release, ${releaseId} (a record written before this check also lands here, carrying no content hash)` : `the recorded mobile-UX drive is about ${uxEvidence.otherReleases.length ? `release ${uxEvidence.otherReleases.join(", ")}` : "no release at all"}, not ${releaseId}${uxEvidence.unstamped ? ` (${uxEvidence.unstamped} drive(s) name no release)` : ""}`;
68530
68617
  const drifting = ctx.nondeterministicWorkers ?? [];
68531
68618
  if (drifting.length > 0) {
68532
68619
  return `${why}. ${releaseDriftCause(drifting, "Re-recording", "a drive")}; then record the drive: ${uxRemedy}, and re-run \`${door}\`. Only if you intend to hand over a preview WITHOUT that evidence, \`${door} --force\`.`;
@@ -68761,7 +68848,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
68761
68848
  };
68762
68849
  const runReleasePatch = async (opts) => {
68763
68850
  const { app, plan, draft } = opts;
68764
- const { regressionDiff: regressionDiff2, patchHonesty: patchHonesty2 } = await Promise.resolve().then(() => (init_release_patch(), release_patch_exports));
68851
+ const { regressionDiff: regressionDiff2, patchHonesty: patchHonesty2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
68765
68852
  const target = targetName();
68766
68853
  const baseFound = await fetchReleaseManifest(client2, app, opts.fromRelease);
68767
68854
  if ("error" in baseFound) {
@@ -69170,12 +69257,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69170
69257
  try {
69171
69258
  const bytes2 = readFileSync75(iconPath);
69172
69259
  payload.icon = bytes2.toString("base64");
69173
- iconHash = createHash27("sha256").update(bytes2).digest("hex");
69260
+ iconHash = createHash28("sha256").update(bytes2).digest("hex");
69174
69261
  } catch {
69175
69262
  }
69176
69263
  }
69177
69264
  payload.app = templateAppId;
69178
- payload.manifest = { ...payload.manifest, app: templateAppId, ...iconHash ? { iconHash } : {} };
69265
+ const { rebindReleaseId: rebindReleaseId2 } = await Promise.resolve().then(() => (init_build3(), build_exports2));
69266
+ payload.manifest = rebindReleaseId2({ ...payload.manifest, app: templateAppId, ...iconHash ? { iconHash } : {} });
69179
69267
  const files = sentinelizeArchive(readTemplateFiles(sourceDir), app);
69180
69268
  if (Object.keys(files).length === 0) {
69181
69269
  io.error(JSON.stringify({ error: `no source files collected from ${sourceDir}` }));
@@ -70801,7 +70889,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70801
70889
  const contentArg = shellQuote2(contentRel);
70802
70890
  if (onlyRaw !== void 0) {
70803
70891
  const onlyDraft = flags.draft === true;
70804
- const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch(), release_patch_exports));
70892
+ const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
70805
70893
  const parsed = parseOnlySelectors2(onlyRaw, {
70806
70894
  screens: Object.keys(manifest.screens ?? {}),
70807
70895
  bundles: Object.keys(manifest.bundles ?? {})
@@ -70859,7 +70947,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70859
70947
  let localSourceHash;
70860
70948
  try {
70861
70949
  const { collectAppSource: collectAppSource2, zipAppSource: zipAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
70862
- localSourceHash = createHash27("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
70950
+ localSourceHash = createHash28("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
70863
70951
  } catch {
70864
70952
  }
70865
70953
  return runReleasePatch({
@@ -70934,7 +71022,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70934
71022
  }
70935
71023
  const evidence = pushEvidence(dir, manifest.releaseId, (candidate) => verifyAttestation(candidate, dir, payload.screens, declared, payload, { requireLatest: false }).ok);
70936
71024
  const drifting = nondeterministicWorkers(outcome.workers);
70937
- const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) carry this release id, but none describes the content being published` : null;
71025
+ const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) were recorded at this commit, but none describes the content being published` : null;
70938
71026
  const attestationErrors = [...evidence.historyErrors, ...staleEvidence ? [staleEvidence] : []];
70939
71027
  if (attestationErrors.length > 0 && !force) {
70940
71028
  const remedy = `\`himi test --content ${contentArg}\` (or \`himi render <screenId> --content ${contentArg}\`)`;
@@ -70943,7 +71031,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70943
71031
  ok: false,
70944
71032
  pushed: false,
70945
71033
  errors: attestationErrors,
70946
- hint: corrupted ? `attestation history in .himi/attestations/ is corrupt, so the retained runs cannot be trusted \u2014 a fresh run does not remove the bad entry. Delete .himi/attestations/, re-attest with ${remedy}, then re-run \`himi push\`. Only if you intend to publish WITHOUT that evidence, \`himi push --force\`.` : `no retained attestation for this release passed live verification${drifting.length ? `. ${releaseDriftCause(drifting, "Re-attesting", "an attestation")}, then re-attest` : " \u2014 a release id is the HEAD sha, so runs recorded before your latest edits still carry it while describing a tree that no longer exists. Re-attest"} the CURRENT release with ${remedy}, then re-run \`himi push\`. Only if you intend to publish WITHOUT that evidence, \`himi push --force\`.`
71034
+ hint: corrupted ? `attestation history in .himi/attestations/ is corrupt, so the retained runs cannot be trusted \u2014 a fresh run does not remove the bad entry. Delete .himi/attestations/, re-attest with ${remedy}, then re-run \`himi push\`. Only if you intend to publish WITHOUT that evidence, \`himi push --force\`.` : `no retained attestation for this release passed live verification${drifting.length ? `. ${releaseDriftCause(drifting, "Re-attesting", "an attestation")}, then re-attest` : " \u2014 runs recorded at this commit before your latest edits describe a tree that no longer exists. Re-attest"} the CURRENT release with ${remedy}, then re-run \`himi push\`. Only if you intend to publish WITHOUT that evidence, \`himi push --force\`.`
70947
71035
  });
70948
71036
  return 1;
70949
71037
  }
@@ -72408,7 +72496,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72408
72496
  });
72409
72497
  }
72410
72498
  const bytes2 = readFileSync75(file);
72411
- const sha = createHash27("sha256").update(bytes2).digest("hex");
72499
+ const sha = createHash28("sha256").update(bytes2).digest("hex");
72412
72500
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
72413
72501
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
72414
72502
  io.error(JSON.stringify({ ok: false, error: "no_rebind_takes_no_value", message: "--no-rebind is a switch and takes no value; pass it bare.", usage: PATCH_USAGE }));
@@ -72744,7 +72832,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72744
72832
  const { preflightApp: preflightApp2 } = await Promise.resolve().then(() => (init_preflight_app(), preflight_app_exports));
72745
72833
  const { resolveStoreCredentials: resolveStoreCredentials2 } = await Promise.resolve().then(() => (init_credential_profile(), credential_profile_exports));
72746
72834
  const { readIosVersion: readIosVersion2, bumpVersion: bumpVersion2 } = await Promise.resolve().then(() => (init_version(), version_exports));
72747
- const { iosProjectYml: iosYmlPath, hasRepoCheckout: hasRepoCheckout2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
72835
+ const { iosProjectYml: iosYmlPath, hasRepoCheckout: hasRepoCheckout2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
72748
72836
  const { ascStatus: ascStatus2 } = await Promise.resolve().then(() => (init_asc_status(), asc_status_exports));
72749
72837
  const { readFileSync: rf, readdirSync: rd, existsSync: ex, statSync: st } = await import("node:fs");
72750
72838
  const { execFile: execFile2 } = await import("node:child_process");