@wix/himalaya-cli 0.1056.0 → 0.1058.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";
@@ -5880,6 +6657,7 @@ function walk(node, registry, path2, out) {
5880
6657
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
5881
6658
  var init_tokens = __esm({
5882
6659
  "../../core/ts/src/tokens.ts"() {
6660
+ "use strict";
5883
6661
  SYSTEM_FONT_FAMILY = "$system";
5884
6662
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
5885
6663
  }
@@ -5906,6 +6684,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
5906
6684
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
5907
6685
  var init_bindings = __esm({
5908
6686
  "../../core/ts/src/bindings.ts"() {
6687
+ "use strict";
5909
6688
  BINDING_SIGILS = ["service", "flag", "item", "t"];
5910
6689
  BINDING_SIGIL_RE = new RegExp(
5911
6690
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -14627,11 +15406,12 @@ function uxEvidenceForRelease(content, release) {
14627
15406
  const failedPlatforms = forRelease.filter(([, entry]) => entry.status === "failed").map(([platform]) => platform);
14628
15407
  return failedPlatforms.length > 0 ? { ...base, state: "failed", platforms, failedPlatforms, malformed } : { ...base, state: "recorded", platforms, malformed };
14629
15408
  }
14630
- if (recorded.some(([, entry]) => entry.releaseId === releaseId)) {
15409
+ const sameCommit = recorded.filter(([, entry]) => sameReleaseCommit(entry.releaseId, releaseId));
15410
+ if (sameCommit.length > 0) {
14631
15411
  return {
14632
15412
  ...base,
14633
15413
  state: "outdated",
14634
- platforms: recorded.filter(([, entry]) => entry.releaseId === releaseId).map(([platform]) => platform),
15414
+ platforms: sameCommit.map(([platform]) => platform),
14635
15415
  malformed
14636
15416
  };
14637
15417
  }
@@ -14648,6 +15428,7 @@ var NATIVE_PLATFORMS, NATIVE_AUDIT_STATUSES;
14648
15428
  var init_ux_evidence = __esm({
14649
15429
  "src/ux-evidence.ts"() {
14650
15430
  init_mobile_ux_contract();
15431
+ init_attestation();
14651
15432
  NATIVE_PLATFORMS = ["ios", "android"];
14652
15433
  NATIVE_AUDIT_STATUSES = ["passed", "failed", "unavailable"];
14653
15434
  }
@@ -15945,7 +16726,7 @@ function generationAllowed(app, configTs, opts = {}) {
15945
16726
  var DEFAULT_ACCENT_TOKEN, DEFAULT_GLYPH_SCALE, GLYPH_SCALE_MIN, GLYPH_SCALE_MAX, LEGACY_PLACEHOLDER_SHA256;
15946
16727
  var init_plan2 = __esm({
15947
16728
  "../app-icons/plan.ts"() {
15948
- init_paths();
16729
+ init_paths2();
15949
16730
  init_glyphs();
15950
16731
  DEFAULT_ACCENT_TOKEN = "accentPrimary";
15951
16732
  DEFAULT_GLYPH_SCALE = 0.62;
@@ -16013,7 +16794,7 @@ var ANDROID_ICON_BACKGROUND_RES, ANDROID_ADAPTIVE_FILES, ANDROID_ICON_BACKGROUND
16013
16794
  var init_icon2 = __esm({
16014
16795
  "../native-config/icon.ts"() {
16015
16796
  init_plan2();
16016
- init_paths();
16797
+ init_paths2();
16017
16798
  ANDROID_ICON_BACKGROUND_RES = "ic_launcher_background";
16018
16799
  ANDROID_ADAPTIVE_FILES = ["ic_launcher.xml", "ic_launcher_round.xml"];
16019
16800
  ANDROID_ICON_BACKGROUND_FILE = "ic_launcher_background.xml";
@@ -16522,7 +17303,7 @@ var init_gen_native_config = __esm({
16522
17303
  "../native-config/gen-native-config.ts"() {
16523
17304
  init_src2();
16524
17305
  init_src2();
16525
- init_paths();
17306
+ init_paths2();
16526
17307
  init_splash();
16527
17308
  init_icon2();
16528
17309
  init_live_activity();
@@ -22031,6 +22812,7 @@ function shouldRevealError(args) {
22031
22812
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
22032
22813
  var init_validation = __esm({
22033
22814
  "../../core/ts/src/validation.ts"() {
22815
+ "use strict";
22034
22816
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
22035
22817
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
22036
22818
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -25631,6 +26413,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
25631
26413
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
25632
26414
  var init_worker_subtree = __esm({
25633
26415
  "../../core/ts/src/worker-subtree.ts"() {
26416
+ "use strict";
25634
26417
  init_worker_subtree_safe_types_generated();
25635
26418
  WORKER_SUBTREE_CEILINGS = Object.freeze({
25636
26419
  maxEncodedBytes: 65536,
@@ -27598,7 +28381,10 @@ function renderInner(node, ctx) {
27598
28381
  case "Badge": {
27599
28382
  const _bt = boundTokenName(props.tokenPath, props.token, ctx);
27600
28383
  const c = _bt ? C(_bt, "#116DFF") : sevColor(ctx, props.style ?? "info");
27601
- return /* @__PURE__ */ jsx6("span", { style: { display: "inline-flex", alignItems: "center", padding: "3px 9px", borderRadius: 999, fontSize: 12, fontWeight: 700, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 16%, ${C("surfacePrimary", "#fff")})`, color: c, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Badge" });
28384
+ const liveCount = typeof props.text === "string" && /^\$(?:service|flag)\b/.test(props.text);
28385
+ const badgeLabel = modifiers?.accessibilityLabel;
28386
+ const badgeRole = liveCount ? "status" : typeof badgeLabel === "string" && badgeLabel.trim() !== "" ? "img" : void 0;
28387
+ return /* @__PURE__ */ jsx6("span", { role: badgeRole, style: { display: "inline-flex", alignItems: "center", padding: "3px 9px", borderRadius: 999, fontSize: 12, fontWeight: 700, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 16%, ${C("surfacePrimary", "#fff")})`, color: c, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Badge" });
27602
28388
  }
27603
28389
  case "Tag": {
27604
28390
  const c = C(boundTokenName(props.tokenPath, props.token, ctx) ?? "accentPrimary", "#116DFF");
@@ -29517,21 +30303,22 @@ var init_renderNode = __esm({
29517
30303
  opacity: disabled(at) ? 0.4 : 1,
29518
30304
  userSelect: "none"
29519
30305
  });
30306
+ const named = { [ROLE_WHEN_NAMED]: "group" };
29520
30307
  if (ctx.empty) {
29521
- return /* @__PURE__ */ jsxs3("div", { style: box, children: [
30308
+ return /* @__PURE__ */ jsxs3("div", { ...named, style: box, children: [
29522
30309
  /* @__PURE__ */ jsx6("span", { style: btn("min"), children: "\u2212" }),
29523
30310
  /* @__PURE__ */ jsx6("b", { style: { fontSize: 16, minWidth: 24, textAlign: "center", color: C("textPrimary", "#111") }, children: "\u2014" }),
29524
30311
  /* @__PURE__ */ jsx6("span", { style: btn("max"), children: "+" })
29525
30312
  ] });
29526
30313
  }
29527
30314
  if (!ctx.onAction) {
29528
- return /* @__PURE__ */ jsxs3("div", { style: box, children: [
30315
+ return /* @__PURE__ */ jsxs3("div", { ...named, style: box, children: [
29529
30316
  /* @__PURE__ */ jsx6("span", { style: btn("min"), children: "\u2212" }),
29530
30317
  /* @__PURE__ */ jsx6("b", { style: { fontSize: 16, minWidth: 24, textAlign: "center", color: C("textPrimary", "#111") }, children: value }),
29531
30318
  /* @__PURE__ */ jsx6("span", { style: btn("max"), children: "+" })
29532
30319
  ] });
29533
30320
  }
29534
- return /* @__PURE__ */ jsxs3("div", { style: box, children: [
30321
+ return /* @__PURE__ */ jsxs3("div", { ...named, style: box, children: [
29535
30322
  /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Decrease", disabled: disabled("min"), onClick: () => bump(-step), style: { ...btn("min"), font: "inherit" }, children: "\u2212" }),
29536
30323
  /* @__PURE__ */ jsx6("b", { style: { fontSize: 16, minWidth: 24, textAlign: "center", color: C("textPrimary", "#111"), fontVariantNumeric: "tabular-nums" }, children: value }),
29537
30324
  /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Increase", disabled: disabled("max"), onClick: () => bump(step), style: { ...btn("max"), font: "inherit" }, children: "+" })
@@ -29744,10 +30531,11 @@ var init_renderNode = __esm({
29744
30531
  const imageUrl = isBinding(props.imageUrl) ? boundUrl : props.imageUrl;
29745
30532
  const initials = isBinding(props.initials) ? boundInitials : props.initials;
29746
30533
  const size = props.size ?? 48;
30534
+ const a11y = { [ROLE_WHEN_NAMED]: "img", [SELF_RENDERED_LEAF]: true };
29747
30535
  if (typeof imageUrl === "string" && imageUrl) {
29748
- return /* @__PURE__ */ jsx6("img", { src: resolveAssetUrl(imageUrl, ctx.assetBaseUrl), alt: "", style: { width: size, height: size, borderRadius: "50%", objectFit: "cover", flex: "none", ...wrap } });
30536
+ return /* @__PURE__ */ jsx6("img", { ...a11y, src: resolveAssetUrl(imageUrl, ctx.assetBaseUrl), alt: "", style: { width: size, height: size, borderRadius: "50%", objectFit: "cover", flex: "none", ...wrap } });
29749
30537
  }
29750
- return /* @__PURE__ */ jsx6("div", { style: { width: size, height: size, borderRadius: "50%", background: C("accentPrimary", "#116DFF"), color: C("textOnAccent", "#fff"), display: "grid", placeItems: "center", fontWeight: 600, fontSize: size * 0.4, fontFamily: bodyFont(ctx), flex: "none", ...wrap }, children: (asText2(initials) || "?").slice(0, 2).toUpperCase() });
30538
+ return /* @__PURE__ */ jsx6("div", { ...a11y, style: { width: size, height: size, borderRadius: "50%", background: C("accentPrimary", "#116DFF"), color: C("textOnAccent", "#fff"), display: "grid", placeItems: "center", fontWeight: 600, fontSize: size * 0.4, fontFamily: bodyFont(ctx), flex: "none", ...wrap }, children: (asText2(initials) || "?").slice(0, 2).toUpperCase() });
29751
30539
  });
29752
30540
  IconNode = withExtras("IconNode", ({ props, ctx, wrap }) => {
29753
30541
  const C = ctx.color;
@@ -30052,7 +30840,11 @@ var init_renderNode = __esm({
30052
30840
  const shown2 = avatars.slice(0, maxVisible);
30053
30841
  const overflow = props.overflowCount || Math.max(0, avatars.length - maxVisible);
30054
30842
  const shift = Math.round(size * (1 - (props.overlapRatio ?? 0.67)));
30055
- return /* @__PURE__ */ jsxs3("div", { style: { width: "100%", ...wrap }, children: [
30843
+ const caption = useBoundString(props.caption, ctx).trim();
30844
+ const count = shown2.length + overflow;
30845
+ const countText = count > 0 ? `${count} ${count === 1 ? "person" : "people"}` : "";
30846
+ const groupName = [caption, countText].filter(Boolean).join(", ");
30847
+ return /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": groupName || void 0, "data-a11y-value": countText || void 0, ...{ [SELF_RENDERED_LEAF]: true }, style: { width: "100%", ...wrap }, children: [
30056
30848
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex" }, children: [
30057
30849
  shown2.map((a, i) => {
30058
30850
  const ring = props.ringGradient ? ctx.gradient(props.ringGradient) : void 0;
@@ -30881,7 +31673,7 @@ var init_renderNode = __esm({
30881
31673
  /* @__PURE__ */ jsxs3("div", { style: { position: "relative", height: Math.min(props.expandedHeight ?? 120, 160), borderRadius: ctx.radius("md", 10), overflow: "hidden", background: imageUrl ? void 0 : `linear-gradient(135deg, ${C("accentPrimary", "#116DFF")}, ${C("accentSecondary", "#0E5FE3")})` }, children: [
30882
31674
  imageUrl && /* @__PURE__ */ jsx6("img", { src: imageUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" } }),
30883
31675
  /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", left: 14, bottom: 12, color: "#fff", textShadow: "0 1px 4px rgba(0,0,0,.5)" }, children: [
30884
- /* @__PURE__ */ jsx6("div", { style: { fontFamily: ctx.font("title1").family || ctx.idiom && ctx.idiom !== "base" ? fontStack(ctx.font("title1").family, ctx.idiom) : '"Wix Madefor Display", system-ui', fontWeight: 700, fontSize: 20 }, children: txt(props.title, ctx) }),
31676
+ /* @__PURE__ */ jsx6("div", { role: "heading", "aria-level": 2, style: { fontFamily: ctx.font("title1").family || ctx.idiom && ctx.idiom !== "base" ? fontStack(ctx.font("title1").family, ctx.idiom) : '"Wix Madefor Display", system-ui', fontWeight: 700, fontSize: 20 }, children: txt(props.title, ctx) }),
30885
31677
  props.subtitle && /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, opacity: 0.9 }, children: txt(props.subtitle, ctx) })
30886
31678
  ] })
30887
31679
  ] }),
@@ -37205,6 +37997,44 @@ var init_src6 = __esm({
37205
37997
  }
37206
37998
  });
37207
37999
 
38000
+ // ../../core/serve-storage/src/release-patch.ts
38001
+ import { createHash as createHash17 } from "node:crypto";
38002
+ function sha256hex(b) {
38003
+ return createHash17("sha256").update(b).digest("hex");
38004
+ }
38005
+ function stableStringify(value) {
38006
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
38007
+ if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
38008
+ const object = value;
38009
+ return "{" + Object.keys(object).sort().map((key2) => JSON.stringify(key2) + ":" + stableStringify(object[key2])).join(",") + "}";
38010
+ }
38011
+ function manifestContent(m) {
38012
+ const {
38013
+ releaseId: _releaseId,
38014
+ gitSha: _gitSha,
38015
+ shellSha: _shellSha,
38016
+ label: _label,
38017
+ notes: _notes,
38018
+ publishedAt: _publishedAt,
38019
+ publishedBy: _publishedBy,
38020
+ attestation: _attestation,
38021
+ forcedPastErrors: _forcedPastErrors,
38022
+ ...content
38023
+ } = m;
38024
+ return content;
38025
+ }
38026
+ function releaseContentId(m) {
38027
+ const content = manifestContent(m);
38028
+ const widgets = content.widgets;
38029
+ if (widgets && typeof widgets === "object" && !Array.isArray(widgets)) content.widgets = stableWidgets(widgets);
38030
+ return sha256hex(stableStringify(content));
38031
+ }
38032
+ var init_release_patch = __esm({
38033
+ "../../core/serve-storage/src/release-patch.ts"() {
38034
+ init_widget_identity();
38035
+ }
38036
+ });
38037
+
37208
38038
  // ../../core/dev-server/src/net-scenario.ts
37209
38039
  function resolveScenario(rules, req, upstream) {
37210
38040
  const ctx = {
@@ -39918,11 +40748,11 @@ var init_dependencies = __esm({
39918
40748
  });
39919
40749
 
39920
40750
  // ../../core/functions-build/src/sbom.ts
39921
- import { createHash as createHash17 } from "node:crypto";
40751
+ import { createHash as createHash18 } from "node:crypto";
39922
40752
  function createFunctionSbom(packages) {
39923
40753
  const components2 = [...packages].sort((a, b) => a.name.localeCompare(b.name)).map(({ name, version: version2, integrity: integrity2, license }) => ({ name, version: version2, integrity: integrity2, license }));
39924
40754
  const body = JSON.stringify({ format: "himalaya-sbom-v1", components: components2 });
39925
- return { format: "himalaya-sbom-v1", components: components2, hash: createHash17("sha256").update(body).digest("hex") };
40755
+ return { format: "himalaya-sbom-v1", components: components2, hash: createHash18("sha256").update(body).digest("hex") };
39926
40756
  }
39927
40757
  var init_sbom = __esm({
39928
40758
  "../../core/functions-build/src/sbom.ts"() {
@@ -39930,12 +40760,12 @@ var init_sbom = __esm({
39930
40760
  });
39931
40761
 
39932
40762
  // ../../core/functions-build/src/build.ts
39933
- import { createHash as createHash18 } from "node:crypto";
40763
+ import { createHash as createHash19 } from "node:crypto";
39934
40764
  function sha2563(value) {
39935
- return createHash18("sha256").update(value).digest("hex");
40765
+ return createHash19("sha256").update(value).digest("hex");
39936
40766
  }
39937
40767
  function sri(value) {
39938
- return `sha256-${createHash18("sha256").update(value).digest("base64")}`;
40768
+ return `sha256-${createHash19("sha256").update(value).digest("base64")}`;
39939
40769
  }
39940
40770
  function schemaHash(schema) {
39941
40771
  if (!schema) return "";
@@ -40116,6 +40946,7 @@ __export(build_exports2, {
40116
40946
  pinBakedServerBaseUrl: () => pinBakedServerBaseUrl,
40117
40947
  pinnedReleaseFromEnv: () => pinnedReleaseFromEnv,
40118
40948
  publishableFontFaces: () => publishableFontFaces,
40949
+ rebindReleaseId: () => rebindReleaseId,
40119
40950
  releaseIdFor: () => releaseIdFor,
40120
40951
  servicesFromBundleJs: () => servicesFromBundleJs,
40121
40952
  shellSha: () => shellSha,
@@ -40125,7 +40956,7 @@ __export(build_exports2, {
40125
40956
  withStoreNavBundle: () => withStoreNavBundle,
40126
40957
  withoutDevOnlyAppConfig: () => withoutDevOnlyAppConfig
40127
40958
  });
40128
- import { createHash as createHash19 } from "node:crypto";
40959
+ import { createHash as createHash20 } from "node:crypto";
40129
40960
  import { readFileSync as readFileSync42, readdirSync as readdirSync21, existsSync as existsSync39 } from "node:fs";
40130
40961
  import { dirname as dirname23, join as join37, resolve as resolve32 } from "node:path";
40131
40962
  import { fileURLToPath as fileURLToPath19 } from "node:url";
@@ -40195,10 +41026,10 @@ async function bakeCatalogs(app, locale, fetchCatalog, putDoc) {
40195
41026
  return out;
40196
41027
  }
40197
41028
  function sha2564(buf) {
40198
- return createHash19("sha256").update(buf).digest("hex");
41029
+ return createHash20("sha256").update(buf).digest("hex");
40199
41030
  }
40200
41031
  function sri2(buf) {
40201
- return "sha256-" + createHash19("sha256").update(buf).digest("base64");
41032
+ return "sha256-" + createHash20("sha256").update(buf).digest("base64");
40202
41033
  }
40203
41034
  async function widgetFailureDetail(res) {
40204
41035
  try {
@@ -40294,14 +41125,14 @@ function pinBakedServerBaseUrl(json5, base) {
40294
41125
  const boundary = /:\d+$/.test(base) ? "(?!\\d)" : "(?![\\w.-])(?!:\\d)";
40295
41126
  return json5.replace(new RegExp(`${escaped}${boundary}`, "g"), BAKED_SERVER_BASE_URL);
40296
41127
  }
40297
- function stableStringify(v) {
41128
+ function stableStringify2(v) {
40298
41129
  if (v === null || typeof v !== "object") return JSON.stringify(v);
40299
- if (Array.isArray(v)) return "[" + v.map(stableStringify).join(",") + "]";
41130
+ if (Array.isArray(v)) return "[" + v.map(stableStringify2).join(",") + "]";
40300
41131
  const o = v;
40301
- return "{" + Object.keys(o).sort().map((k) => JSON.stringify(k) + ":" + stableStringify(o[k])).join(",") + "}";
41132
+ return "{" + Object.keys(o).sort().map((k) => JSON.stringify(k) + ":" + stableStringify2(o[k])).join(",") + "}";
40302
41133
  }
40303
41134
  function contentReleaseId(content) {
40304
- return sha2564(stableStringify(content));
41135
+ return sha2564(stableStringify2(content));
40305
41136
  }
40306
41137
  function servicesFromBundleJs(bundleJs, input = {}) {
40307
41138
  try {
@@ -40316,7 +41147,13 @@ function hydrationServices(tier5Dir, bundleName, input = {}) {
40316
41147
  return js ? servicesFromBundleJs(js, input) : null;
40317
41148
  }
40318
41149
  function releaseIdFor(sha, content) {
40319
- return sha !== "dev" ? sha : contentReleaseId(content);
41150
+ if (sha === "dev") return contentReleaseId(content);
41151
+ const stored = JSON.parse(JSON.stringify(content));
41152
+ return `${sha}-${releaseContentId(stored).slice(0, 12)}`;
41153
+ }
41154
+ function rebindReleaseId(manifest) {
41155
+ const { releaseId: _releaseId, gitSha: gitSha2, shellSha: _shellSha, ...content } = manifest;
41156
+ return { ...manifest, releaseId: releaseIdFor(gitSha2, content) };
40320
41157
  }
40321
41158
  function shellSha(root = REPO_ROOT7) {
40322
41159
  if (!isMonorepoCheckout(root)) return "";
@@ -41261,6 +42098,7 @@ var init_build3 = __esm({
41261
42098
  init_src6();
41262
42099
  init_check();
41263
42100
  init_file_store();
42101
+ init_release_patch();
41264
42102
  init_validate_descriptor();
41265
42103
  init_wix_member_login();
41266
42104
  init_wix_placeholders();
@@ -41923,7 +42761,7 @@ __export(studio_exports, {
41923
42761
  renderStudioHtml: () => renderStudioHtml,
41924
42762
  writeStudioRecord: () => writeStudioRecord
41925
42763
  });
41926
- import { createHash as createHash20 } from "node:crypto";
42764
+ import { createHash as createHash21 } from "node:crypto";
41927
42765
  import { existsSync as existsSync41, readdirSync as readdirSync22, readFileSync as readFileSync44, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
41928
42766
  import { basename as basename10, extname as extname3, join as join39, relative as relative7, resolve as resolve33, sep as sep9 } from "node:path";
41929
42767
  import { pathToFileURL as pathToFileURL7 } from "node:url";
@@ -42161,7 +42999,7 @@ function buildView(dir, memory, now) {
42161
42999
  reveal: memory.reveal ?? null
42162
43000
  };
42163
43001
  const { generatedAt: _ignored, timelineFrames: _count, ...stable } = view;
42164
- const v = createHash20("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, 16);
43002
+ const v = createHash21("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, 16);
42165
43003
  return { v, ...view };
42166
43004
  }
42167
43005
  function writeAtomic(file, content) {
@@ -42659,7 +43497,7 @@ function patchHonesty(opts) {
42659
43497
  return { attestation, source };
42660
43498
  }
42661
43499
  var SELECTOR_KINDS;
42662
- var init_release_patch = __esm({
43500
+ var init_release_patch2 = __esm({
42663
43501
  "src/release-patch.ts"() {
42664
43502
  init_widget_identity();
42665
43503
  SELECTOR_KINDS = ["changed", "screen:<id|glob>", "bundle:<name>", "tokens", "app-config"];
@@ -45405,7 +46243,7 @@ __export(pack_exports, {
45405
46243
  unpackTo: () => unpackTo,
45406
46244
  writeZip: () => writeZip
45407
46245
  });
45408
- import { createHash as createHash21 } from "node:crypto";
46246
+ import { createHash as createHash22 } from "node:crypto";
45409
46247
  import { deflateRawSync, inflateRawSync } from "node:zlib";
45410
46248
  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";
45411
46249
  import { dirname as dirname26, join as join42, resolve as resolve37 } from "node:path";
@@ -45555,11 +46393,11 @@ function gatherReleaseEntries(appDir2) {
45555
46393
  return { entries, manifest, skipped };
45556
46394
  }
45557
46395
  function sha256Hex4(bytes2) {
45558
- return createHash21("sha256").update(bytes2).digest("hex");
46396
+ return createHash22("sha256").update(bytes2).digest("hex");
45559
46397
  }
45560
46398
  function computeContentHash(entries) {
45561
46399
  const input = packageHashInput(entries.map((e) => ({ path: e.path, hash: sha256Hex4(e.bytes) })));
45562
- return "sha256-" + createHash21("sha256").update(input).digest("base64");
46400
+ return "sha256-" + createHash22("sha256").update(input).digest("base64");
45563
46401
  }
45564
46402
  function buildEntries(appDir2, app, opts) {
45565
46403
  const { entries: released, manifest, skipped } = gatherReleaseEntries(appDir2);
@@ -47768,10 +48606,10 @@ var init_calls = __esm({
47768
48606
  });
47769
48607
 
47770
48608
  // ../../core/functions-runtime/src/queue.ts
47771
- import { createHash as createHash22 } from "node:crypto";
48609
+ import { createHash as createHash23 } from "node:crypto";
47772
48610
  function inputSchemaHash(definition) {
47773
48611
  const schema = definition.input;
47774
- return schema ? createHash22("sha256").update(JSON.stringify(schema.descriptor ?? { kind: schema.kind, optional: schema.optional === true })).digest("hex") : "";
48612
+ return schema ? createHash23("sha256").update(JSON.stringify(schema.descriptor ?? { kind: schema.kind, optional: schema.optional === true })).digest("hex") : "";
47775
48613
  }
47776
48614
  function createFunctionEnqueueCapability(options) {
47777
48615
  return {
@@ -47837,14 +48675,14 @@ var init_queue = __esm({
47837
48675
  });
47838
48676
 
47839
48677
  // ../../core/data-resources/src/plan.ts
47840
- import { createHash as createHash23 } from "node:crypto";
48678
+ import { createHash as createHash24 } from "node:crypto";
47841
48679
  function canonical2(value) {
47842
48680
  if (Array.isArray(value)) return `[${value.map(canonical2).join(",")}]`;
47843
48681
  if (value && typeof value === "object") return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonical2(item)}`).join(",")}}`;
47844
48682
  return JSON.stringify(value);
47845
48683
  }
47846
48684
  function hashResourcePlan(plan) {
47847
- return createHash23("sha256").update(canonical2(plan)).digest("hex");
48685
+ return createHash24("sha256").update(canonical2(plan)).digest("hex");
47848
48686
  }
47849
48687
  function finalize(input) {
47850
48688
  return { ...input, hash: hashResourcePlan(input) };
@@ -48780,7 +49618,7 @@ __export(studio_reel_exports, {
48780
49618
  thin: () => thin,
48781
49619
  utcDay: () => utcDay
48782
49620
  });
48783
- import { createHash as createHash24 } from "node:crypto";
49621
+ import { createHash as createHash25 } from "node:crypto";
48784
49622
  import { lstatSync as lstatSync9, readFileSync as readFileSync55 } from "node:fs";
48785
49623
  import { basename as basename12, join as join49 } from "node:path";
48786
49624
  function loadMemory2(dir) {
@@ -48868,7 +49706,7 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
48868
49706
  const st = lstatSync9(path2);
48869
49707
  if (!st.isFile() || st.nlink !== 1) return null;
48870
49708
  const bytes2 = readFileSync55(path2);
48871
- return createHash24("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
49709
+ return createHash25("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
48872
49710
  } catch {
48873
49711
  return null;
48874
49712
  }
@@ -49288,7 +50126,7 @@ __export(browser_authoring_exports, {
49288
50126
  sdkSubpathReader: () => sdkSubpathReader
49289
50127
  });
49290
50128
  import { createServer as createServer3 } from "node:http";
49291
- import { createHash as createHash25, randomBytes as randomBytes4 } from "node:crypto";
50129
+ import { createHash as createHash26, randomBytes as randomBytes4 } from "node:crypto";
49292
50130
  import { existsSync as existsSync52, readdirSync as readdirSync26, readFileSync as readFileSync57, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
49293
50131
  import { join as join51, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
49294
50132
  function humanMs(ms) {
@@ -49387,7 +50225,7 @@ function encodeJob(job) {
49387
50225
  return Buffer.from(JSON.stringify(job), "utf8").toString("base64");
49388
50226
  }
49389
50227
  function mintToken2(payload, nonce = randomBytes4(16).toString("hex")) {
49390
- const digest2 = createHash25("sha256").update(payload, "utf8").digest("base64url");
50228
+ const digest2 = createHash26("sha256").update(payload, "utf8").digest("base64url");
49391
50229
  return `${nonce}.${digest2}`;
49392
50230
  }
49393
50231
  function defaultOpenBrowser2(url) {
@@ -50310,7 +51148,7 @@ function makeServeHandler(opts) {
50310
51148
  }
50311
51149
  const PINNED_MANIFEST_CACHE_CAP = 100;
50312
51150
  const pinnedManifestCache = /* @__PURE__ */ new Map();
50313
- const sha256hex = (b) => sha256Hex(b);
51151
+ const sha256hex2 = (b) => sha256Hex(b);
50314
51152
  function resolvePin(request, url) {
50315
51153
  return request.headers.get("x-himalaya-release") || url.searchParams.get("release") || null;
50316
51154
  }
@@ -50363,7 +51201,7 @@ ${releaseId}`;
50363
51201
  const doc = store.getDoc ? await store.getDoc(app, hash) : null;
50364
51202
  if (doc) return doc;
50365
51203
  const bytes2 = await slot();
50366
- return bytes2 && sha256hex(bytes2) === hash ? bytes2 : null;
51204
+ return bytes2 && sha256hex2(bytes2) === hash ? bytes2 : null;
50367
51205
  }
50368
51206
  function callbackUrlFor(url) {
50369
51207
  const base = ((typeof opts.publicBaseUrl === "function" ? opts.publicBaseUrl(url) : opts.publicBaseUrl) ?? url.origin).replace(/\/$/, "");
@@ -51027,7 +51865,7 @@ ${releaseId}`;
51027
51865
  }
51028
51866
  }
51029
51867
  }
51030
- const cfgTag = ringPinned ? sha256hex(JSON.stringify(appConfig)) : mm.appConfigHash ?? "";
51868
+ const cfgTag = ringPinned ? sha256hex2(JSON.stringify(appConfig)) : mm.appConfigHash ?? "";
51031
51869
  const etag = strongEtag(`${mm.releaseId}:${cfgTag}:${mm.tokensHash ?? ""}:${screenHash}:${homeBundle?.sha256 ?? ""}`);
51032
51870
  const cache = pin && !ringPinned ? PINNED_CACHE_CONTROL : DOC_CACHE_CONTROL;
51033
51871
  const vary = pin ? { vary: ringPinned ? `x-himalaya-release, ${RELEASE_SOURCE_HEADER}` : "x-himalaya-release" } : {};
@@ -51630,21 +52468,21 @@ var init_preview_assets_generated = __esm({
51630
52468
  "../../packages/serve/src/preview-assets.generated.ts"() {
51631
52469
  init_preview_assets_format();
51632
52470
  PREVIEW_ASSET_MANIFEST = [
51633
- { key: "assets/index-BpZ7D4Bh.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1058524, sha256: "cc7f9edeb0a351f0d1fbc18d622044afaf3112c567893f04135a5cf846e2c4d2" },
51634
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1058532, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
51635
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1063142, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
51636
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1551726, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
51637
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1642650, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
51638
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1733634, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
51639
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1824614, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
51640
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1915486, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
51641
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2006466, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
51642
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2115102, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
51643
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2224186, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
51644
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2333234, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
51645
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2440934, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
51646
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2529042, length: 4947, sha256: "bed413299e95d197f054aae695914a18403bef8f893a8ed89a87068cbd0cab80" },
51647
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2533989, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
52471
+ { key: "assets/index-zgMUMszy.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1058954, sha256: "f7d0a27ec7c0241f1ab3bbda5a83b3d0195f95c0cefbda2125289ce966b6f2da" },
52472
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1058962, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
52473
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1063572, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
52474
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1552156, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
52475
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1643080, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
52476
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1734064, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
52477
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1825044, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
52478
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1915916, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
52479
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2006896, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
52480
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2115532, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
52481
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2224616, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
52482
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2333664, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
52483
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2441364, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
52484
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2529472, length: 4947, sha256: "b2015f4f6ddcb285a96284836207d8e60d0bf4de727b072c4a3b48e919f5067a" },
52485
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2534419, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
51648
52486
  ];
51649
52487
  }
51650
52488
  });
@@ -52774,7 +53612,7 @@ function loadStoreConfig(app) {
52774
53612
  }
52775
53613
  var init_preflight = __esm({
52776
53614
  "../store-release/preflight.ts"() {
52777
- init_paths();
53615
+ init_paths2();
52778
53616
  }
52779
53617
  });
52780
53618
 
@@ -52914,7 +53752,7 @@ __export(preflight_app_exports, {
52914
53752
  profileAccount: () => profileAccount
52915
53753
  });
52916
53754
  import { readFileSync as readFileSync66, existsSync as existsSync60, readdirSync as readdirSync28, statSync as statSync15 } from "node:fs";
52917
- import { createHash as createHash26 } from "node:crypto";
53755
+ import { createHash as createHash27 } from "node:crypto";
52918
53756
  import { join as join57 } from "node:path";
52919
53757
  function analyze(projectYml, storeConfig, icon, account) {
52920
53758
  const f = [];
@@ -53203,7 +54041,7 @@ function placeholderHashes2() {
53203
54041
  const p = join57(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
53204
54042
  try {
53205
54043
  if (!statSync15(p).isFile()) continue;
53206
- return [LEGACY_PLACEHOLDER_SHA2562, createHash26("sha256").update(readFileSync66(p)).digest("hex")];
54044
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync66(p)).digest("hex")];
53207
54045
  } catch {
53208
54046
  }
53209
54047
  }
@@ -53240,7 +54078,7 @@ async function appIconState(iosDir) {
53240
54078
  try {
53241
54079
  if (!statSync15(p).isFile()) continue;
53242
54080
  const bytes2 = readFileSync66(p);
53243
- const hash = createHash26("sha256").update(bytes2).digest("hex");
54081
+ const hash = createHash27("sha256").update(bytes2).digest("hex");
53244
54082
  const pixels = await pixelIssues(bytes2);
53245
54083
  return {
53246
54084
  exists: true,
@@ -53275,7 +54113,7 @@ ${app}: ${errors.length} error(s), ${warns.length} warning(s).`);
53275
54113
  var APP_STORE_SIZES, PNG_COLOR_TYPE_RGB, PLAY_FEATURE_GRAPHIC, LEGACY_PLACEHOLDER_SHA2562;
53276
54114
  var init_preflight_app = __esm({
53277
54115
  "../store-release/preflight-app.ts"() {
53278
- init_paths();
54116
+ init_paths2();
53279
54117
  init_preflight();
53280
54118
  init_credential_profile();
53281
54119
  init_app_icon_rules();
@@ -53382,7 +54220,7 @@ function bumpVersion(app, opts) {
53382
54220
  }
53383
54221
  var init_version = __esm({
53384
54222
  "../store-release/version.ts"() {
53385
- init_paths();
54223
+ init_paths2();
53386
54224
  }
53387
54225
  });
53388
54226
 
@@ -53441,7 +54279,7 @@ async function main2() {
53441
54279
  var ASC_API;
53442
54280
  var init_register_bundle = __esm({
53443
54281
  "../store-release/register-bundle.ts"() {
53444
- init_paths();
54282
+ init_paths2();
53445
54283
  init_preflight();
53446
54284
  ASC_API = "https://api.appstoreconnect.apple.com";
53447
54285
  if (runningAsScript("store-release/register-bundle.ts")) {
@@ -53506,7 +54344,7 @@ async function main3() {
53506
54344
  var ASC;
53507
54345
  var init_asc_status = __esm({
53508
54346
  "../store-release/asc-status.ts"() {
53509
- init_paths();
54347
+ init_paths2();
53510
54348
  init_register_bundle();
53511
54349
  init_preflight();
53512
54350
  ASC = "https://api.appstoreconnect.apple.com";
@@ -63446,7 +64284,7 @@ var init_context = __esm({
63446
64284
  // src/cli.ts
63447
64285
  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";
63448
64286
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
63449
- import { createHash as createHash27, randomUUID as randomUUID5 } from "node:crypto";
64287
+ import { createHash as createHash28, randomUUID as randomUUID5 } from "node:crypto";
63450
64288
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
63451
64289
  import { join as join61, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
63452
64290
  import { fileURLToPath as fileURLToPath24 } from "node:url";
@@ -64046,743 +64884,7 @@ init_store();
64046
64884
  init_contract();
64047
64885
  init_app_link();
64048
64886
  init_himi_report();
64049
-
64050
- // src/attestation.ts
64051
- import { execFileSync as execFileSync2 } from "node:child_process";
64052
- import { createHash as createHash3, randomBytes } from "node:crypto";
64053
- import {
64054
- existsSync as existsSync3,
64055
- lstatSync as lstatSync3,
64056
- mkdirSync as mkdirSync5,
64057
- realpathSync,
64058
- readlinkSync as readlinkSync2,
64059
- readFileSync as readFileSync5,
64060
- readdirSync as readdirSync3,
64061
- unlinkSync,
64062
- writeFileSync as writeFileSync5
64063
- } from "node:fs";
64064
- import { basename as basename2, join as join6, relative, resolve as resolve6 } from "node:path";
64065
-
64066
- // ../verify-gate/diff-hash.mjs
64067
- import {
64068
- lstatSync as lstatSync2,
64069
- mkdirSync as mkdirSync4,
64070
- mkdtempSync,
64071
- readFileSync as readFileSync4,
64072
- readlinkSync,
64073
- rmSync as rmSync2,
64074
- writeFileSync as writeFileSync4
64075
- } from "node:fs";
64076
- import { createHash as createHash2 } from "node:crypto";
64077
- import { tmpdir } from "node:os";
64078
- import { basename, dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
64079
- import { pathToFileURL } from "node:url";
64080
-
64081
- // ../pre-pr-review/scope.mjs
64082
- import { execFileSync } from "node:child_process";
64083
- import { createHash } from "node:crypto";
64084
- import { resolve as resolve4, dirname as dirname4, isAbsolute, join as join4 } from "node:path";
64085
- import { fileURLToPath as fileURLToPath3 } from "node:url";
64086
-
64087
- // ../ai-review-gate/paths.mjs
64088
- import { resolve as resolve3, dirname as dirname3 } from "node:path";
64089
- import { fileURLToPath as fileURLToPath2 } from "node:url";
64090
- var REPO_ROOT = resolve3(dirname3(fileURLToPath2(import.meta.url)), "../..");
64091
-
64092
- // ../pre-pr-review/command.mjs
64093
- var SHELL_NAMES = "sh|bash|zsh|ksh|dash|ash|fish|csh|tcsh|pwsh|xonsh|nu";
64094
- var SHELL_TOKEN = new RegExp(`^(?:.*/)?(?:${SHELL_NAMES})$`);
64095
-
64096
- // ../pre-pr-review/scope.mjs
64097
- var REPO_ROOT2 = resolve4(dirname4(fileURLToPath3(import.meta.url)), "../..");
64098
- var BASE_REF = process.env.PRE_PR_REVIEW_BASE || "origin/master";
64099
- function resolveRepoRoot(cwd = process.cwd()) {
64100
- let top;
64101
- try {
64102
- top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
64103
- cwd,
64104
- encoding: "utf8",
64105
- stdio: ["ignore", "pipe", "pipe"]
64106
- }).toString().trim();
64107
- } catch (err) {
64108
- throw new Error(`cannot resolve a repo root from ${cwd}: ${err?.message || err}`);
64109
- }
64110
- if (!top) throw new Error(`cannot resolve a repo root from ${cwd}: git named no toplevel`);
64111
- return top;
64112
- }
64113
- function git(args, opts = {}) {
64114
- return execFileSync("git", args, {
64115
- cwd: opts.cwd || REPO_ROOT2,
64116
- encoding: "utf8",
64117
- // A full PR diff can be megabytes; the default 1MB cap would truncate mid-hunk and the
64118
- // delta parser would then read a malformed diff as "unsupported shape".
64119
- maxBuffer: opts.maxBuffer ?? 256 * 1024 * 1024,
64120
- stdio: ["ignore", "pipe", "pipe"]
64121
- }).toString();
64122
- }
64123
- function diffHash(diff) {
64124
- return createHash("sha256").update(diff).digest("hex");
64125
- }
64126
-
64127
- // ../verify-gate/diff-hash.mjs
64128
- var VALIDATION_METADATA = /* @__PURE__ */ new Set([
64129
- ".claude/.validation-baseline.json",
64130
- ".claude/.validation-evidence.json"
64131
- ]);
64132
- function isValidationMetadata(path2) {
64133
- return VALIDATION_METADATA.has(path2) || /(^|[\\/])\.himi([\\/]|$)/.test(path2);
64134
- }
64135
- function tryGit(args, root) {
64136
- try {
64137
- return git(args, { cwd: root });
64138
- } catch {
64139
- return null;
64140
- }
64141
- }
64142
- function committedRecords(raw) {
64143
- const fields = raw.split("\0");
64144
- if (fields.length > 0 && fields[fields.length - 1] === "") fields.pop();
64145
- if (fields.length % 2 !== 0) return null;
64146
- const records = [];
64147
- for (let i = 0; i < fields.length; i += 2) {
64148
- const metadata2 = fields[i];
64149
- const path2 = fields[i + 1];
64150
- if (!metadata2.startsWith(":") || path2 === "") return null;
64151
- if (isValidationMetadata(path2)) continue;
64152
- records.push(`${Buffer.byteLength(path2)}:${path2}\0${metadata2}\0`);
64153
- }
64154
- return records.sort().join("");
64155
- }
64156
- function committedIdentity(root) {
64157
- const head = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root)?.trim();
64158
- if (!head) return "unborn\0";
64159
- const base = tryGit(["merge-base", BASE_REF, "HEAD"], root)?.trim();
64160
- if (base) {
64161
- const raw = tryGit(["diff", "--raw", "--no-renames", "-z", "--no-abbrev", base, "HEAD"], root);
64162
- const records = raw === null ? null : committedRecords(raw);
64163
- if (records !== null) return `base\0${base}\0${records}`;
64164
- }
64165
- const listing = tryGit(["ls-tree", "-rz", "--full-tree", "HEAD"], root);
64166
- const tree = listing === null ? null : treeRecords(listing);
64167
- return tree !== null ? `tree-listing\0${tree}` : `commit\0${head}\0`;
64168
- }
64169
- function treeRecords(listing) {
64170
- const records = [];
64171
- for (const record2 of listing.split("\0")) {
64172
- if (record2 === "") continue;
64173
- const tab = record2.indexOf(" ");
64174
- if (tab <= 0 || tab === record2.length - 1) return null;
64175
- const path2 = record2.slice(tab + 1);
64176
- if (isValidationMetadata(path2)) continue;
64177
- records.push(`${Buffer.byteLength(path2)}:${path2}\0${record2.slice(0, tab)}\0`);
64178
- }
64179
- return records.sort().join("");
64180
- }
64181
- function treeEntries(output, index = false) {
64182
- const entries = /* @__PURE__ */ new Map();
64183
- for (const record2 of output.split("\0").filter(Boolean)) {
64184
- const tab = record2.indexOf(" ");
64185
- if (tab === -1) continue;
64186
- const metadata2 = record2.slice(0, tab).split(" ");
64187
- if (index && metadata2[2] !== "0") continue;
64188
- entries.set(record2.slice(tab + 1), {
64189
- mode: metadata2[0],
64190
- oid: index ? metadata2[1] : metadata2[2]
64191
- });
64192
- }
64193
- return entries;
64194
- }
64195
- function sameEntry(left, right) {
64196
- if (!left || !right) return left === right;
64197
- return left.mode === right.mode && left.oid === right.oid;
64198
- }
64199
- function stagedIdentity(head, index, worktree) {
64200
- if (sameEntry(index, head) || sameEntry(index, worktree)) return "";
64201
- if (!index) return head ? "deleted" : "";
64202
- return `${index.mode}:${index.oid}`;
64203
- }
64204
- function blobOid(contents, algorithm) {
64205
- const body = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
64206
- return createHash2(algorithm).update(Buffer.from(`blob ${body.byteLength}\0`)).update(body).digest("hex");
64207
- }
64208
- function validationDiffHash(cwd = process.cwd()) {
64209
- const root = resolveRepoRoot(cwd);
64210
- const born = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root) !== null;
64211
- const staged = git(["diff", "--cached", "--name-only", "--no-renames", "-z", ...born ? ["HEAD"] : []], {
64212
- cwd: root
64213
- }).split("\0").filter(Boolean);
64214
- const unstaged = git(["diff", "--name-only", "--no-renames", "-z"], { cwd: root }).split("\0").filter(Boolean);
64215
- const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], {
64216
- cwd: root
64217
- }).split("\0").filter(Boolean);
64218
- const headEntries = born ? treeEntries(git(["ls-tree", "-rz", "--full-tree", "HEAD"], { cwd: root })) : /* @__PURE__ */ new Map();
64219
- const indexEntries = treeEntries(git(["ls-files", "--stage", "-z"], { cwd: root }), true);
64220
- const objectAlgorithm = git(["rev-parse", "--show-object-format"], { cwd: root }).trim();
64221
- let trustFilesystemMode = true;
64222
- try {
64223
- trustFilesystemMode = git(["config", "--bool", "--get", "core.fileMode"], { cwd: root }).trim() !== "false";
64224
- } catch {
64225
- }
64226
- const paths = [.../* @__PURE__ */ new Set([...staged, ...unstaged, ...untracked])].filter((path2) => !isValidationMetadata(path2)).sort();
64227
- const identity = paths.map((path2) => {
64228
- const absolute = resolve5(root, path2);
64229
- const headEntry = headEntries.get(path2);
64230
- const indexEntry = indexEntries.get(path2);
64231
- const deletedRecord = () => [`${Buffer.byteLength(path2)}:${path2}`, "deleted", stagedIdentity(headEntry, indexEntry, void 0), ""].join("\0");
64232
- let stat2;
64233
- try {
64234
- stat2 = lstatSync2(absolute);
64235
- } catch (error) {
64236
- if (error?.code === "ENOENT") return deletedRecord();
64237
- throw error;
64238
- }
64239
- try {
64240
- if (stat2.isSymbolicLink()) {
64241
- const target = readlinkSync(absolute);
64242
- const worktreeEntry = { mode: "120000", oid: blobOid(target, objectAlgorithm) };
64243
- const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
64244
- return [
64245
- `${Buffer.byteLength(path2)}:${path2}`,
64246
- "symlink",
64247
- "120000",
64248
- diffHash(target),
64249
- stagedState2,
64250
- ""
64251
- ].join("\0");
64252
- }
64253
- if (stat2.isFile()) {
64254
- const filesystemMode = stat2.mode & 73 ? "100755" : "100644";
64255
- const worktreeMode = trustFilesystemMode ? filesystemMode : indexEntry?.mode ?? headEntry?.mode ?? "100644";
64256
- const worktreeOid = git(["hash-object", `--path=${path2}`, "--", path2], { cwd: root }).trim();
64257
- const worktreeEntry = { mode: worktreeMode, oid: worktreeOid };
64258
- const stagedState2 = stagedIdentity(headEntry, indexEntry, worktreeEntry);
64259
- return [
64260
- `${Buffer.byteLength(path2)}:${path2}`,
64261
- "file",
64262
- worktreeMode,
64263
- diffHash(readFileSync4(absolute)),
64264
- stagedState2,
64265
- ""
64266
- ].join("\0");
64267
- }
64268
- } catch (error) {
64269
- try {
64270
- lstatSync2(absolute);
64271
- } catch (recheck) {
64272
- if (recheck?.code === "ENOENT") return deletedRecord();
64273
- }
64274
- throw error;
64275
- }
64276
- const type = stat2.isDirectory() ? "directory" : "other";
64277
- const stagedState = stagedIdentity(headEntry, indexEntry, void 0);
64278
- return [
64279
- `${Buffer.byteLength(path2)}:${path2}`,
64280
- type,
64281
- stat2.mode.toString(8),
64282
- stagedState,
64283
- ""
64284
- ].join("\0");
64285
- }).join("");
64286
- const committed = committedIdentity(root);
64287
- return diffHash(
64288
- `validation-change-v3\0${Buffer.byteLength(committed)}\0${committed}${identity}`
64289
- );
64290
- }
64291
- function seed(repo, commands) {
64292
- for (const args of commands) git(args, { cwd: repo });
64293
- }
64294
- function write(repo, rel, body) {
64295
- const absolute = resolve5(repo, rel);
64296
- mkdirSync4(dirname5(absolute), { recursive: true });
64297
- writeFileSync4(absolute, body);
64298
- }
64299
- function selfTestRepo({ withBase }) {
64300
- const repo = mkdtempSync(join5(tmpdir(), "diff-hash-self-test-"));
64301
- seed(repo, [
64302
- ["init", "-q", repo],
64303
- ["-C", repo, "config", "user.email", "self-test@test.invalid"],
64304
- ["-C", repo, "config", "user.name", "diff-hash-self-test"]
64305
- ]);
64306
- write(repo, "src/base.txt", "base\n");
64307
- seed(repo, [["-C", repo, "add", "-A"], ["-C", repo, "commit", "-qm", "base"]]);
64308
- const base = git(["-C", repo, "rev-parse", "HEAD"], { cwd: repo }).trim();
64309
- if (withBase) seed(repo, [["-C", repo, "update-ref", "refs/remotes/origin/master", base]]);
64310
- return { repo, base };
64311
- }
64312
- function commitChange(repo, base, branch, rel, body) {
64313
- seed(repo, [["-C", repo, "checkout", "-q", base], ["-C", repo, "checkout", "-q", "-b", branch]]);
64314
- write(repo, rel, body);
64315
- seed(repo, [["-C", repo, "add", rel], ["-C", repo, "commit", "-qm", `commit ${rel}`]]);
64316
- return validationDiffHash(repo);
64317
- }
64318
- var SELF_TEST_CASES = [
64319
- {
64320
- name: "committed diffs are distinguished (the #1831 regression)",
64321
- reason: "two unrelated COMMITTED diffs share one identity \u2014 a record for either would satisfy the gate for the other",
64322
- run: () => {
64323
- const { repo, base } = selfTestRepo({ withBase: true });
64324
- try {
64325
- return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
64326
- } finally {
64327
- rmSync2(repo, { recursive: true, force: true });
64328
- }
64329
- }
64330
- },
64331
- {
64332
- name: "committed diffs are distinguished with NO base ref (fallback ladder)",
64333
- reason: "without origin/master the committed identity collapses to a constant",
64334
- run: () => {
64335
- const { repo, base } = selfTestRepo({ withBase: false });
64336
- try {
64337
- return commitChange(repo, base, "a", "src/alpha.txt", "alpha\n") !== commitChange(repo, base, "b", "src/beta.txt", "a different change\n");
64338
- } finally {
64339
- rmSync2(repo, { recursive: true, force: true });
64340
- }
64341
- }
64342
- },
64343
- {
64344
- name: "a clean tree is not the digest of an empty path list",
64345
- reason: 'a clean tree still hashes to sha256("validation-worktree-v2\\0"), the constant measured in #1831',
64346
- run: () => {
64347
- const { repo } = selfTestRepo({ withBase: true });
64348
- try {
64349
- return validationDiffHash(repo) !== createHash2("sha256").update("validation-worktree-v2\0").digest("hex");
64350
- } finally {
64351
- rmSync2(repo, { recursive: true, force: true });
64352
- }
64353
- }
64354
- },
64355
- {
64356
- name: "an uncommitted edit still changes the identity",
64357
- reason: "the working-tree half of the identity stopped responding to edits",
64358
- run: () => {
64359
- const { repo } = selfTestRepo({ withBase: true });
64360
- try {
64361
- const before = validationDiffHash(repo);
64362
- write(repo, "src/base.txt", "edited in the working tree\n");
64363
- return validationDiffHash(repo) !== before;
64364
- } finally {
64365
- rmSync2(repo, { recursive: true, force: true });
64366
- }
64367
- }
64368
- },
64369
- {
64370
- // The "can pass" half. Without it every case above is satisfied by a hash that simply
64371
- // changes every call, which would identify nothing and invalidate every record.
64372
- name: "one unchanged state hashes to one value",
64373
- reason: "the identity is not stable for an unchanged state \u2014 every record would read stale",
64374
- run: () => {
64375
- const { repo } = selfTestRepo({ withBase: true });
64376
- try {
64377
- write(repo, "src/base.txt", "edited in the working tree\n");
64378
- return validationDiffHash(repo) === validationDiffHash(repo);
64379
- } finally {
64380
- rmSync2(repo, { recursive: true, force: true });
64381
- }
64382
- }
64383
- }
64384
- ];
64385
- function runSelfTest() {
64386
- let failed2 = 0;
64387
- for (const testCase of SELF_TEST_CASES) {
64388
- let ok = false;
64389
- let thrown = null;
64390
- try {
64391
- ok = testCase.run() === true;
64392
- } catch (error) {
64393
- thrown = error?.message || String(error);
64394
- }
64395
- if (ok) {
64396
- process.stdout.write(` \u2713 ${testCase.name}
64397
- `);
64398
- continue;
64399
- }
64400
- failed2++;
64401
- process.stderr.write(` \u2717 ${testCase.name}
64402
- ${thrown ?? testCase.reason}
64403
- `);
64404
- }
64405
- return failed2;
64406
- }
64407
- if (process.argv[1] && basename(process.argv[1]) === "diff-hash.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) {
64408
- if (process.argv.slice(2).includes("--self-test")) {
64409
- process.stdout.write("\u25B8 diff-hash --self-test: proving the identity can change, and can hold\n");
64410
- const failed2 = runSelfTest();
64411
- if (failed2) {
64412
- process.stderr.write(
64413
- `\u2717 diff-hash self-test FAILED (${failed2} case(s)) \u2014 the evidence record's diff identity cannot be trusted
64414
- `
64415
- );
64416
- process.exit(1);
64417
- }
64418
- process.stdout.write(`\u2713 diff-hash self-test passed (${SELF_TEST_CASES.length} cases)
64419
- `);
64420
- } else {
64421
- process.stdout.write(`${validationDiffHash()}
64422
- `);
64423
- }
64424
- }
64425
-
64426
- // src/attestation.ts
64427
- init_source_deny();
64428
- init_widget_identity();
64429
- var DEFAULT_MAX_RUNS = 20;
64430
- var ATTESTATION_VERSION = 2;
64431
- function attestationVersionOf(value) {
64432
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
64433
- const version2 = value.attestation;
64434
- return typeof version2 === "number" && Number.isInteger(version2) && version2 > 0 ? version2 : null;
64435
- }
64436
- function classifyRetainedRun(dir, name) {
64437
- let value;
64438
- try {
64439
- value = JSON.parse(readFileSync5(join6(dir, name), "utf8"));
64440
- } catch {
64441
- return { kind: "unreadable" };
64442
- }
64443
- if (isAttestationV2(value)) return { kind: "current", run: value };
64444
- const version2 = attestationVersionOf(value);
64445
- if (version2 === null || version2 === ATTESTATION_VERSION) return { kind: "malformed" };
64446
- return { kind: version2 < ATTESTATION_VERSION ? "legacy" : "future" };
64447
- }
64448
- function screenshotArtifactPath(contentDir2, hash) {
64449
- return join6(resolve6(contentDir2), ".himi", "attestations", "artifacts", `${hash}.png`);
64450
- }
64451
- var GIT_HASH_MAX_BUFFER = 128 * 1024 * 1024;
64452
- var sha2562 = (bytes2) => createHash3("sha256").update(bytes2).digest("hex");
64453
- function canonical(value) {
64454
- if (value === null || typeof value !== "object") return JSON.stringify(value);
64455
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
64456
- const record2 = value;
64457
- return `{${Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${canonical(record2[key2])}`).join(",")}}`;
64458
- }
64459
- function releasePayloadHash(payload) {
64460
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) return sha2562(canonical(payload));
64461
- const record2 = payload;
64462
- const manifest = record2.manifest;
64463
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return sha2562(canonical(payload));
64464
- const manifestRecord = manifest;
64465
- const widgets = manifestRecord.widgets;
64466
- if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return sha2562(canonical(payload));
64467
- return sha2562(canonical({ ...record2, manifest: { ...manifestRecord, widgets: stableWidgets(widgets) } }));
64468
- }
64469
- function withoutSelfHash(attestation) {
64470
- const { selfHash: _selfHash, ...body } = attestation;
64471
- return body;
64472
- }
64473
- function selfHashFor(attestation) {
64474
- return sha2562(canonical(withoutSelfHash(attestation)));
64475
- }
64476
- function repoRoot(contentDir2) {
64477
- try {
64478
- const root = execFileSync2("git", ["-C", contentDir2, "rev-parse", "--show-toplevel"], {
64479
- encoding: "utf8",
64480
- stdio: ["ignore", "pipe", "ignore"]
64481
- }).trim();
64482
- if (!root) return null;
64483
- execFileSync2("git", ["-C", root, "rev-parse", "--verify", "--quiet", "HEAD^{commit}"], { stdio: "ignore" });
64484
- return root;
64485
- } catch {
64486
- return null;
64487
- }
64488
- }
64489
- function storedContentDir(contentDir2) {
64490
- const absolute = resolve6(contentDir2);
64491
- const root = repoRoot(absolute);
64492
- return root ? relative(root, absolute) || "." : ".";
64493
- }
64494
- function currentGitDiffHash(contentDir2) {
64495
- const root = repoRoot(resolve6(contentDir2));
64496
- if (!root) return "unavailable";
64497
- return validationDiffHash(root);
64498
- }
64499
- function currentGitUntrackedHash(contentDir2) {
64500
- const unresolved = resolve6(contentDir2);
64501
- const absolute = realpathSync.native(unresolved);
64502
- const root = repoRoot(absolute);
64503
- if (!root) return sourceTreeHash(absolute);
64504
- const scope = relative(root, absolute) || ".";
64505
- const listed = execFileSync2("git", ["-C", root, "ls-files", "--others", "--exclude-standard", "-z", "--", scope], {
64506
- encoding: "utf8",
64507
- maxBuffer: GIT_HASH_MAX_BUFFER
64508
- });
64509
- const hash = createHash3("sha256");
64510
- for (const path2 of listed.split("\0").filter(Boolean).sort()) {
64511
- const local = relative(absolute, join6(root, path2));
64512
- const segs = local.split(/[\\/]/);
64513
- if (NOT_ATTESTED_DIRS.has(segs[0])) continue;
64514
- if (segs.length === 1 && AGENT_TOOLING_ROOT_FILES.includes(segs[0])) continue;
64515
- const full = join6(root, path2);
64516
- const stats = lstatSync3(full, { throwIfNoEntry: false });
64517
- if (!stats) continue;
64518
- const kind = stats.isSymbolicLink() ? "link" : stats.isDirectory() ? "dir" : stats.isFile() ? "file" : null;
64519
- if (!kind) continue;
64520
- const payload = kind === "link" ? Buffer.from(readlinkSync2(full)) : kind === "file" ? readFileSync5(full) : Buffer.alloc(0);
64521
- const pathBytes = Buffer.from(path2);
64522
- hash.update(`${kind} ${pathBytes.length} ${payload.length}
64523
- `).update(pathBytes).update(payload);
64524
- }
64525
- return hash.digest("hex");
64526
- }
64527
- var NOT_ATTESTED_DIRS = /* @__PURE__ */ new Set([".himi", ...AGENT_TOOLING_DIRS]);
64528
- function sourceTreeHash(root) {
64529
- const hash = createHash3("sha256");
64530
- const visit = (dir) => {
64531
- for (const entry of readdirSync3(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
64532
- if (entry.isDirectory() && (entry.name === ".git" || entry.name === "node_modules" || NOT_ATTESTED_DIRS.has(entry.name))) continue;
64533
- const path2 = join6(dir, entry.name);
64534
- const local = relative(root, path2);
64535
- if (dir === root && entry.isFile() && AGENT_TOOLING_ROOT_FILES.includes(entry.name)) continue;
64536
- if (entry.isDirectory()) visit(path2);
64537
- else if (entry.isFile()) hash.update(local).update("\0").update(readFileSync5(path2)).update("\0");
64538
- else if (entry.isSymbolicLink()) hash.update(local).update("\0link\0").update(readlinkSync2(path2)).update("\0");
64539
- }
64540
- };
64541
- visit(root);
64542
- return hash.digest("hex");
64543
- }
64544
- var stringRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
64545
- function isAttestationV2(value) {
64546
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
64547
- const a = value;
64548
- const coverage = a.coverage;
64549
- const report2 = a.report;
64550
- 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");
64551
- }
64552
- function readLatestAttestation(contentDir2) {
64553
- try {
64554
- const parsed = JSON.parse(readFileSync5(join6(resolve6(contentDir2), ".himi", "attestations", "latest.json"), "utf8"));
64555
- return isAttestationV2(parsed) ? parsed : null;
64556
- } catch {
64557
- return null;
64558
- }
64559
- }
64560
- function readRetainedAttestations(contentDir2) {
64561
- const dir = join6(resolve6(contentDir2), ".himi", "attestations");
64562
- if (!existsSync3(dir)) return { runs: [], errors: [] };
64563
- try {
64564
- const names = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
64565
- const parsedRuns = [];
64566
- const errors = [];
64567
- for (const name of names) {
64568
- const classified = classifyRetainedRun(dir, name);
64569
- if (classified.kind === "current") parsedRuns.push(classified.run);
64570
- else if (classified.kind === "legacy") {
64571
- 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`);
64572
- } else if (classified.kind === "future") {
64573
- errors.push(`attestation history contains retained run ${name} from a newer attestation version \u2014 upgrade himi rather than deleting it`);
64574
- } else if (classified.kind === "unreadable") {
64575
- errors.push(`attestation history contains unreadable retained run ${name}`);
64576
- } else errors.push(`attestation history contains malformed retained run ${name}`);
64577
- }
64578
- const latest = readLatestAttestation(contentDir2);
64579
- if (existsSync3(join6(dir, "latest.json")) && !latest) errors.push("attestation history has no valid latest.json");
64580
- else if (names.length > 0 && !latest) errors.push("attestation history has no latest.json");
64581
- const byHash = new Map(parsedRuns.map((run2) => [run2.selfHash, run2]));
64582
- if (byHash.size !== parsedRuns.length) errors.push("attestation history contains duplicate self hashes");
64583
- for (const run2 of parsedRuns) {
64584
- if (selfHashFor(run2) !== run2.selfHash) errors.push(`selfHash does not match retained run ${run2.runId}`);
64585
- for (const expected of Object.values(run2.screenshots)) {
64586
- const path2 = screenshotArtifactPath(contentDir2, expected);
64587
- if (!existsSync3(path2)) errors.push(`missing screenshot for retained run ${run2.runId}: ${basename2(path2)}`);
64588
- else if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match retained run ${run2.runId}: ${basename2(path2)}`);
64589
- }
64590
- }
64591
- const runs = [];
64592
- const seen = /* @__PURE__ */ new Set();
64593
- let cursor = latest ?? void 0;
64594
- while (cursor && !seen.has(cursor.selfHash)) {
64595
- runs.push(cursor);
64596
- seen.add(cursor.selfHash);
64597
- cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
64598
- }
64599
- if (latest && !byHash.has(latest.selfHash)) errors.push("latest.json has no matching retained run");
64600
- if (seen.size !== parsedRuns.length) errors.push("attestation hash chain does not reach every retained run");
64601
- runs.push(...parsedRuns.filter((run2) => !seen.has(run2.selfHash)).sort((a, b) => a.runId < b.runId ? 1 : -1));
64602
- return {
64603
- runs,
64604
- errors
64605
- };
64606
- } catch {
64607
- return { runs: [], errors: ["attestation history could not be read"] };
64608
- }
64609
- }
64610
- function selectAttestationForPush(runs) {
64611
- const newestBehavioral = runs.find((run2) => run2.tool === "himi-test");
64612
- if (!newestBehavioral) return runs[0];
64613
- const behavioralIndex = runs.indexOf(newestBehavioral);
64614
- const newerFailure = runs.slice(0, behavioralIndex).find((run2) => run2.report.ok === false);
64615
- return newerFailure ?? newestBehavioral;
64616
- }
64617
- function pushEvidence(contentDir2, releaseId, verify) {
64618
- const retained = readRetainedAttestations(contentDir2);
64619
- const forRelease = retained.runs.filter((candidate) => candidate.releaseId === releaseId);
64620
- const verified = forRelease.filter(verify);
64621
- const selected = selectAttestationForPush(verified);
64622
- return {
64623
- ...selected ? { selected } : {},
64624
- historyErrors: retained.errors,
64625
- staleRuns: verified.length === 0 ? forRelease.length : 0
64626
- };
64627
- }
64628
- function summaryOf(attestation) {
64629
- return {
64630
- runId: attestation.runId,
64631
- selfHash: attestation.selfHash,
64632
- tool: attestation.tool,
64633
- screenIds: Object.keys(attestation.descriptorHashes).sort(),
64634
- coverage: attestation.coverage,
64635
- report: { ok: attestation.report.ok, counts: attestation.report.counts },
64636
- gitDiffHash: attestation.gitDiffHash,
64637
- releaseHash: attestation.releaseHash
64638
- };
64639
- }
64640
- function writeAttestation(input, opts = {}) {
64641
- const contentDir2 = resolve6(input.contentDir);
64642
- const dir = join6(contentDir2, ".himi", "attestations");
64643
- mkdirSync5(dir, { recursive: true });
64644
- const previous = readLatestAttestation(contentDir2);
64645
- const runId = `${(opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()}-${(opts.randomSuffix ?? (() => randomBytes(3).toString("hex")))()}`;
64646
- const descriptorHashes = Object.fromEntries(
64647
- Object.entries(input.descriptors).sort(([a], [b]) => a.localeCompare(b)).map(([screen, bytes2]) => [screen, sha2562(bytes2)])
64648
- );
64649
- const screenshotEntries = Object.entries(input.screenshots ?? {}).sort(([a], [b]) => a.localeCompare(b)).filter(([, path2]) => existsSync3(path2)).map(([screen, path2]) => {
64650
- const bytes2 = readFileSync5(path2);
64651
- return { screen, bytes: bytes2, hash: sha2562(bytes2) };
64652
- });
64653
- const screenshots = Object.fromEntries(screenshotEntries.map(({ screen, hash }) => [screen, hash]));
64654
- for (const { bytes: bytes2, hash } of screenshotEntries) {
64655
- const artifact = screenshotArtifactPath(contentDir2, hash);
64656
- mkdirSync5(join6(dir, "artifacts"), { recursive: true });
64657
- if (!existsSync3(artifact)) writeFileSync5(artifact, bytes2);
64658
- }
64659
- const unsigned = {
64660
- attestation: 2,
64661
- runId,
64662
- tool: input.tool,
64663
- app: input.app,
64664
- contentDir: storedContentDir(contentDir2),
64665
- gitDiffHash: input.gitDiffHash,
64666
- releaseId: input.releaseId,
64667
- releaseHash: releasePayloadHash(input.releasePayload),
64668
- gitUntrackedHash: currentGitUntrackedHash(contentDir2),
64669
- descriptorHashes,
64670
- screenshots,
64671
- coverage: input.coverage,
64672
- report: input.report,
64673
- prev: previous?.selfHash ?? null
64674
- };
64675
- const attestation = { ...unsigned, selfHash: "" };
64676
- attestation.selfHash = selfHashFor(attestation);
64677
- const json5 = JSON.stringify(attestation, null, 2) + "\n";
64678
- const runFile = `${runId.replace(/:/g, "-")}.json`;
64679
- writeFileSync5(join6(dir, runFile), json5);
64680
- writeFileSync5(join6(dir, "latest.json"), json5);
64681
- const keep = Math.max(1, opts.maxRuns ?? DEFAULT_MAX_RUNS);
64682
- const allPriorRuns = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").filter((name) => name !== runFile).sort().reverse();
64683
- const priorRuns = allPriorRuns.filter((name) => {
64684
- const kind = classifyRetainedRun(dir, name).kind;
64685
- if (kind === "legacy") {
64686
- unlinkSync(join6(dir, name));
64687
- return false;
64688
- }
64689
- return kind !== "future";
64690
- });
64691
- for (const name of priorRuns.slice(Math.max(0, keep - 1))) unlinkSync(join6(dir, name));
64692
- const retainedFiles = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json");
64693
- const retainedRuns = retainedFiles.map((name) => {
64694
- const classified = classifyRetainedRun(dir, name);
64695
- return classified.kind === "current" ? classified.run : null;
64696
- });
64697
- if (retainedRuns.every((run2) => run2 !== null)) {
64698
- const retainedScreenshotHashes = new Set(
64699
- retainedRuns.flatMap((run2) => Object.values(run2.screenshots))
64700
- );
64701
- const artifactDir = join6(dir, "artifacts");
64702
- if (existsSync3(artifactDir)) {
64703
- for (const name of readdirSync3(artifactDir).filter((candidate) => candidate.endsWith(".png"))) {
64704
- if (!retainedScreenshotHashes.has(name.slice(0, -4))) unlinkSync(join6(artifactDir, name));
64705
- }
64706
- }
64707
- }
64708
- return attestation;
64709
- }
64710
- function verifyAttestation(attestation, contentDir2, descriptors, expectedApp, releasePayload, opts = {}) {
64711
- const errors = [];
64712
- if (selfHashFor(attestation) !== attestation.selfHash) errors.push("selfHash does not match the attestation bytes");
64713
- try {
64714
- const dir = join6(resolve6(contentDir2), ".himi", "attestations");
64715
- const latest = readLatestAttestation(contentDir2);
64716
- if (!latest || opts.requireLatest !== false && latest.selfHash !== attestation.selfHash) {
64717
- errors.push("attestation does not match latest.json");
64718
- } else {
64719
- const classified = readdirSync3(dir).filter((name) => name.endsWith(".json") && name !== "latest.json").map((name) => classifyRetainedRun(dir, name)).filter((entry) => entry.kind !== "future");
64720
- const retained = classified.map((entry) => entry.kind === "current" ? entry.run : null);
64721
- if (retained.some((run2) => run2 === null)) {
64722
- errors.push("attestation history contains a malformed retained run");
64723
- } else {
64724
- const runs = retained;
64725
- const byHash = new Map(runs.map((run2) => [run2.selfHash, run2]));
64726
- if (!byHash.has(attestation.selfHash)) errors.push("attestation has no matching retained run");
64727
- if (!byHash.has(latest.selfHash)) {
64728
- errors.push("latest.json has no matching retained run");
64729
- } else {
64730
- const seen = /* @__PURE__ */ new Set();
64731
- let cursor = latest;
64732
- while (cursor && !seen.has(cursor.selfHash)) {
64733
- seen.add(cursor.selfHash);
64734
- if (selfHashFor(cursor) !== cursor.selfHash) {
64735
- errors.push(`selfHash does not match retained run ${cursor.runId}`);
64736
- break;
64737
- }
64738
- cursor = cursor.prev ? byHash.get(cursor.prev) : void 0;
64739
- }
64740
- if (seen.size !== runs.length) errors.push("attestation hash chain does not reach every retained run");
64741
- }
64742
- }
64743
- }
64744
- } catch {
64745
- errors.push("attestation history could not be verified");
64746
- }
64747
- if (attestation.app !== expectedApp) errors.push(`attestation app does not match ${expectedApp}`);
64748
- if (attestation.contentDir !== storedContentDir(contentDir2)) errors.push("attestation contentDir does not match the requested content directory");
64749
- if (releasePayloadHash(releasePayload) !== attestation.releaseHash) {
64750
- errors.push("releaseHash does not match the current release payload");
64751
- }
64752
- try {
64753
- const liveDiffHash = currentGitDiffHash(contentDir2);
64754
- if (liveDiffHash !== "unavailable" && liveDiffHash !== attestation.gitDiffHash) {
64755
- errors.push("gitDiffHash does not match the live git diff");
64756
- }
64757
- } catch {
64758
- errors.push("gitDiffHash could not be recomputed from the live git diff");
64759
- }
64760
- try {
64761
- const liveUntrackedHash = currentGitUntrackedHash(contentDir2);
64762
- if (liveUntrackedHash !== "unavailable" && liveUntrackedHash !== attestation.gitUntrackedHash) {
64763
- errors.push("gitUntrackedHash does not match the live untracked source bytes");
64764
- }
64765
- } catch {
64766
- errors.push("gitUntrackedHash could not be recomputed from the live untracked source bytes");
64767
- }
64768
- for (const [screen, expected] of Object.entries(attestation.descriptorHashes)) {
64769
- const bytes2 = descriptors[screen];
64770
- if (bytes2 === void 0) {
64771
- errors.push(`missing baked descriptor for ${screen}`);
64772
- continue;
64773
- }
64774
- if (sha2562(bytes2) !== expected) errors.push(`descriptor hash does not match for ${screen}`);
64775
- }
64776
- for (const [screen, expected] of Object.entries(attestation.screenshots)) {
64777
- const path2 = screenshotArtifactPath(contentDir2, expected);
64778
- if (!existsSync3(path2)) {
64779
- errors.push(`missing screenshot for ${screen}: ${basename2(path2)}`);
64780
- continue;
64781
- }
64782
- if (sha2562(readFileSync5(path2)) !== expected) errors.push(`screenshot hash does not match for ${screen}: ${basename2(path2)}`);
64783
- }
64784
- return { ok: errors.length === 0, errors };
64785
- }
64887
+ init_attestation();
64786
64888
 
64787
64889
  // ../../core/admin-web/src/utils/qr-terminal.ts
64788
64890
  init_qr();
@@ -68001,7 +68103,7 @@ function nativeConfigTokenNotice(configSource, optedIn) {
68001
68103
  }
68002
68104
  async function nativeConfigAdvisories(app, configSource) {
68003
68105
  try {
68004
- const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
68106
+ const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
68005
68107
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
68006
68108
  const iosYmlPath = iosProjectYml2(app);
68007
68109
  const iosYml = existsSync63(iosYmlPath) ? readFileSync75(iosYmlPath, "utf8") : "";
@@ -68522,7 +68624,7 @@ function uxHandoverHint(uxEvidence, ctx) {
68522
68624
  if (uxEvidence.state === "failed") {
68523
68625
  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\`.`;
68524
68626
  }
68525
- 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)` : ""}`;
68627
+ 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)` : ""}`;
68526
68628
  const drifting = ctx.nondeterministicWorkers ?? [];
68527
68629
  if (drifting.length > 0) {
68528
68630
  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\`.`;
@@ -68757,7 +68859,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
68757
68859
  };
68758
68860
  const runReleasePatch = async (opts) => {
68759
68861
  const { app, plan, draft } = opts;
68760
- const { regressionDiff: regressionDiff2, patchHonesty: patchHonesty2 } = await Promise.resolve().then(() => (init_release_patch(), release_patch_exports));
68862
+ const { regressionDiff: regressionDiff2, patchHonesty: patchHonesty2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
68761
68863
  const target = targetName();
68762
68864
  const baseFound = await fetchReleaseManifest(client2, app, opts.fromRelease);
68763
68865
  if ("error" in baseFound) {
@@ -69166,12 +69268,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69166
69268
  try {
69167
69269
  const bytes2 = readFileSync75(iconPath);
69168
69270
  payload.icon = bytes2.toString("base64");
69169
- iconHash = createHash27("sha256").update(bytes2).digest("hex");
69271
+ iconHash = createHash28("sha256").update(bytes2).digest("hex");
69170
69272
  } catch {
69171
69273
  }
69172
69274
  }
69173
69275
  payload.app = templateAppId;
69174
- payload.manifest = { ...payload.manifest, app: templateAppId, ...iconHash ? { iconHash } : {} };
69276
+ const { rebindReleaseId: rebindReleaseId2 } = await Promise.resolve().then(() => (init_build3(), build_exports2));
69277
+ payload.manifest = rebindReleaseId2({ ...payload.manifest, app: templateAppId, ...iconHash ? { iconHash } : {} });
69175
69278
  const files = sentinelizeArchive(readTemplateFiles(sourceDir), app);
69176
69279
  if (Object.keys(files).length === 0) {
69177
69280
  io.error(JSON.stringify({ error: `no source files collected from ${sourceDir}` }));
@@ -70797,7 +70900,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70797
70900
  const contentArg = shellQuote2(contentRel);
70798
70901
  if (onlyRaw !== void 0) {
70799
70902
  const onlyDraft = flags.draft === true;
70800
- const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch(), release_patch_exports));
70903
+ const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
70801
70904
  const parsed = parseOnlySelectors2(onlyRaw, {
70802
70905
  screens: Object.keys(manifest.screens ?? {}),
70803
70906
  bundles: Object.keys(manifest.bundles ?? {})
@@ -70855,7 +70958,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70855
70958
  let localSourceHash;
70856
70959
  try {
70857
70960
  const { collectAppSource: collectAppSource2, zipAppSource: zipAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
70858
- localSourceHash = createHash27("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
70961
+ localSourceHash = createHash28("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
70859
70962
  } catch {
70860
70963
  }
70861
70964
  return runReleasePatch({
@@ -70930,7 +71033,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70930
71033
  }
70931
71034
  const evidence = pushEvidence(dir, manifest.releaseId, (candidate) => verifyAttestation(candidate, dir, payload.screens, declared, payload, { requireLatest: false }).ok);
70932
71035
  const drifting = nondeterministicWorkers(outcome.workers);
70933
- const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) carry this release id, but none describes the content being published` : null;
71036
+ const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) were recorded at this commit, but none describes the content being published` : null;
70934
71037
  const attestationErrors = [...evidence.historyErrors, ...staleEvidence ? [staleEvidence] : []];
70935
71038
  if (attestationErrors.length > 0 && !force) {
70936
71039
  const remedy = `\`himi test --content ${contentArg}\` (or \`himi render <screenId> --content ${contentArg}\`)`;
@@ -70939,7 +71042,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70939
71042
  ok: false,
70940
71043
  pushed: false,
70941
71044
  errors: attestationErrors,
70942
- 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\`.`
71045
+ 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\`.`
70943
71046
  });
70944
71047
  return 1;
70945
71048
  }
@@ -72404,7 +72507,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72404
72507
  });
72405
72508
  }
72406
72509
  const bytes2 = readFileSync75(file);
72407
- const sha = createHash27("sha256").update(bytes2).digest("hex");
72510
+ const sha = createHash28("sha256").update(bytes2).digest("hex");
72408
72511
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
72409
72512
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
72410
72513
  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 }));
@@ -72740,7 +72843,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72740
72843
  const { preflightApp: preflightApp2 } = await Promise.resolve().then(() => (init_preflight_app(), preflight_app_exports));
72741
72844
  const { resolveStoreCredentials: resolveStoreCredentials2 } = await Promise.resolve().then(() => (init_credential_profile(), credential_profile_exports));
72742
72845
  const { readIosVersion: readIosVersion2, bumpVersion: bumpVersion2 } = await Promise.resolve().then(() => (init_version(), version_exports));
72743
- const { iosProjectYml: iosYmlPath, hasRepoCheckout: hasRepoCheckout2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
72846
+ const { iosProjectYml: iosYmlPath, hasRepoCheckout: hasRepoCheckout2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
72744
72847
  const { ascStatus: ascStatus2 } = await Promise.resolve().then(() => (init_asc_status(), asc_status_exports));
72745
72848
  const { readFileSync: rf, readdirSync: rd, existsSync: ex, statSync: st } = await import("node:fs");
72746
72849
  const { execFile: execFile2 } = await import("node:child_process");