@gjermundgaraba/upup 0.5.0 → 0.5.2

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/main.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as openFlags, r as reason, t as byName } from "./io-ns6xhFdF.mjs";
2
+ import { i as reason, n as decodeUtf8, r as openFlags, t as byName } from "./io-Dgi-IiVu.mjs";
3
3
  import { findPackageJSON } from "node:module";
4
4
  import { NodeRuntime, NodeServices, NodeStdio } from "@effect/platform-node";
5
5
  import { Cause, Clock, Config, Context, Data, Deferred, Duration, Effect, Equal, Exit, Fiber, FiberHandle, FiberSet, FileSystem, Layer, Option, Predicate, Queue, Ref, Result, Schedule, Schema, Semaphore, Stream, SubscriptionRef, identity } from "effect";
@@ -19,6 +19,7 @@ import lockfile from "proper-lockfile";
19
19
  import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/context";
20
20
  import { GenerationTask, Harness, LiveDoc, ROOT_CONVERSATION_ID, ToolTask, createRegistry, defineDoc, defineExtension, defineTool, hook, section } from "@earendil-works/pi-durable";
21
21
  import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
22
+ import { absurd, constVoid } from "effect/Function";
22
23
  import { ChildProcess, ChildProcessSpawner, CommandLine } from "effect/process";
23
24
  import { Worker } from "node:worker_threads";
24
25
  import semver from "semver";
@@ -33,16 +34,14 @@ import { bundleFromJSON, isBundleWithDsseEnvelope } from "@sigstore/bundle";
33
34
  import { getTrustedRoot } from "@sigstore/tuf";
34
35
  import { Verifier, toSignedEntity, toTrustMaterial } from "@sigstore/verify";
35
36
  import { isDeepStrictEqual } from "node:util";
36
- import { constVoid } from "effect/Function";
37
37
  import { applyEdits, modify, parse as parse$1 } from "jsonc-parser";
38
38
  import { getStaticTOMLValue, parseTOML, traverseNodes } from "toml-eslint-parser";
39
39
  import { XMLParser } from "fast-xml-parser";
40
- import { isSea } from "node:sea";
41
40
  import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
42
41
  import * as Action from "@gjermundgaraba/effect-actions/Action";
43
42
  import { setup, types } from "xstate/fsm";
44
43
  // package.json
45
- var version$1 = "0.5.0";
44
+ var version$1 = "0.5.2";
46
45
  // src/ai/Setup.ts
47
46
  const Thinking = Schema.Literals([
48
47
  "off",
@@ -428,13 +427,15 @@ const withTarget = (pkg, target) => ({
428
427
  const isUnaudited = Schema.is(UnauditedEcosystem);
429
428
  const isApp$1 = Schema.is(AppEcosystem);
430
429
  const isRuntime = Schema.is(RuntimeEcosystem);
430
+ const isOs = Schema.is(OsEcosystem);
431
431
  const isUnauditedUpdate = (pkg) => isUnaudited(pkg.ecosystem);
432
432
  const isAppUpdate = (pkg) => isApp$1(pkg.ecosystem);
433
- const unauditedKind = (pkg) => {
434
- if (isApp$1(pkg.ecosystem)) return "app";
435
- if (isRuntime(pkg.ecosystem)) return "runtime";
436
- if (isUnaudited(pkg.ecosystem)) return "os";
433
+ const ecosystemKind = (ecosystem) => {
434
+ if (isApp$1(ecosystem)) return "app";
435
+ if (isRuntime(ecosystem)) return "runtime";
436
+ return isOs(ecosystem) ? "os" : absurd(ecosystem);
437
437
  };
438
+ const unauditedKind = (pkg) => isUnaudited(pkg.ecosystem) ? ecosystemKind(pkg.ecosystem) : void 0;
438
439
  // src/inventory/Provider.ts
439
440
  var OutputMalformed = class extends Schema.TaggedError()("OutputMalformed", {
440
441
  command: Schema.String,
@@ -560,7 +561,8 @@ const layer$6 = Layer.effect(Exec, Effect.gen(function* () {
560
561
  const nodeLayer = layer$6.pipe(Layer.provide(NodeServices.layer));
561
562
  // src/brew/Brew.ts
562
563
  const caskPrefix = "cask:";
563
- const isCask = (pkg) => pkg.id.startsWith(caskPrefix);
564
+ const Kind = Schema.Literals(["formula", "cask"]);
565
+ const kindOf$2 = (pkg) => pkg.id.startsWith(caskPrefix) ? "cask" : "formula";
564
566
  const Keg = Schema.Struct({
565
567
  version: Schema.String,
566
568
  installed_on_request: Schema.Boolean,
@@ -604,13 +606,12 @@ const Outdated$1 = Schema.Struct({
604
606
  formulae: Schema.Array(OutdatedEntry),
605
607
  casks: Schema.Array(OutdatedEntry)
606
608
  });
607
- const brew$1 = (args, refreshMetadata = false) => ({
609
+ const brew = (args, refreshMetadata = false) => ({
608
610
  command: "brew",
609
611
  args,
610
612
  cwd: "/",
611
613
  env: refreshMetadata ? {} : { HOMEBREW_NO_AUTO_UPDATE: "1" }
612
614
  });
613
- const kindOf$2 = (pkg) => pkg.id.startsWith("cask:") ? "cask" : "formula";
614
615
  const activeKeg = (formula) => formula.installed.find((keg) => keg.version === formula.linked_keg) ?? formula.installed.at(-1);
615
616
  const describedAs = (desc) => desc === void 0 || desc === null || desc === "" ? {} : { description: desc };
616
617
  const packages = (installed, outdated) => {
@@ -657,19 +658,19 @@ const make$14 = Effect.gen(function* () {
657
658
  const exec = yield* Exec;
658
659
  const json = (schema, invocation) => exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(schema, commandLine(invocation))));
659
660
  const list = Effect.gen(function* () {
660
- const installed = yield* json(Installed, brew$1([
661
+ const installed = yield* json(Installed, brew([
661
662
  "info",
662
663
  "--json=v2",
663
664
  "--installed"
664
665
  ]));
665
- const outdated = yield* json(Outdated$1, brew$1(["outdated", "--json=v2"], true));
666
+ const outdated = yield* json(Outdated$1, brew(["outdated", "--json=v2"], true));
666
667
  return {
667
668
  packages: packages(installed, outdated),
668
669
  notices: []
669
670
  };
670
671
  }).pipe(Effect.withSpan("Brew.list"));
671
672
  const observe = Effect.fn("Brew.observe")(function* (pkg) {
672
- const installed = yield* json(Installed, brew$1([
673
+ const installed = yield* json(Installed, brew([
673
674
  "info",
674
675
  "--json=v2",
675
676
  `--${kindOf$2(pkg)}`,
@@ -682,7 +683,7 @@ const make$14 = Effect.gen(function* () {
682
683
  return Option.fromUndefinedOr(observed.find((candidate) => candidate.id === pkg.id));
683
684
  });
684
685
  const offered = Effect.fn("Brew.offered")(function* (pkg) {
685
- const invocation = brew$1([
686
+ const invocation = brew([
686
687
  "outdated",
687
688
  "--json=v2",
688
689
  `--${kindOf$2(pkg)}`,
@@ -710,14 +711,14 @@ const make$14 = Effect.gen(function* () {
710
711
  expected: target,
711
712
  ...now === void 0 ? {} : { offered: now }
712
713
  });
713
- yield* exec.stdout(brew$1([
714
+ yield* exec.stdout(brew([
714
715
  "upgrade",
715
716
  `--${kindOf$2(pkg)}`,
716
717
  pkg.name
717
718
  ]));
718
719
  }),
719
720
  uninstall: Effect.fn("Brew.uninstall")(function* (pkg) {
720
- yield* exec.stdout(brew$1([
721
+ yield* exec.stdout(brew([
721
722
  "uninstall",
722
723
  `--${kindOf$2(pkg)}`,
723
724
  pkg.name
@@ -762,65 +763,88 @@ const Report = Schema.Struct({
762
763
  findings: Schema.Array(Finding),
763
764
  unverified: Schema.Array(Schema.String).annotate({ description: "Each thing you could not verify." })
764
765
  });
766
+ const Context$1 = Schema.TaggedUnion({
767
+ None: {},
768
+ Brew: { kind: Kind },
769
+ Python: { python: Schema.String },
770
+ Revision: { revision: Schema.String }
771
+ });
772
+ const sameContext = Schema.toEquivalence(Context$1);
765
773
  Schema.Struct({
766
774
  mode: Mode,
767
775
  delta: Delta,
768
- context: Schema.Record(Schema.String, Schema.String)
776
+ context: Context$1
769
777
  });
770
778
  var ChangesUnavailable = class extends Schema.TaggedError()("ChangesUnavailable", { issue: Schema.String }) {};
771
779
  const isRegistry = Schema.is(RegistryEcosystem);
772
- const deltaOf = (pkg) => pkg.target === void 0 || !isRegistry(pkg.ecosystem) ? void 0 : {
773
- ecosystem: pkg.ecosystem,
774
- artifact: pkg.artifact,
775
- from: pkg.current,
776
- to: pkg.target
777
- };
778
780
  const contextOf = (pkg) => {
779
- if (pkg.ecosystem === "brew") return { kind: isCask(pkg) ? "cask" : "formula" };
780
- if (pkg.ecosystem === "pypi" && pkg.runtime !== void 0) return { python: pkg.runtime };
781
- if (pkg.ecosystem === "github-actions" && pkg.revision !== void 0) return { revision: pkg.revision };
782
- return {};
781
+ if (pkg.ecosystem === "brew") return {
782
+ _tag: "Brew",
783
+ kind: kindOf$2(pkg)
784
+ };
785
+ if (pkg.ecosystem === "pypi" && pkg.runtime !== void 0) return {
786
+ _tag: "Python",
787
+ python: pkg.runtime
788
+ };
789
+ if (pkg.ecosystem === "github-actions" && pkg.revision !== void 0) return {
790
+ _tag: "Revision",
791
+ revision: pkg.revision
792
+ };
793
+ return { _tag: "None" };
794
+ };
795
+ const unauditedReasons = {
796
+ app: "an app update",
797
+ runtime: "a Node.js runtime version",
798
+ os: "an operating-system update"
799
+ };
800
+ const registryOf = (pkg) => {
801
+ if (!isRegistry(pkg.ecosystem)) return Result.fail(`it is ${unauditedReasons[ecosystemKind(pkg.ecosystem)]}, which upup does not audit`);
802
+ return pkg.privateRegistry === void 0 ? Result.succeed(pkg.ecosystem) : Result.fail(`it comes from the private registry ${pkg.privateRegistry}, which upup does not audit`);
803
+ };
804
+ const deltaOf = (pkg) => {
805
+ const { target } = pkg;
806
+ return target === void 0 ? Result.fail("no update is offered") : Result.map(registryOf(pkg), (ecosystem) => ({
807
+ ecosystem,
808
+ artifact: pkg.artifact,
809
+ from: pkg.current,
810
+ to: target
811
+ }));
783
812
  };
784
- const landingOf = (pkg, mode) => pkg.privateRegistry === void 0 && isRegistry(pkg.ecosystem) ? {
813
+ const auditOf = (pkg, mode) => Result.map(deltaOf(pkg), (delta) => ({
785
814
  mode,
786
- ecosystem: pkg.ecosystem,
815
+ delta,
816
+ context: contextOf(pkg)
817
+ }));
818
+ const identityOf = (pkg, mode) => Result.getOrUndefined(auditOf(pkg, mode));
819
+ const unauditable = (pkg) => Result.getOrUndefined(Result.flip(deltaOf(pkg)));
820
+ const landingOf = (pkg, mode) => Result.getOrUndefined(Result.map(registryOf(pkg), (ecosystem) => ({
821
+ mode,
822
+ ecosystem,
787
823
  artifact: pkg.artifact,
788
824
  version: pkg.current,
789
825
  context: contextOf(pkg)
790
- } : void 0;
826
+ })));
827
+ const landingAt = (identity) => ({
828
+ mode: identity.mode,
829
+ ecosystem: identity.delta.ecosystem,
830
+ artifact: identity.delta.artifact,
831
+ version: identity.delta.to,
832
+ context: identity.context
833
+ });
791
834
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
792
835
  const landingKey = (landing) => sha256(JSON.stringify([
793
836
  landing.ecosystem,
794
837
  landing.artifact,
795
838
  landing.version,
796
- Object.entries(landing.context).toSorted(([left], [right]) => left.localeCompare(right))
839
+ landing.context
797
840
  ]));
798
- const unauditedReasons = {
799
- app: "an app update",
800
- runtime: "a Node.js runtime version",
801
- os: "an operating-system update"
802
- };
803
- const unauditable = (pkg) => {
804
- if (pkg.target === void 0) return "no update is offered";
805
- const kind = unauditedKind(pkg);
806
- if (kind !== void 0) return `it is ${unauditedReasons[kind]}, which upup does not audit`;
807
- return pkg.privateRegistry === void 0 ? void 0 : `it comes from the private registry ${pkg.privateRegistry}, which upup does not audit`;
808
- };
809
- const identityOf = (pkg, mode) => {
810
- const delta = deltaOf(pkg);
811
- return delta === void 0 || pkg.privateRegistry !== void 0 ? void 0 : {
812
- mode,
813
- delta,
814
- context: contextOf(pkg)
815
- };
816
- };
817
841
  const deltaKey = (delta) => sha256(JSON.stringify([
818
842
  delta.ecosystem,
819
843
  delta.artifact,
820
844
  delta.from,
821
845
  delta.to
822
846
  ]));
823
- const workspaceKey = (identity) => sha256(JSON.stringify([deltaKey(identity.delta), Object.entries(identity.context).toSorted(([left], [right]) => left.localeCompare(right))]));
847
+ const workspaceKey = (identity) => sha256(JSON.stringify([deltaKey(identity.delta), identity.context]));
824
848
  // src/audit/tools.ts
825
849
  const limits$1 = {
826
850
  resultBytes: 262144,
@@ -833,16 +857,6 @@ const limits$1 = {
833
857
  longLineMatches: 1025
834
858
  };
835
859
  const grepTimeoutMs = 3e4;
836
- const names$1 = {
837
- read: "read",
838
- grep: "grep",
839
- list: "list"
840
- };
841
- const descriptions = {
842
- read: "Read a bounded byte range from one regular UTF-8 file.",
843
- grep: "Find a JavaScript regular expression in UTF-8 regular files below a path. Each line is matched whole. Lines over 1 KiB show a bounded window around each match with its file byte offset; a file stops at a line over 16 MiB.",
844
- list: "List workspace entries below a directory to a bounded depth."
845
- };
846
860
  const count$1 = (maximum) => Schema.Number.pipe(Schema.check(Schema.isInt(), Schema.isBetween({
847
861
  minimum: 1,
848
862
  maximum
@@ -895,7 +909,6 @@ const respond = async (signal, run) => {
895
909
  const checkPath = (name) => {
896
910
  if (!(name === "." || name !== "" && !name.includes("\\") && !name.includes("\0") && name.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."))) fail$1(`path ${JSON.stringify(name)} is not workspace-relative`);
897
911
  };
898
- const isCount = (value, min, max) => Number.isSafeInteger(value) && value >= min && value <= max;
899
912
  const kindOf$1 = (info) => {
900
913
  if (info.isFile()) return "file";
901
914
  return info.isDirectory() ? "directory" : "other";
@@ -931,20 +944,9 @@ const trimIncompleteUtf8 = (data) => {
931
944
  return data;
932
945
  }
933
946
  };
934
- const decodeUtf8 = (data) => {
935
- try {
936
- return new TextDecoder("utf-8", {
937
- fatal: true,
938
- ignoreBOM: true
939
- }).decode(data);
940
- } catch {
941
- return;
942
- }
943
- };
944
947
  const read$2 = async (workspace, args, signal) => await respond(signal, async () => {
945
948
  const name = args.path;
946
949
  checkPath(name);
947
- if (!isCount(args.offset, 0, Number.MAX_SAFE_INTEGER) || !isCount(args.limit, 1, limits$1.readBytes)) fail$1("offset or limit is outside the allowed range");
948
950
  const located = await locate$4(workspace, name, `open ${name}`);
949
951
  if (located.kind !== "file") fail$1("path is not a regular file");
950
952
  signal?.throwIfAborted();
@@ -978,7 +980,6 @@ const read$2 = async (workspace, args, signal) => await respond(signal, async ()
978
980
  });
979
981
  const list = async (workspace, args, signal) => await respond(signal, async () => {
980
982
  checkPath(args.path);
981
- if (!isCount(args.depth, 1, limits$1.listDepth)) fail$1("depth is outside the allowed range");
982
983
  const located = await locate$4(workspace, args.path, `list workspace: walk ${args.path}`);
983
984
  if (located.kind !== "directory") return "no entries";
984
985
  let result = "";
@@ -1005,7 +1006,7 @@ const list = async (workspace, args, signal) => await respond(signal, async () =
1005
1006
  return result === "" ? "no entries" : result;
1006
1007
  });
1007
1008
  const decodeAnswer = Schema.decodeUnknownOption(Schema.Union([Schema.Struct({ text: Schema.String }), Schema.Struct({ error: Schema.String })]));
1008
- const grepWorker = process.env["UPUP_GREP_WORKER"] ?? new URL(`grep-worker${path.extname(new URL(import.meta.url).pathname)}`, import.meta.url);
1009
+ const grepWorker = new URL(`grep-worker${path.extname(new URL(import.meta.url).pathname)}`, import.meta.url);
1009
1010
  const search = async (job, timeoutMs, signal) => {
1010
1011
  const { promise, resolve, reject } = Promise.withResolvers();
1011
1012
  const worker = new Worker(grepWorker);
@@ -1063,15 +1064,29 @@ const ends = (offset, limit) => {
1063
1064
  const [from, to] = [span(offset), span(offset + limit)];
1064
1065
  return from === to ? `${from} +${span(limit)}` : `${from}–${to}`;
1065
1066
  };
1067
+ const tool = (description, Args, run, detail) => {
1068
+ const decode = Schema.decodeUnknownSync(Args);
1069
+ return {
1070
+ description,
1071
+ Args,
1072
+ call: async (workspace, args, signal) => await run(workspace, decode(args), signal),
1073
+ detail
1074
+ };
1075
+ };
1076
+ const tools$3 = {
1077
+ read: tool("Read a bounded byte range from one regular UTF-8 file.", ReadArgs, read$2, ({ path: shown, range }) => `${shown}${range}`),
1078
+ grep: tool("Find a JavaScript regular expression in UTF-8 regular files below a path. Each line is matched whole. Lines over 1 KiB show a bounded window around each match with its file byte offset; a file stops at a line over 16 MiB.", GrepArgs, async (workspace, args, signal) => await grep(workspace, args, signal === void 0 ? {} : { signal }), ({ path: shown, pattern }) => `${pattern} ${shown}`),
1079
+ list: tool("List workspace entries below a directory to a bounded depth.", ListArgs, list, ({ path: shown }) => shown)
1080
+ };
1081
+ const isTool = (name) => Object.hasOwn(tools$3, name);
1066
1082
  const describeCall = (name, args) => {
1067
- if (!Predicate.isObject(args)) return name;
1083
+ if (!Predicate.isObject(args) || !isTool(name)) return name;
1068
1084
  const { path: target, pattern, offset, limit } = args;
1069
- const shownPath = Predicate.isString(target) ? target : "";
1070
- const range = Predicate.isNumber(offset) && Predicate.isNumber(limit) ? ` bytes ${ends(offset, limit)}` : "";
1071
- let detail = "";
1072
- if (name === names$1.read) detail = `${shownPath}${range}`;
1073
- else if (name === names$1.list) detail = shownPath;
1074
- else if (name === names$1.grep) detail = `${Predicate.isString(pattern) ? pattern : ""} ${shownPath}`;
1085
+ const detail = tools$3[name].detail({
1086
+ path: Predicate.isString(target) ? target : "",
1087
+ pattern: Predicate.isString(pattern) ? pattern : "",
1088
+ range: Predicate.isNumber(offset) && Predicate.isNumber(limit) ? ` bytes ${ends(offset, limit)}` : ""
1089
+ });
1075
1090
  return detail === "" ? name : `${name} ${detail}`;
1076
1091
  };
1077
1092
  // src/audit/hashed-names.ts
@@ -1729,7 +1744,7 @@ const Meta = Schema.Struct({
1729
1744
  ecosystem: Delta.fields.ecosystem,
1730
1745
  from: Schema.String,
1731
1746
  to: Schema.String,
1732
- context: Schema.Record(Schema.String, Schema.String),
1747
+ context: Context$1,
1733
1748
  evidence: Grade,
1734
1749
  evidence_detail: Schema.String,
1735
1750
  preparation_key: Schema.String
@@ -1740,7 +1755,7 @@ const failed$1 = (workspace) => (cause) => new WorkspaceFailed({
1740
1755
  issue: cause instanceof Error ? cause.message : String(cause)
1741
1756
  });
1742
1757
  const listed$2 = async (workspace, name) => await readFile(path.join(workspace, name), "utf-8").catch(() => void 0);
1743
- const write = (workspace, identity, diff, evidence, detail, preparationKey) => Effect.tryPromise({
1758
+ const write = (workspace, identity, { diff, evidence, detail }, preparationKey) => Effect.tryPromise({
1744
1759
  try: async () => {
1745
1760
  await writeFile(path.join(workspace, files.diff), diff, { mode: 384 });
1746
1761
  await writeFile(path.join(workspace, files.index), buildIndex(diff), { mode: 384 });
@@ -1763,7 +1778,6 @@ const readMeta = (workspace) => Effect.tryPromise({
1763
1778
  try: async () => await readFile(path.join(workspace, files.meta), "utf-8"),
1764
1779
  catch: failed$1(workspace)
1765
1780
  }).pipe(Effect.flatMap((text) => Schema.decodeEffect(MetaJson)(text).pipe(Effect.mapError(failed$1(workspace)))));
1766
- const sameContext = (left, right) => JSON.stringify(Object.entries(left).toSorted(([a], [b]) => a.localeCompare(b))) === JSON.stringify(Object.entries(right).toSorted(([a], [b]) => a.localeCompare(b)));
1767
1781
  const describes = (meta, identity) => meta.name === identity.delta.artifact && meta.ecosystem === identity.delta.ecosystem && meta.from === identity.delta.from && meta.to === identity.delta.to && sameContext(meta.context, identity.context);
1768
1782
  const readable = (workspace, identity) => readMeta(workspace).pipe(Effect.flatMap((meta) => describes(meta, identity) ? Effect.promise(async () => await stat(path.join(workspace, files.diff)).then(() => true, () => false)) : Effect.succeed(false)), Effect.orElseSucceed(() => false));
1769
1783
  const prune = (workspace) => Effect.promise(async () => {
@@ -1907,7 +1921,7 @@ const Brief = Schema.Struct({
1907
1921
  artifact: Schema.String,
1908
1922
  from: Schema.String,
1909
1923
  to: Schema.String,
1910
- context: Schema.Record(Schema.String, Schema.String),
1924
+ context: Context$1,
1911
1925
  workspace: Schema.String,
1912
1926
  evidence: Grade,
1913
1927
  evidenceDetail: Schema.String,
@@ -1928,9 +1942,6 @@ const BriefDoc = defineDoc({
1928
1942
  checkpointWhen: () => false
1929
1943
  });
1930
1944
  const parameters = (schema) => Schema.toJsonSchemaDocument(schema).schema;
1931
- const decodeRead = Schema.decodeUnknownSync(ReadArgs);
1932
- const decodeGrep = Schema.decodeUnknownSync(GrepArgs);
1933
- const decodeList = Schema.decodeUnknownSync(ListArgs);
1934
1945
  const decodeSubmitted = Schema.decodeUnknownSync(Report);
1935
1946
  const encodeReport = (submitted) => ({
1936
1947
  ...submitted,
@@ -1960,27 +1971,13 @@ const answer = (result) => {
1960
1971
  const auditExtension = (workspace) => {
1961
1972
  let steps = 0;
1962
1973
  let nudged = false;
1963
- const read = defineTool({
1964
- name: "read",
1965
- description: descriptions.read,
1966
- replay: "safe",
1967
- parameters: parameters(ReadArgs),
1968
- execute: async (args, _api, invocation) => answer(await read$2(workspace, decodeRead(args), invocation.abortSignal))
1969
- });
1970
- const grep$1 = defineTool({
1971
- name: "grep",
1972
- description: descriptions.grep,
1973
- replay: "safe",
1974
- parameters: parameters(GrepArgs),
1975
- execute: async (args, _api, invocation) => answer(await grep(workspace, decodeGrep(args), invocation.abortSignal === void 0 ? {} : { signal: invocation.abortSignal }))
1976
- });
1977
- const list$1 = defineTool({
1978
- name: "list",
1979
- description: descriptions.list,
1974
+ const investigate = Object.entries(tools$3).map(([name, tool]) => defineTool({
1975
+ name,
1976
+ description: tool.description,
1980
1977
  replay: "safe",
1981
- parameters: parameters(ListArgs),
1982
- execute: async (args, _api, invocation) => answer(await list(workspace, decodeList(args), invocation.abortSignal))
1983
- });
1978
+ parameters: parameters(tool.Args),
1979
+ execute: async (args, _api, invocation) => answer(await tool.call(workspace, args, invocation.abortSignal))
1980
+ }));
1984
1981
  const submit = defineTool({
1985
1982
  name: "submit_report",
1986
1983
  description: "Submit the audit report. Call exactly once, when the investigation is done; the audit ends.",
@@ -2000,12 +1997,7 @@ const auditExtension = (workspace) => {
2000
1997
  });
2001
1998
  return defineExtension({
2002
1999
  name: "upup.audit",
2003
- tools: [
2004
- read,
2005
- grep$1,
2006
- list$1,
2007
- submit
2008
- ],
2000
+ tools: [...investigate, submit],
2009
2001
  sections: [section("audit", () => instructions$1)],
2010
2002
  hooks: [hook(GenerationTask, {
2011
2003
  beforeRequest: (request) => {
@@ -2036,17 +2028,15 @@ const policyKey = (prompt, setup) => sha256(JSON.stringify([
2036
2028
  setup.modelId,
2037
2029
  setup.thinking
2038
2030
  ]));
2031
+ const said = (kind, content) => content.trim() === "" ? [] : [{
2032
+ kind,
2033
+ text: content
2034
+ }];
2039
2035
  const entriesOf$1 = (message) => {
2040
2036
  if (message.role === "assistant") {
2041
2037
  const blocks = message.content.flatMap((block) => {
2042
- if (block.type === "thinking") return block.thinking.trim() === "" ? [] : [{
2043
- kind: "thinking",
2044
- text: block.thinking
2045
- }];
2046
- if (block.type === "text") return block.text.trim() === "" ? [] : [{
2047
- kind: "text",
2048
- text: block.text
2049
- }];
2038
+ if (block.type === "thinking") return said("thinking", block.thinking);
2039
+ if (block.type === "text") return said("text", block.text);
2050
2040
  return [{
2051
2041
  kind: "tool",
2052
2042
  text: describeCall(block.name, block.arguments)
@@ -2082,14 +2072,8 @@ const decodeLive = Schema.decodeUnknownOption(Schema.Struct({ generation: Schema
2082
2072
  retry: Schema.optional(Schema.Struct({ error: Schema.String }))
2083
2073
  })) }));
2084
2074
  const streamed = (block) => {
2085
- if (isThinkingBlock(block)) return block.thinking.trim() === "" ? [] : [{
2086
- kind: "thinking",
2087
- text: block.thinking
2088
- }];
2089
- if (isTextBlock(block)) return block.text.trim() === "" ? [] : [{
2090
- kind: "text",
2091
- text: block.text
2092
- }];
2075
+ if (isThinkingBlock(block)) return said("thinking", block.thinking);
2076
+ if (isTextBlock(block)) return said("text", block.text);
2093
2077
  return [];
2094
2078
  };
2095
2079
  var Transcript = class {
@@ -2360,6 +2344,42 @@ const lookup$5 = Effect.fn("go.lookup")(function* (client, wanted) {
2360
2344
  withdrawn: {}
2361
2345
  };
2362
2346
  });
2347
+ // src/registry/npm.ts
2348
+ const Version = Schema.Struct({ deprecated: Schema.optionalKey(Schema.Unknown) });
2349
+ const Packument$1 = Schema.Struct({
2350
+ description: Schema.optionalKey(Schema.String),
2351
+ homepage: Schema.optionalKey(Schema.String),
2352
+ repository: Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ url: Schema.optionalKey(Schema.String) })])),
2353
+ "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) })),
2354
+ time: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
2355
+ versions: Schema.optionalKey(Schema.Record(Schema.String, Version))
2356
+ });
2357
+ const Downloads = Schema.Struct({ downloads: Schema.Finite });
2358
+ const message$2 = (version) => {
2359
+ const deprecated = version?.deprecated;
2360
+ return Predicate.isString(deprecated) && deprecated !== "" ? Option.some(deprecated) : Option.none();
2361
+ };
2362
+ const npmRegistry = "https://registry.npmjs.org";
2363
+ const npmPath = (segment) => encodeURIComponent(segment).replaceAll(/%(?:24|26|2B|3A|3D|40)/gu, (escaped) => decodeURIComponent(escaped));
2364
+ const lookup$4 = Effect.fn("npm.lookup")(function* (client, wanted) {
2365
+ const [packument, downloads] = yield* Effect.all([getJson(client, `${npmRegistry}/${npmPath(wanted.name)}`, Packument$1), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: "unbounded" });
2366
+ const versions = packument.versions ?? {};
2367
+ const latest = packument["dist-tags"]?.latest;
2368
+ const repository = Predicate.isString(packument.repository) ? packument.repository : packument.repository?.url;
2369
+ const deprecated = latest === void 0 ? Option.none() : message$2(versions[latest]);
2370
+ return {
2371
+ ...packument.description === void 0 ? {} : { description: packument.description },
2372
+ ...packument.homepage === void 0 ? {} : { homepage: packument.homepage },
2373
+ ...repository === void 0 ? {} : { repository: repositoryUrl(repository) },
2374
+ ...Option.isSome(downloads) ? { popularity: `${count(downloads.value.downloads)} downloads last week` } : {},
2375
+ ...Option.isSome(deprecated) ? { deprecated: deprecated.value } : {},
2376
+ released: pick$1(packument.time ?? {}, wanted.versions),
2377
+ withdrawn: Object.fromEntries(wanted.versions.flatMap((version) => Option.match(message$2(versions[version]), {
2378
+ onNone: () => [],
2379
+ onSome: (reason) => [[version, reason]]
2380
+ })))
2381
+ };
2382
+ });
2363
2383
  // src/audit/binary.ts
2364
2384
  const startsWith$1 = (prefix, magic) => magic.every((byte, index) => prefix[index] === byte);
2365
2385
  const binaryKind = (prefix) => {
@@ -4274,11 +4294,11 @@ var EvidenceUnavailable = class extends Schema.TaggedError()("EvidenceUnavailabl
4274
4294
  reason: Schema.optionalKey(Schema.Literals(["gone", "mismatch"]))
4275
4295
  }) {};
4276
4296
  const maxDownloadBytes = 8 * 1024 ** 3;
4277
- const message$2 = (cause) => cause instanceof Error ? cause.message : String(cause);
4297
+ const message$1 = (cause) => cause instanceof Error ? cause.message : String(cause);
4278
4298
  const download = Effect.fn("evidence.download")(function* (client, url, limit = maxDownloadBytes, stall) {
4279
4299
  const unavailable = (cause) => new EvidenceUnavailable({
4280
4300
  source: url,
4281
- issue: message$2(cause)
4301
+ issue: message$1(cause)
4282
4302
  });
4283
4303
  const stalled = (after) => unavailable(`sent nothing for ${Duration.format(after)}, so upup gave up`);
4284
4304
  const directory = yield* Effect.acquireRelease(Effect.tryPromise({
@@ -4324,7 +4344,7 @@ const download = Effect.fn("evidence.download")(function* (client, url, limit =
4324
4344
  sha256: hash.digest("hex")
4325
4345
  };
4326
4346
  });
4327
- const canonicalSha256$1 = (raw) => {
4347
+ const canonicalSha256 = (raw) => {
4328
4348
  const value = raw.trim().toLowerCase();
4329
4349
  return /^[0-9a-f]{64}$/u.test(value) ? value : void 0;
4330
4350
  };
@@ -4346,7 +4366,7 @@ const crate = Effect.fn("evidence.crate")(function* (client, name, version) {
4346
4366
  source,
4347
4367
  issue: `no crates index entry for ${name} ${version}`
4348
4368
  });
4349
- const sum = canonicalSha256$1(entry.cksum);
4369
+ const sum = canonicalSha256(entry.cksum);
4350
4370
  if (sum === void 0) return yield* new EvidenceUnavailable({
4351
4371
  source,
4352
4372
  issue: `crates index publishes an invalid checksum for ${name} ${version}`
@@ -4400,8 +4420,6 @@ const NpmVersion = Schema.Struct({
4400
4420
  shasum: Schema.optionalKey(Schema.String)
4401
4421
  }))
4402
4422
  });
4403
- const npmRegistry = "https://registry.npmjs.org";
4404
- const npmPath = (segment) => encodeURIComponent(segment).replaceAll(/%(?:24|26|2B|3A|3D|40)/gu, (escaped) => decodeURIComponent(escaped));
4405
4423
  const strictBase64 = (value) => {
4406
4424
  const decoded = Buffer.from(value, "base64");
4407
4425
  return decoded.toString("base64") === value ? decoded : void 0;
@@ -4452,7 +4470,7 @@ const npmPlan = Effect.fn("evidence.npmPlan")(function* (name, version, metadata
4452
4470
  try: () => npmDigest(dist.integrity ?? "", dist.shasum ?? ""),
4453
4471
  catch: (error) => new EvidenceUnavailable({
4454
4472
  source,
4455
- issue: `npm ${name}@${version}: ${message$2(error)}`
4473
+ issue: `npm ${name}@${version}: ${message$1(error)}`
4456
4474
  })
4457
4475
  })
4458
4476
  };
@@ -4515,7 +4533,7 @@ const fetchVerified = Effect.fn("evidence.fetchVerified")(function* (client, url
4515
4533
  try: async () => await computedDigest(digest, downloaded),
4516
4534
  catch: (error) => new EvidenceUnavailable({
4517
4535
  source: url,
4518
- issue: message$2(error)
4536
+ issue: message$1(error)
4519
4537
  })
4520
4538
  });
4521
4539
  if (computed === void 0) return yield* new EvidenceUnavailable({
@@ -4568,6 +4586,10 @@ const preparationPlan = (subject, evidence) => {
4568
4586
  plan: `${JSON.stringify(plan, null, 2)}\n`
4569
4587
  };
4570
4588
  };
4589
+ const prepareWorkspace = Effect.fn("evidence.prepareWorkspace")(function* (workspace, subject, preparation) {
4590
+ const prepared = yield* preparation.materialize(workspace);
4591
+ return yield* write(workspace, subject, prepared, preparation.key);
4592
+ });
4571
4593
  // src/registry/github.ts
4572
4594
  const githubApi = "https://api.github.com";
4573
4595
  const githubRepository = (name) => `https://github.com/${name}.git`;
@@ -4630,18 +4652,11 @@ const sourceOf$1 = (entry) => ({
4630
4652
  tapGitHead: entry.tap_git_head ?? "",
4631
4653
  rubySourceChecksum: entry.ruby_source_checksum?.sha256 ?? ""
4632
4654
  });
4633
- const brew = (args) => ({
4634
- command: "brew",
4635
- args,
4636
- cwd: "/",
4637
- env: { HOMEBREW_NO_AUTO_UPDATE: "1" }
4638
- });
4639
4655
  const unavailable$2 = (source, issue) => new EvidenceUnavailable({
4640
4656
  source,
4641
4657
  issue
4642
4658
  });
4643
4659
  const query = Effect.fn("brew.query")(function* (kind, name) {
4644
- if (kind !== "formula" && kind !== "cask") return yield* unavailable$2("brew", `unsupported brew package variant ${JSON.stringify(kind)}`);
4645
4660
  const raw = yield* (yield* Exec).stdout(brew([
4646
4661
  "info",
4647
4662
  "--json=v2",
@@ -4791,9 +4806,7 @@ const nextPage = (current, link) => {
4791
4806
  const githubGet = (client, url, token) => Effect.gen(function* () {
4792
4807
  const headers = {
4793
4808
  "user-agent": userAgent,
4794
- accept: "application/vnd.github+json",
4795
- "x-github-api-version": "2022-11-28",
4796
- ...token !== void 0 && url.startsWith(`https://api.github.com/`) ? { authorization: `Bearer ${token}` } : {}
4809
+ ...githubHeaders(url.startsWith(`https://api.github.com/`) ? token : void 0)
4797
4810
  };
4798
4811
  const response = yield* get(client, url, headers);
4799
4812
  const fail = (issue) => Effect.fail(new EvidenceUnavailable({
@@ -6099,17 +6112,17 @@ const provenanceSource = (statement, integrity) => {
6099
6112
  commit
6100
6113
  };
6101
6114
  };
6102
- const message$1 = (cause) => cause instanceof Error ? cause.message : String(cause);
6115
+ const message = (cause) => cause instanceof Error ? cause.message : String(cause);
6103
6116
  const sigstoreVerifier = Effect.gen(function* () {
6104
- const cachePath = path.join(yield* cacheDir.pipe(Effect.mapError((error) => `no cache directory: ${message$1(error)}`)), "sigstore");
6117
+ const cachePath = path.join(yield* cacheDir.pipe(Effect.mapError((error) => `no cache directory: ${message(error)}`)), "sigstore");
6105
6118
  const root = yield* Effect.tryPromise({
6106
6119
  try: async () => await getTrustedRoot({ cachePath }),
6107
- catch: (error) => `the Sigstore trust root could not be loaded: ${message$1(error)}`
6120
+ catch: (error) => `the Sigstore trust root could not be loaded: ${message(error)}`
6108
6121
  });
6109
6122
  const verifier = new Verifier(toTrustMaterial(root));
6110
6123
  return (bundle) => Effect.try({
6111
6124
  try: () => verifier.verify(toSignedEntity(bundle), {}),
6112
- catch: (error) => `its Sigstore verification failed: ${message$1(error)}`
6125
+ catch: (error) => `its Sigstore verification failed: ${message(error)}`
6113
6126
  });
6114
6127
  });
6115
6128
  const sourceRepositoryUri = "1.3.6.1.4.1.57264.1.12";
@@ -6344,7 +6357,7 @@ const sourceEvidence = Effect.fn("brew.sourceEvidence")(function* (client, works
6344
6357
  detail: ["Checksum-verified publisher source archive Delta is included.", partial("source archive", source.omitted)].filter((sentence) => sentence !== "").join(" ")
6345
6358
  };
6346
6359
  });
6347
- const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace, identity, plan, key) {
6360
+ const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace, identity, plan) {
6348
6361
  const [formula, source, payload] = yield* Effect.all([
6349
6362
  materializeFormula(workspace, plan.formula),
6350
6363
  sourceEvidence(client, workspace, archives(plan.formula), plan.sourceGap),
@@ -6360,17 +6373,26 @@ const materialize$1 = Effect.fn("brew.materialize")(function* (client, workspace
6360
6373
  goSource?.detail ?? "",
6361
6374
  repository?.detail ?? ""
6362
6375
  ].filter((sentence) => sentence !== "").join(" ");
6363
- return yield* write(workspace, identity, Buffer.concat([
6364
- formula,
6365
- source.diff,
6366
- payload.diff,
6367
- goSource?.diff ?? Buffer.alloc(0),
6368
- repository?.diff ?? Buffer.alloc(0)
6369
- ]), evidence, plan.formula.fromGap === "" ? detail : `The installed version's formula snapshot is unavailable: ${plan.formula.fromGap}. ${detail}`, key);
6376
+ return {
6377
+ diff: Buffer.concat([
6378
+ formula,
6379
+ source.diff,
6380
+ payload.diff,
6381
+ goSource?.diff ?? Buffer.alloc(0),
6382
+ repository?.diff ?? Buffer.alloc(0)
6383
+ ]),
6384
+ evidence,
6385
+ detail: plan.formula.fromGap === "" ? detail : `The installed version's formula snapshot is unavailable: ${plan.formula.fromGap}. ${detail}`
6386
+ };
6370
6387
  });
6371
6388
  const resolve$2 = Effect.fn("brew.resolve")(function* (client, identity) {
6372
6389
  const { artifact, from, to } = identity.delta;
6373
- const kind = identity.context["kind"] ?? "";
6390
+ const { context } = identity;
6391
+ if (context._tag !== "Brew") return yield* new EvidenceUnavailable({
6392
+ source: "brew",
6393
+ issue: `a brew package's audit needs its kind, not ${context._tag}`
6394
+ });
6395
+ const { kind } = context;
6374
6396
  const { source, formula: info } = yield* query(kind, artifact);
6375
6397
  const formula = yield* resolveFormula(client, source, from, to);
6376
6398
  const formulaGap = formula.fromGap === "" ? archiveUnavailableReason(archives(formula)) : "the installed version's formula snapshot is unavailable";
@@ -6389,25 +6411,21 @@ const resolve$2 = Effect.fn("brew.resolve")(function* (client, identity) {
6389
6411
  ...repository === void 0 ? {} : { repository }
6390
6412
  };
6391
6413
  });
6392
- const prepareBrewWith = (exec) => (client, identity) => resolve$2(client, identity).pipe(Effect.provide(exec), Effect.map((plan) => {
6393
- const { key, plan: recorded } = preparationPlan(identity, {
6414
+ const prepareBrewWith = (exec) => (client, identity) => resolve$2(client, identity).pipe(Effect.provide(exec), Effect.map((plan) => ({
6415
+ ...preparationPlan(identity, {
6394
6416
  kind: "brew",
6395
6417
  plan: planKey(plan)
6396
- });
6397
- return {
6398
- key,
6399
- plan: recorded,
6400
- artifacts: artifactsOf(plan),
6401
- materialize: (workspace) => materialize$1(client, workspace, identity, plan, key).pipe(Effect.provide(exec))
6402
- };
6403
- }));
6418
+ }),
6419
+ artifacts: artifactsOf(plan),
6420
+ materialize: (workspace) => materialize$1(client, workspace, identity, plan).pipe(Effect.provide(exec))
6421
+ })));
6404
6422
  const prepareBrew = prepareBrewWith(nodeLayer);
6405
6423
  // src/audit/github-actions.ts
6406
6424
  const unavailable$1 = (error) => new EvidenceUnavailable({
6407
6425
  source: error.repository,
6408
6426
  issue: error.issue
6409
6427
  });
6410
- const workspaceOf$1 = Effect.fn("githubActions.workspace")(function* (workspace, identity, repository, commits, key, options) {
6428
+ const workspaceOf = Effect.fn("githubActions.workspace")(function* (workspace, identity, repository, commits, options) {
6411
6429
  const trees = {
6412
6430
  from: path.join(workspace, files.actionFrom),
6413
6431
  to: path.join(workspace, files.actionTo)
@@ -6432,17 +6450,25 @@ const workspaceOf$1 = Effect.fn("githubActions.workspace")(function* (workspace,
6432
6450
  const { from, to } = identity.delta;
6433
6451
  const commitsSentence = `In ${repository}, ${from} is ${commits.from} and ${to} is ${commits.to}.`;
6434
6452
  const modes = yield* writeFileDiff(workspace, files.actionModesFrom, files.actionModesTo, json(Object.fromEntries(changed.modes.map((change) => [change.path, change.from]))), json(Object.fromEntries(changed.modes.map((change) => [change.path, change.to]))));
6435
- if (changed.omitted !== "") return yield* write(workspace, identity, modes, "metadata-only", `${commitsSentence} The changed files were not fetched: ${changed.omitted}.`, key);
6453
+ if (changed.omitted !== "") return {
6454
+ diff: modes,
6455
+ evidence: "metadata-only",
6456
+ detail: `${commitsSentence} The changed files were not fetched: ${changed.omitted}.`
6457
+ };
6436
6458
  const { diff, omitted } = yield* treeDiff(workspace, files.actionFrom, files.actionTo);
6437
6459
  const submodules = changed.modes.flatMap((change) => change.submodule ? [change.path] : []);
6438
6460
  const gaps = [partial("action", omitted), submodules.length === 0 ? "" : `Submodules changed, and their contents were not fetched: ${submodules.join(", ")}.`].filter((sentence) => sentence !== "");
6439
- return yield* write(workspace, identity, Buffer.concat([diff, modes]), gaps.length === 0 ? "full" : "partial", [commitsSentence, ...gaps].join(" "), key);
6461
+ return {
6462
+ diff: Buffer.concat([diff, modes]),
6463
+ evidence: gaps.length === 0 ? "full" : "partial",
6464
+ detail: [commitsSentence, ...gaps].join(" ")
6465
+ };
6440
6466
  });
6441
6467
  const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubActions.prepare")(function* (_client, identity) {
6442
6468
  const { artifact, from, to } = identity.delta;
6443
6469
  const repository = repositoryOf(artifact);
6444
6470
  const [fromCommit, toCommit] = yield* Effect.all([tagCommit(repository, from, options), tagCommit(repository, to, options)], { concurrency: "unbounded" }).pipe(Effect.mapError(unavailable$1));
6445
- const listed = identity.context["revision"];
6471
+ const listed = identity.context._tag === "Revision" ? identity.context.revision : void 0;
6446
6472
  if (listed !== toCommit) return yield* new EvidenceUnavailable({
6447
6473
  source: repository,
6448
6474
  issue: listed === void 0 ? `upup listed no commit for ${to}; refresh` : `${to} now points at ${toCommit}, not the ${listed} upup listed; refresh`,
@@ -6452,16 +6478,14 @@ const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubAc
6452
6478
  from: fromCommit,
6453
6479
  to: toCommit
6454
6480
  };
6455
- const { key, plan } = preparationPlan(identity, {
6456
- kind: "github-actions",
6457
- repository,
6458
- commits
6459
- });
6460
6481
  return {
6461
- key,
6462
- plan,
6482
+ ...preparationPlan(identity, {
6483
+ kind: "github-actions",
6484
+ repository,
6485
+ commits
6486
+ }),
6463
6487
  artifacts: [`from ${fromCommit}`, `to ${toCommit}`],
6464
- materialize: (workspace) => workspaceOf$1(workspace, identity, repository, commits, key, options)
6488
+ materialize: (workspace) => workspaceOf(workspace, identity, repository, commits, options)
6465
6489
  };
6466
6490
  });
6467
6491
  const prepareGitHubAction = githubActionPreparer(githubRepository);
@@ -6475,10 +6499,6 @@ const Release$2 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6475
6499
  digests: optional(Schema.Struct({ sha256: optional(Schema.String) }))
6476
6500
  }))) });
6477
6501
  const releaseUrl = (name, version) => `https://pypi.org/pypi/${encodeURIComponent(name)}/${encodeURIComponent(version)}/json`;
6478
- const canonicalSha256 = (raw) => {
6479
- const value = raw.trim().toLowerCase();
6480
- return /^[0-9a-f]{64}$/u.test(value) ? value : void 0;
6481
- };
6482
6502
  const formatOf = (url) => {
6483
6503
  try {
6484
6504
  return formatOf$1(url);
@@ -6627,7 +6647,7 @@ const trees = [
6627
6647
  files.wheelFrom,
6628
6648
  files.wheelTo
6629
6649
  ];
6630
- const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace, identity, plans, key) {
6650
+ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace, plans) {
6631
6651
  yield* Effect.tryPromise({
6632
6652
  try: async () => await Promise.all(trees.map(async (tree) => await mkdir(path.join(workspace, tree), {
6633
6653
  recursive: true,
@@ -6647,38 +6667,40 @@ const pypiWorkspace = Effect.fn("pypi.workspace")(function* (client, workspace,
6647
6667
  writeFileDiff(workspace, files.binaryWheelFrom, files.binaryWheelTo, json(binarySummaries(from.compatible_universal_wheel?.archive_manifest)), json(binarySummaries(to.compatible_universal_wheel?.archive_manifest)))
6648
6668
  ], { concurrency: "unbounded" });
6649
6669
  const detail = `Checksum-verified PyPI publisher artifacts; ${pairedDetail("source distributions", from.publisher_sdist, to.publisher_sdist)}; ${pairedDetail("compatible universal wheel candidates", from.compatible_universal_wheel, to.compatible_universal_wheel)}; the installer’s exact resolver selection is not known.`;
6650
- return yield* write(workspace, identity, Buffer.concat([
6651
- sources.diff,
6652
- records,
6653
- sourceBinaries,
6654
- wheels.diff,
6655
- wheelBinaries
6656
- ]), "partial", [
6657
- detail,
6658
- partial("source distribution", sources.omitted),
6659
- partial("wheel", wheels.omitted)
6660
- ].filter((sentence) => sentence !== "").join(" "), key);
6670
+ return {
6671
+ diff: Buffer.concat([
6672
+ sources.diff,
6673
+ records,
6674
+ sourceBinaries,
6675
+ wheels.diff,
6676
+ wheelBinaries
6677
+ ]),
6678
+ evidence: "partial",
6679
+ detail: [
6680
+ detail,
6681
+ partial("source distribution", sources.omitted),
6682
+ partial("wheel", wheels.omitted)
6683
+ ].filter((sentence) => sentence !== "").join(" ")
6684
+ };
6661
6685
  });
6662
6686
  const preparePyPI = Effect.fn("pypi.prepare")(function* (client, identity) {
6663
6687
  const { artifact, from, to } = identity.delta;
6664
- const python = identity.context["python"] ?? "";
6688
+ const python = identity.context._tag === "Python" ? identity.context.python : "";
6665
6689
  const [fromPlan, toPlan] = yield* Effect.all([resolve$1(client, artifact, from, python), resolve$1(client, artifact, to, python)], { concurrency: "unbounded" });
6666
6690
  const plans = {
6667
6691
  from: fromPlan,
6668
6692
  to: toPlan
6669
6693
  };
6670
- const { key, plan } = preparationPlan(identity, {
6671
- kind: "pypi",
6672
- ...plans
6673
- });
6674
6694
  return {
6675
- key,
6676
- plan,
6695
+ ...preparationPlan(identity, {
6696
+ kind: "pypi",
6697
+ ...plans
6698
+ }),
6677
6699
  artifacts: ["from", "to"].flatMap((side) => [plans[side].publisher_sdist, plans[side].compatible_universal_wheel].map((selected) => `${side} ${selected === null ? "none" : `sha256:${selected.sha256}`}`)),
6678
- materialize: (workspace) => pypiWorkspace(client, workspace, identity, plans, key)
6700
+ materialize: (workspace) => pypiWorkspace(client, workspace, plans)
6679
6701
  };
6680
6702
  });
6681
- const Packument$1 = Schema.Struct({
6703
+ const Packument = Schema.Struct({
6682
6704
  "dist-tags": Schema.optionalKey(Schema.NullOr(Schema.Record(Schema.String, Schema.String))),
6683
6705
  versions: Schema.Record(Schema.String, Schema.Unknown)
6684
6706
  });
@@ -6710,7 +6732,7 @@ const resolveSide = (client, host, name, declared, options = {}) => Effect.gen(f
6710
6732
  issue: `declares ${JSON.stringify(declared.spec)}, which is not a registry version range`
6711
6733
  });
6712
6734
  const source = `${npmRegistry}/${npmPath(name)}`;
6713
- const packument = yield* getJson(client, source, Packument$1, { accept: "application/vnd.npm.install-v1+json" }).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6735
+ const packument = yield* getJson(client, source, Packument, { accept: "application/vnd.npm.install-v1+json" }).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6714
6736
  source,
6715
6737
  issue: error.issue
6716
6738
  })));
@@ -6903,87 +6925,102 @@ const materializeDependencies = Effect.fn("npmDependencies.materialize")(functio
6903
6925
  });
6904
6926
  // src/audit/registry-evidence.ts
6905
6927
  const registryDetail = "Both published packages were downloaded and their checksums verified. File types and modes, and any native binaries, are summarised without running them.";
6906
- const registryWorkspace = Effect.fn("evidence.registryWorkspace")(function* (client, workspace, identity, plans, npm, key) {
6928
+ const sentences = (parts) => parts.filter((sentence) => sentence !== "").join(" ");
6929
+ const artifactEvidence = Effect.fn("evidence.artifactEvidence")(function* (client, workspace, plans) {
6907
6930
  const [from, to] = yield* Effect.all([materialize$2(client, plans.from, path.join(workspace, files.from)), materialize$2(client, plans.to, path.join(workspace, files.to))], { concurrency: "unbounded" });
6908
6931
  const [artifacts, trees, binaries] = yield* Effect.all([
6909
6932
  writeFileDiff(workspace, files.artifactFrom, files.artifactTo, manifestJson(from), manifestJson(to)),
6910
6933
  treeDiff(workspace, files.from, files.to),
6911
6934
  writeFileDiff(workspace, files.binaryFrom, files.binaryTo, json(binarySummaries(from)), json(binarySummaries(to)))
6912
6935
  ], { concurrency: "unbounded" });
6913
- const treeDetail = [registryDetail, partial("artifact", trees.omitted)].filter((sentence) => sentence !== "").join(" ");
6914
- if (npm === void 0) return yield* write(workspace, identity, Buffer.concat([
6915
- artifacts,
6916
- trees.diff,
6917
- binaries
6918
- ]), trees.omitted === "" ? "full" : "partial", treeDetail, key);
6919
- const changed = yield* materializeDependencies(client, workspace, npm.dependencies);
6920
- const linked = yield* Effect.forEach(changed.binaries, (binary) => releaseAssetSources(client, binary.repository, [binary.from, binary.to]).pipe(Effect.map(([fromRelease, toRelease]) => ({
6921
- label: binary.label,
6922
- from: fromRelease ?? { gap: "no release lookup" },
6923
- to: toRelease ?? { gap: "no release lookup" }
6924
- }))), { concurrency: 2 });
6925
- const source = yield* materializeSources(workspace, [npm.source, ...linked]);
6926
- return yield* write(workspace, identity, Buffer.concat([
6927
- artifacts,
6928
- trees.diff,
6929
- binaries,
6930
- changed.diff,
6931
- source.diff
6932
- ]), "partial", [
6933
- treeDetail,
6934
- changed.detail,
6935
- source.detail
6936
- ].filter((sentence) => sentence !== "").join(" "), key);
6937
- });
6938
- const npmExtras = Effect.fn("evidence.npmExtras")(function* (client, name, from, to, manifests) {
6939
- const fromManifest = manifests.from;
6940
- const toManifest = manifests.to;
6941
- const dependencies = yield* resolveDependencies(client, manifests, {
6942
- os: process.platform,
6943
- cpu: process.arch
6944
- });
6945
- const verifier = yield* Effect.cached(sigstoreVerifier);
6946
- const [fromSource, toSource] = yield* Effect.all([npmSource(client, name, from, fromManifest, verifier), npmSource(client, name, to, toManifest, verifier)], { concurrency: "unbounded" });
6947
- const { directory } = npmRepository(toManifest);
6948
6936
  return {
6949
- dependencies,
6950
- source: {
6951
- label: name,
6952
- ...directory === void 0 ? {} : { directory },
6953
- from: fromSource,
6954
- to: toSource
6955
- }
6937
+ diff: Buffer.concat([
6938
+ artifacts,
6939
+ trees.diff,
6940
+ binaries
6941
+ ]),
6942
+ evidence: trees.omitted === "" ? "full" : "partial",
6943
+ detail: sentences([registryDetail, partial("artifact", trees.omitted)])
6956
6944
  };
6957
6945
  });
6946
+ const digests = (plans) => [`from ${plans.from.published_digest}`, `to ${plans.to.published_digest}`];
6958
6947
  const prepareRegistry = Effect.fn("evidence.prepareRegistry")(function* (client, identity) {
6959
6948
  const { ecosystem, artifact, from, to } = identity.delta;
6960
- const manifests = ecosystem === "npm" ? yield* Effect.all({
6961
- from: npmVersion(client, artifact, from),
6962
- to: npmVersion(client, artifact, to)
6963
- }, { concurrency: "unbounded" }) : void 0;
6964
- const [fromPlan, toPlan] = yield* Effect.all(manifests === void 0 ? [resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)] : [npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: "unbounded" });
6949
+ const [fromPlan, toPlan] = yield* Effect.all([resolve$3(client, ecosystem, artifact, from), resolve$3(client, ecosystem, artifact, to)], { concurrency: "unbounded" });
6965
6950
  const plans = {
6966
6951
  from: fromPlan,
6967
6952
  to: toPlan
6968
6953
  };
6969
- const npm = manifests === void 0 ? void 0 : yield* npmExtras(client, artifact, from, to, manifests);
6970
- const { key, plan } = preparationPlan(identity, {
6971
- kind: "registry",
6972
- ...plans,
6973
- ...npm
6974
- });
6975
6954
  return {
6976
- key,
6977
- plan,
6978
- artifacts: [`from ${fromPlan.published_digest}`, `to ${toPlan.published_digest}`],
6979
- materialize: (workspace) => registryWorkspace(client, workspace, identity, plans, npm, key)
6955
+ ...preparationPlan(identity, {
6956
+ kind: "registry",
6957
+ ...plans
6958
+ }),
6959
+ artifacts: digests(plans),
6960
+ materialize: (workspace) => artifactEvidence(client, workspace, plans)
6980
6961
  };
6981
6962
  });
6982
6963
  // src/audit/prepare.ts
6983
6964
  const preparers = {
6984
6965
  crates: prepareRegistry,
6985
6966
  go: prepareRegistry,
6986
- npm: prepareRegistry,
6967
+ npm: Effect.fn("evidence.prepareNpm")(function* (client, identity) {
6968
+ const { artifact, from, to } = identity.delta;
6969
+ const manifests = yield* Effect.all({
6970
+ from: npmVersion(client, artifact, from),
6971
+ to: npmVersion(client, artifact, to)
6972
+ }, { concurrency: "unbounded" });
6973
+ const [fromPlan, toPlan] = yield* Effect.all([npmPlan(artifact, from, manifests.from), npmPlan(artifact, to, manifests.to)], { concurrency: "unbounded" });
6974
+ const dependencies = yield* resolveDependencies(client, manifests, {
6975
+ os: process.platform,
6976
+ cpu: process.arch
6977
+ });
6978
+ const verifier = yield* Effect.cached(sigstoreVerifier);
6979
+ const [fromSource, toSource] = yield* Effect.all([npmSource(client, artifact, from, manifests.from, verifier), npmSource(client, artifact, to, manifests.to, verifier)], { concurrency: "unbounded" });
6980
+ const { directory } = npmRepository(manifests.to);
6981
+ const source = {
6982
+ label: artifact,
6983
+ ...directory === void 0 ? {} : { directory },
6984
+ from: fromSource,
6985
+ to: toSource
6986
+ };
6987
+ const plans = {
6988
+ from: fromPlan,
6989
+ to: toPlan
6990
+ };
6991
+ return {
6992
+ ...preparationPlan(identity, {
6993
+ kind: "npm",
6994
+ ...plans,
6995
+ dependencies,
6996
+ source
6997
+ }),
6998
+ artifacts: digests(plans),
6999
+ materialize: (workspace) => Effect.gen(function* () {
7000
+ const artifacts = yield* artifactEvidence(client, workspace, plans);
7001
+ const changed = yield* materializeDependencies(client, workspace, dependencies);
7002
+ const linked = yield* Effect.forEach(changed.binaries, (binary) => releaseAssetSources(client, binary.repository, [binary.from, binary.to]).pipe(Effect.map(([fromRelease, toRelease]) => ({
7003
+ label: binary.label,
7004
+ from: fromRelease ?? { gap: "no release lookup" },
7005
+ to: toRelease ?? { gap: "no release lookup" }
7006
+ }))), { concurrency: 2 });
7007
+ const sources = yield* materializeSources(workspace, [source, ...linked]);
7008
+ return {
7009
+ diff: Buffer.concat([
7010
+ artifacts.diff,
7011
+ changed.diff,
7012
+ sources.diff
7013
+ ]),
7014
+ evidence: "partial",
7015
+ detail: sentences([
7016
+ artifacts.detail,
7017
+ changed.detail,
7018
+ sources.detail
7019
+ ])
7020
+ };
7021
+ })
7022
+ };
7023
+ }),
6987
7024
  pypi: preparePyPI,
6988
7025
  brew: prepareBrew,
6989
7026
  "github-actions": prepareGitHubAction
@@ -7069,7 +7106,7 @@ const within = (effect, what) => effect.pipe(Effect.timeoutOrElse({
7069
7106
  issue: `evidence preparation timed out after 15 minutes`
7070
7107
  }))
7071
7108
  }));
7072
- const ensure = (workspace, preparation) => Effect.gen(function* () {
7109
+ const ensure = (workspace, identity, preparation) => Effect.gen(function* () {
7073
7110
  yield* Effect.tryPromise({
7074
7111
  try: async () => {
7075
7112
  await rm(workspace, {
@@ -7087,23 +7124,21 @@ const ensure = (workspace, preparation) => Effect.gen(function* () {
7087
7124
  issue: issueOf$1(error)
7088
7125
  })
7089
7126
  });
7090
- yield* within(preparation.materialize(workspace), "prepare workspace").pipe(Effect.onError(() => Effect.promise(async () => {
7127
+ yield* within(prepareWorkspace(workspace, identity, preparation), "prepare workspace").pipe(Effect.onError(() => Effect.promise(async () => {
7091
7128
  await rm(workspace, {
7092
7129
  recursive: true,
7093
7130
  force: true
7094
7131
  });
7095
7132
  })));
7096
7133
  });
7097
- const workspaceOf = (file) => path.join(path.dirname(path.dirname(path.dirname(file))), "workspaces", path.basename(path.dirname(file)));
7098
7134
  const sameArtifacts = (left, right) => left.length === right.length && left.every((artifact, index) => artifact === right[index]);
7099
- const reportedFrom = (file, stored) => Option.isSome(stored.report) && Option.isSome(stored.brief) ? Option.some({
7135
+ const reportedFrom = (paths, stored) => Option.isSome(stored.report) && Option.isSome(stored.brief) ? Option.some({
7100
7136
  _tag: "Reported",
7101
7137
  report: stored.report.value,
7102
7138
  evidence: stored.brief.value.evidence,
7103
7139
  detail: stored.brief.value.evidenceDetail,
7104
7140
  model: `${stored.brief.value.provider}/${stored.brief.value.modelId}`,
7105
- file,
7106
- workspace: workspaceOf(file)
7141
+ ...paths
7107
7142
  }) : Option.none();
7108
7143
  const audits$1 = (stored, artifacts) => Option.isSome(stored.brief) && sameArtifacts(stored.brief.value.artifacts, artifacts);
7109
7144
  const children = (directory) => Effect.promise(async () => await readdir(directory).catch(() => []));
@@ -7144,13 +7179,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7144
7179
  });
7145
7180
  const landingFile = (landing) => path.join(root, "landed", landing.mode, `${landingKey(landing)}.json`);
7146
7181
  const land = (identity) => Effect.tryPromise(async () => {
7147
- const file = landingFile({
7148
- mode: identity.mode,
7149
- ecosystem: identity.delta.ecosystem,
7150
- artifact: identity.delta.artifact,
7151
- version: identity.delta.to,
7152
- context: identity.context
7153
- });
7182
+ const file = landingFile(landingAt(identity));
7154
7183
  await mkdir(path.dirname(file), {
7155
7184
  recursive: true,
7156
7185
  mode: 448
@@ -7164,10 +7193,15 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7164
7193
  const note = JSON.parse(text);
7165
7194
  return Predicate.hasProperty(note, "from") && Predicate.hasProperty(note, "to") && Predicate.isString(note.from) && note.to === landing.version ? Option.some(note.from) : Option.none();
7166
7195
  }, () => Option.none()));
7167
- const fileOf = (identity, policyKey) => path.join(root, identity.mode, workspaceKey(identity), `${policyKey}.sqlite`);
7168
- const storedIn = (file) => Effect.scoped(Effect.gen(function* () {
7169
- if (!(yield* tryHold(workspaceOf(file)))) return Option.none();
7170
- return Option.some(yield* stored(file, ai.models));
7196
+ const workspaces = path.join(root, "workspaces");
7197
+ const workspaceFor = (identity) => path.join(workspaces, workspaceKey(identity));
7198
+ const pathsOf = (identity, policyKey) => ({
7199
+ file: path.join(root, identity.mode, workspaceKey(identity), `${policyKey}.sqlite`),
7200
+ workspace: workspaceFor(identity)
7201
+ });
7202
+ const storedIn = (paths) => Effect.scoped(Effect.gen(function* () {
7203
+ if (!(yield* tryHold(paths.workspace))) return Option.none();
7204
+ return Option.some(yield* stored(paths.file, ai.models));
7171
7205
  })).pipe(Effect.orElseSucceed(() => Option.none()));
7172
7206
  const attempt = Effect.fn("Audits.attempt")(function* (identity, options) {
7173
7207
  let transcript = [];
@@ -7186,8 +7220,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7186
7220
  const prompt = promptFor(identity.mode);
7187
7221
  const policyKey$1 = policyKey(prompt, setup.value);
7188
7222
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7189
- const file = fileOf(identity, policyKey$1);
7190
- const workspace = workspaceOf(file);
7223
+ const paths = pathsOf(identity, policyKey$1);
7224
+ const { file, workspace } = paths;
7191
7225
  yield* hold(workspace);
7192
7226
  const model = `${setup.value.provider}/${setup.value.modelId}`;
7193
7227
  if (existsSync(file)) {
@@ -7195,7 +7229,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7195
7229
  brief: Option.none(),
7196
7230
  report: Option.none()
7197
7231
  })));
7198
- const found = reportedFrom(file, stored$1);
7232
+ const found = reportedFrom(paths, stored$1);
7199
7233
  if (!options.force && Option.isSome(found) && audits$1(stored$1, preparation.artifacts)) {
7200
7234
  yield* land(identity);
7201
7235
  return found.value;
@@ -7209,7 +7243,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7209
7243
  }, file);
7210
7244
  }
7211
7245
  yield* Effect.addFinalizer(() => prune(workspace));
7212
- yield* ensure(workspace, preparation);
7246
+ yield* ensure(workspace, identity, preparation);
7213
7247
  const meta = yield* readMeta(workspace);
7214
7248
  yield* Effect.tryPromise({
7215
7249
  try: async () => await mkdir(path.dirname(file), {
@@ -7274,8 +7308,7 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7274
7308
  evidence: meta.evidence,
7275
7309
  detail: meta.evidence_detail,
7276
7310
  model,
7277
- file,
7278
- workspace
7311
+ ...paths
7279
7312
  };
7280
7313
  });
7281
7314
  const audit = (identity, options) => Effect.sync(() => {
@@ -7294,8 +7327,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7294
7327
  const recall = Effect.fn("Audits.recall")(function* (identity) {
7295
7328
  const setup = yield* ai.setup;
7296
7329
  if (Option.isNone(setup)) return Option.none();
7297
- const file = fileOf(identity, policyKey(promptFor(identity.mode), setup.value));
7298
- const found = Option.flatMap(yield* storedIn(file), (stored) => reportedFrom(file, stored));
7330
+ const paths = pathsOf(identity, policyKey(promptFor(identity.mode), setup.value));
7331
+ const found = Option.flatMap(yield* storedIn(paths), (stored) => reportedFrom(paths, stored));
7299
7332
  if (Option.isSome(found)) yield* land(identity);
7300
7333
  return found;
7301
7334
  });
@@ -7303,26 +7336,26 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7303
7336
  const setup = yield* ai.setup;
7304
7337
  if (Option.isNone(setup)) return Option.none();
7305
7338
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7306
- const file = fileOf(identity, policyKey(promptFor(identity.mode), setup.value));
7307
- return Option.filter(yield* storedIn(file), (stored) => audits$1(stored, preparation.artifacts)).pipe(Option.flatMap((stored) => reportedFrom(file, stored)));
7339
+ const paths = pathsOf(identity, policyKey(promptFor(identity.mode), setup.value));
7340
+ return Option.filter(yield* storedIn(paths), (stored) => audits$1(stored, preparation.artifacts)).pipe(Option.flatMap((stored) => reportedFrom(paths, stored)));
7308
7341
  }).pipe(Effect.catch((error) => Effect.succeedSome({
7309
7342
  _tag: "Failed",
7310
7343
  issue: `the update's artifacts could not be resolved: ${issueOf$1(error)}`,
7311
7344
  transcript: []
7312
7345
  })));
7313
7346
  const changes = (identity) => Effect.scoped(Effect.gen(function* () {
7314
- const workspace = path.join(root, "workspaces", workspaceKey(identity));
7347
+ const workspace = workspaceFor(identity);
7315
7348
  const held = yield* tryHold(workspace);
7316
7349
  if (yield* readable(workspace, identity)) return yield* read$1(workspace);
7317
7350
  if (!held) return yield* new ChangesUnavailable({ issue: "A running Audit is preparing this evidence." });
7318
7351
  const preparation = yield* within(prepare(client, identity), "resolve evidence");
7319
- yield* ensure(workspace, preparation);
7352
+ yield* ensure(workspace, identity, preparation);
7320
7353
  const view = yield* read$1(workspace);
7321
7354
  yield* prune(workspace);
7322
7355
  return view;
7323
7356
  })).pipe(Effect.mapError((error) => new ChangesUnavailable({ issue: issueOf$1(error) })));
7324
7357
  const majors = (identity) => Effect.gen(function* () {
7325
- const workspace = path.join(root, "workspaces", workspaceKey(identity));
7358
+ const workspace = workspaceFor(identity);
7326
7359
  if (!(yield* readable(workspace, identity))) return [];
7327
7360
  return yield* readMajors(workspace);
7328
7361
  }).pipe(Effect.orElseSucceed(() => []));
@@ -7335,9 +7368,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7335
7368
  mode: 448
7336
7369
  }));
7337
7370
  const started = yield* Clock.currentTimeMillis;
7338
- const base = path.join(root, "workspaces");
7339
- yield* Effect.forEach((yield* children(base)).filter((name) => !name.endsWith(".lock")), (name) => {
7340
- const workspace = path.join(base, name);
7371
+ yield* Effect.forEach((yield* children(workspaces)).filter((name) => !name.endsWith(".lock")), (name) => {
7372
+ const workspace = path.join(workspaces, name);
7341
7373
  return Effect.scoped(Effect.gen(function* () {
7342
7374
  if (!(yield* tryHold(workspace))) return;
7343
7375
  const prepared = yield* Effect.promise(async () => await stat(path.join(workspace, files.meta)).then((info) => info.mtimeMs, () => 0));
@@ -7368,8 +7400,8 @@ const layer$5 = (cacheDir) => Layer.effect(Audits, Effect.gen(function* () {
7368
7400
  changes,
7369
7401
  majors,
7370
7402
  landed,
7371
- transcript: (file) => Effect.scoped(Effect.gen(function* () {
7372
- if (!(yield* tryHold(workspaceOf(file)))) return yield* new AgentFailed({ issue: "the Audit is running" });
7403
+ transcript: ({ file, workspace }) => Effect.scoped(Effect.gen(function* () {
7404
+ if (!(yield* tryHold(workspace))) return yield* new AgentFailed({ issue: "the Audit is running" });
7373
7405
  return yield* transcript(file, ai.models);
7374
7406
  }))
7375
7407
  }));
@@ -7385,6 +7417,18 @@ const replaceText = Effect.fn("replaceText")(function* (file, text) {
7385
7417
  yield* fs.writeFileString(temporary, text, { mode }).pipe(Effect.mapError(fileFailed("write", temporary)));
7386
7418
  yield* fs.rename(temporary, file).pipe(Effect.mapError(fileFailed("replace", file)));
7387
7419
  });
7420
+ const editFiles = Effect.fn("editFiles")(function* (root, edits) {
7421
+ const edited = /* @__PURE__ */ new Map();
7422
+ for (const { file, edit } of edits) {
7423
+ const next = edit(edited.get(file) ?? (yield* readText(path.join(root, file))));
7424
+ if (Result.isFailure(next)) return yield* new EditRefused({
7425
+ manifest: file,
7426
+ issue: next.failure
7427
+ });
7428
+ edited.set(file, next.success);
7429
+ }
7430
+ for (const [file, text] of edited) yield* replaceText(path.join(root, file), text);
7431
+ });
7388
7432
  // src/project/find-up.ts
7389
7433
  const findUp = Effect.fn("findUp")(function* (start, name) {
7390
7434
  const fs = yield* FileSystem.FileSystem;
@@ -7782,7 +7826,6 @@ const publicTags = (url) => tags(url, { timeout: lsRemoteTimeout }).pipe(Effect.
7782
7826
  const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7783
7827
  const exec = yield* Exec;
7784
7828
  const fs = yield* FileSystem.FileSystem;
7785
- const read = (file) => readText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7786
7829
  const scanned = Effect.gen(function* () {
7787
7830
  const files = yield* workflowFiles(root).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7788
7831
  return {
@@ -7869,17 +7912,10 @@ const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function*
7869
7912
  expected: pinned && pkg.revision !== void 0 ? `${target} at ${short(pkg.revision)}` : target,
7870
7913
  ...sha === void 0 ? {} : { offered: `${target} at ${short(sha)}` }
7871
7914
  });
7872
- const edited = /* @__PURE__ */ new Map();
7873
- for (const use of group.uses) {
7874
- const text = edited.get(use.file) ?? (yield* read(path.join(root, use.file)));
7875
- const next = pinned ? editYamlScalar(text, use.path, `${use.name}@${sha}`, movedComment(use.comment, target)) : editYamlScalar(text, use.path, `${use.name}@${target}`);
7876
- if (Result.isFailure(next)) return yield* new EditRefused({
7877
- manifest: use.file,
7878
- issue: next.failure
7879
- });
7880
- edited.set(use.file, next.success);
7881
- }
7882
- for (const [file, text] of edited) yield* replaceText(path.join(root, file), text).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7915
+ yield* editFiles(root, group.uses.map((use) => ({
7916
+ file: use.file,
7917
+ edit: (text) => pinned ? editYamlScalar(text, use.path, `${use.name}@${sha}`, movedComment(use.comment, target)) : editYamlScalar(text, use.path, `${use.name}@${target}`)
7918
+ }))).pipe(Effect.provideService(FileSystem.FileSystem, fs));
7883
7919
  });
7884
7920
  return {
7885
7921
  name: providerName$1,
@@ -8757,19 +8793,11 @@ const Manifest$2 = Schema.fromJsonString(Schema.Struct({
8757
8793
  }));
8758
8794
  const zipName = "upup-sparkle.zip";
8759
8795
  const manifestName = "upup-sparkle.json";
8760
- const seaKeys = {
8761
- zip: `sparkle/${zipName}`,
8762
- manifest: `sparkle/${manifestName}`
8763
- };
8764
8796
  const decode = (text, zip) => Schema.decodeEffect(Manifest$2)(text).pipe(Effect.map((manifest) => ({
8765
8797
  zip,
8766
8798
  ...manifest
8767
8799
  })), Effect.option);
8768
8800
  const folder = () => {
8769
- if (isSea()) {
8770
- const assets = process.env["OTUI_ASSET_ROOT"];
8771
- return assets === void 0 ? void 0 : path.join(assets, path.dirname(seaKeys.zip));
8772
- }
8773
8801
  const packageJson = findPackageJSON(import.meta.url);
8774
8802
  return packageJson === void 0 ? void 0 : path.join(path.dirname(packageJson), "native", "dist");
8775
8803
  };
@@ -8851,9 +8879,16 @@ ready() {
8851
8879
  return 1
8852
8880
  fi
8853
8881
  }
8854
- # A copy of what upup handed over, where only this user can write; a failure is step $1's.
8882
+ # A copy of what upup handed over, where only this user can write; a failure is step $1's. Others
8883
+ # may also read a shared one ($2): as root, Sparkle starts its progress agent from upup-sparkle's
8884
+ # framework as the user, so it goes in /var/tmp, whose sticky bit keeps others from moving it. An
8885
+ # app's copy stays private, since its owner gets it after it is checked.
8855
8886
  secure() {
8856
- private=$(mktemp -d 2>"$log") || { private=; fail "$1"; return 1; }
8887
+ if [ "$mode" = root ] && [ "$2" = shared ]; then
8888
+ private=$(mktemp -d /var/tmp/upup.XXXXXX 2>"$log") && chmod 755 "$private" 2>"$log" || { fail "$1"; return 1; }
8889
+ else
8890
+ private=$(mktemp -d 2>"$log") || { fail "$1"; return 1; }
8891
+ fi
8857
8892
  payload="$private/$(basename "$source")"
8858
8893
  "$ditto" "$source" "$payload" 2>"$log" || { fail "$1"; return 1; }
8859
8894
  }
@@ -8867,7 +8902,7 @@ app() {
8867
8902
  if [ -e "$old" ] && [ ! -e "$dest" ]; then recover; return; fi
8868
8903
  rm -rf "$old" "$new" 2>"$log" || { fail cleanup; return; }
8869
8904
  if [ "$mode" = root ]; then
8870
- secure copy || return
8905
+ secure copy private || return
8871
8906
  "$codesign" --verify --deep --strict -R "$check" "$payload" 2>"$log" || { fail recheck; return; }
8872
8907
  owner=$("$stat" -f %u:%g "$dest" 2>"$log") && "$chown" -R "$owner" "$payload" 2>"$log" || { fail copy; return; }
8873
8908
  staged=$payload
@@ -8897,7 +8932,7 @@ app() {
8897
8932
  # upup-sparkle's exit status: 0 installed, 2 nothing to install, 3 Sparkle would install another
8898
8933
  # build, 4 the app is open; its last line on standard error says why.
8899
8934
  sparkle() {
8900
- secure helper || return
8935
+ secure helper shared || return
8901
8936
  sum=$("$shasum" -a 256 "$payload" 2>"$log") || { fail helper; return; }
8902
8937
  [ "\${sum%% *}" = "$check" ] || { echo "its sha256 is \${sum%% *}, not $check" >"$log"; fail helper; return; }
8903
8938
  "$ditto" -x -k "$payload" "$private" 2>"$log" || { fail helper; return; }
@@ -9021,7 +9056,7 @@ const versionsOf = (kind, installed, offer) => {
9021
9056
  target: withBuild(offer.short, offer.version)
9022
9057
  };
9023
9058
  };
9024
- const caskInfo = brew$1([
9059
+ const caskInfo = brew([
9025
9060
  "info",
9026
9061
  "--json=v2",
9027
9062
  "--installed",
@@ -9283,7 +9318,7 @@ const makeWith = (options) => Effect.gen(function* () {
9283
9318
  const queue = yield* Ref.make([]);
9284
9319
  const using = options.tools ?? tools$2;
9285
9320
  const helper = options.sparkle === void 0 ? locate$3.pipe(Effect.provideService(FileSystem.FileSystem, fs)) : Effect.succeed(options.sparkle);
9286
- const sparkleOf = (pkg, install, build) => {
9321
+ const sparkleHelper = (pkg, install) => {
9287
9322
  const refused = (reason) => new InstallRefused({
9288
9323
  package: pkg.name,
9289
9324
  reason
@@ -9294,10 +9329,8 @@ const makeWith = (options) => Effect.gen(function* () {
9294
9329
  if (Option.isNone(found)) return yield* refused("this copy of upup has no upup-sparkle, which installs Sparkle apps' updates; build it with `vp run sparkle`");
9295
9330
  if (install.installationType === "package" && found.value.team === null) return yield* refused("its update is an installer package, which Sparkle installs only through an upup-sparkle signed with a Developer ID, and this copy of upup's is signed ad hoc");
9296
9331
  return {
9297
- kind: "sparkle",
9298
- helper: found.value,
9299
- build,
9300
- feed: install.feed
9332
+ ...install,
9333
+ helper: found.value
9301
9334
  };
9302
9335
  });
9303
9336
  };
@@ -9322,21 +9355,23 @@ const makeWith = (options) => Effect.gen(function* () {
9322
9355
  const inside = yield* hosts.pipe(Effect.mapError((error) => refused(`upup could not tell which app it runs in, so it risks none: ${describeError(error)}`)));
9323
9356
  if (inside.paths.has(pkg.id) || inside.ids.has(bundle.bundleId)) return yield* refused(`upup is running inside ${pkg.name}; update it from another terminal app`);
9324
9357
  yield* unchanged$1(pkg, plist);
9325
- const planned = install.kind === "sparkle" ? yield* sparkleOf(pkg, install, offer.version) : install;
9358
+ const planned = install.kind === "sparkle" ? yield* sparkleHelper(pkg, install) : install;
9326
9359
  const writable = yield* Effect.isSuccess(Effect.all([fs.access(pkg.id, { writable: true }), fs.access(path.dirname(pkg.id), { writable: true })]));
9327
- const ownsApp = install.kind === "sparkle" && (yield* owned(pkg.id));
9328
9360
  const quitAllowed = permission?.quitApps === true;
9329
9361
  yield* gate(pkg, bundle.bundleId, quitAllowed);
9330
9362
  const team = yield* teamOf(pkg.id);
9331
9363
  if (team === void 0) return yield* refused("it is not signed by a developer team, so upup cannot check its update");
9332
- const version = {
9333
- key: install.kind === "sparkle" ? "CFBundleVersion" : "CFBundleShortVersionString",
9334
- value: offer.version
9335
- };
9336
9364
  const prepared = planned.kind === "sparkle" ? {
9337
- ...planned,
9338
- requirement: requirement(bundle.bundleId, team)
9339
- } : yield* stage(client, {
9365
+ update: {
9366
+ kind: "sparkle",
9367
+ helper: planned.helper,
9368
+ build: offer.version,
9369
+ feed: planned.feed,
9370
+ requirement: requirement(bundle.bundleId, team)
9371
+ },
9372
+ dir: yield* room(pkg.name),
9373
+ root: !writable || planned.installationType === "package" || !(yield* owned(pkg.id))
9374
+ } : yield* Effect.map(stage(client, {
9340
9375
  name: pkg.name,
9341
9376
  url: planned.url,
9342
9377
  ...planned.size === void 0 ? {} : { size: planned.size },
@@ -9345,18 +9380,19 @@ const makeWith = (options) => Effect.gen(function* () {
9345
9380
  team,
9346
9381
  arch: listing.here.arch,
9347
9382
  version: offer.version
9348
- });
9349
- const dir = prepared.kind === "app" ? prepared.dir : yield* room(pkg.name);
9383
+ }), (staged) => ({
9384
+ update: staged,
9385
+ dir: staged.dir,
9386
+ root: !writable
9387
+ }));
9350
9388
  yield* Ref.update(queue, (all) => [...all, {
9351
9389
  pkg,
9352
9390
  bundleId: bundle.bundleId,
9353
9391
  executable: path.join("Contents", "MacOS", bundle.executable ?? pkg.name),
9354
9392
  installed: plist,
9355
- update: prepared,
9356
- dir,
9393
+ ...prepared,
9357
9394
  quitAllowed,
9358
- root: !writable || install.kind === "sparkle" && (install.installationType === "package" || !ownsApp),
9359
- version,
9395
+ version: offer.version,
9360
9396
  team
9361
9397
  }]);
9362
9398
  }, provide);
@@ -9366,9 +9402,10 @@ const makeWith = (options) => Effect.gen(function* () {
9366
9402
  if (now.value === item.installed) return replaceable(now.value);
9367
9403
  const { info, digest } = yield* readInfoStable(item.pkg.id);
9368
9404
  if (Option.isNone(info) || digest !== now.value || info.value.CFBundleIdentifier !== item.bundleId || (yield* teamOf(item.pkg.id)) !== item.team) return changed;
9369
- const version = info.value[item.version.key];
9370
- if (version === item.version.value) return alreadyUpdated;
9371
- return version !== void 0 && (item.version.key === "CFBundleVersion" ? isNewer(item.version.value, version) : isNewerRelease(item.version.value, version)) ? replaceable(now.value) : changed;
9405
+ const sparkle = item.update.kind === "sparkle";
9406
+ const version = sparkle ? info.value.CFBundleVersion : info.value.CFBundleShortVersionString;
9407
+ if (version === item.version) return alreadyUpdated;
9408
+ return version !== void 0 && (sparkle ? isNewer(item.version, version) : isNewerRelease(item.version, version)) ? replaceable(now.value) : changed;
9372
9409
  }).pipe(Effect.orElseSucceed(() => changed));
9373
9410
  const ready = (item) => Effect.gen(function* () {
9374
9411
  const now = yield* installedNow(item);
@@ -9473,7 +9510,7 @@ const makeWith = (options) => Effect.gen(function* () {
9473
9510
  uninstall: Effect.fn("Apps.uninstall")(function* (pkg) {
9474
9511
  const token = (yield* Ref.get(memory))?.rows.get(pkg.id)?.cask;
9475
9512
  if (token === void 0) return yield* new Unsupported({ reason: unremovable$1 });
9476
- yield* exec.stdout(brew$1([
9513
+ yield* exec.stdout(brew([
9477
9514
  "uninstall",
9478
9515
  "--cask",
9479
9516
  token
@@ -9927,6 +9964,11 @@ const matchesGoPattern = (pattern, path) => {
9927
9964
  const prefix = segments.slice(0, parts.length).join("/");
9928
9965
  return new RegExp(`^${parts.map((part) => RegExp.escape(part).replaceAll("\\*", "[^/]*").replaceAll("\\?", "[^/]")).join("/")}$`, "u").test(prefix);
9929
9966
  };
9967
+ const GoProxyEnv = Schema.Struct({
9968
+ GOPROXY: Schema.optionalKey(Schema.String),
9969
+ GOPRIVATE: Schema.optionalKey(Schema.String),
9970
+ GONOPROXY: Schema.optionalKey(Schema.String)
9971
+ });
9930
9972
  const privateGoModule = (env, path) => {
9931
9973
  if (goPatterns(env.GONOPROXY === void 0 || env.GONOPROXY === "" ? env.GOPRIVATE ?? "" : env.GONOPROXY).some((pattern) => matchesGoPattern(pattern, path))) return "a GOPRIVATE module";
9932
9974
  const [proxy] = goPatterns((env.GOPROXY ?? "https://proxy.golang.org,direct").replaceAll("|", ","));
@@ -9944,7 +9986,21 @@ const privatePypiIndex = (env = process.env) => {
9944
9986
  const index = env["UV_DEFAULT_INDEX"] ?? env["UV_INDEX_URL"] ?? env["PIP_INDEX_URL"];
9945
9987
  return index === void 0 || index === "" || publicPypi.has(trailing(index)) ? void 0 : index;
9946
9988
  };
9989
+ const withPypiOrigin = (index) => (pkg) => index === void 0 ? pkg : {
9990
+ ...pkg,
9991
+ privateRegistry: index
9992
+ };
9947
9993
  // src/go/go-json.ts
9994
+ const go = (cwd, args, env = {}) => ({
9995
+ command: "go",
9996
+ args,
9997
+ cwd,
9998
+ env: {
9999
+ GOWORK: "off",
10000
+ GIT_TERMINAL_PROMPT: "0",
10001
+ ...env
10002
+ }
10003
+ });
9948
10004
  const decodeGoJson = (schema, command) => decodeJson(Schema.toCodecGoJson(schema), command);
9949
10005
  const jsonObjects = (text) => {
9950
10006
  const objects = [];
@@ -9973,22 +10029,14 @@ const jsonObjects = (text) => {
9973
10029
  const GoEnv = Schema.Struct({
9974
10030
  GOBIN: Schema.String,
9975
10031
  GOPATH: Schema.String,
9976
- GOPROXY: Schema.optionalKey(Schema.String),
9977
- GOPRIVATE: Schema.optionalKey(Schema.String),
9978
- GONOPROXY: Schema.optionalKey(Schema.String)
10032
+ ...GoProxyEnv.fields
9979
10033
  });
9980
- const envKeys = [
9981
- "GOBIN",
9982
- "GOPATH",
9983
- "GOPROXY",
9984
- "GOPRIVATE",
9985
- "GONOPROXY"
9986
- ];
10034
+ const envKeys = Object.keys(GoEnv.fields);
9987
10035
  const BuildInfo = Schema.Struct({
9988
- Path: Schema.String,
10036
+ Path: Schema.optionalKey(Schema.String),
9989
10037
  Main: Schema.Struct({
9990
- Path: Schema.String,
9991
- Version: Schema.String,
10038
+ Path: Schema.optionalKey(Schema.String),
10039
+ Version: Schema.optionalKey(Schema.String),
9992
10040
  Sum: Schema.optionalKey(Schema.String),
9993
10041
  Replace: Schema.optionalKey(Schema.Unknown)
9994
10042
  })
@@ -9998,36 +10046,27 @@ const ModuleQuery = Schema.Struct({
9998
10046
  Version: Schema.String,
9999
10047
  Error: Schema.optionalKey(Schema.Struct({ Err: Schema.String }))
10000
10048
  });
10001
- const go = (args, env = {}) => ({
10002
- command: "go",
10003
- args,
10004
- cwd: "/",
10005
- env: {
10006
- GOWORK: "off",
10007
- GIT_TERMINAL_PROMPT: "0",
10008
- ...env
10009
- }
10010
- });
10011
10049
  const executableName = (importPath) => {
10012
10050
  const elements = importPath.split("/");
10013
10051
  const last = elements.at(-1) ?? importPath;
10014
10052
  return /^v\d+$/u.test(last) && elements.length > 1 ? elements.at(-2) ?? last : last;
10015
10053
  };
10016
10054
  const fromBuildInfo = (info, file) => {
10017
- const { Main: main } = info;
10018
- return main.Sum !== void 0 && main.Sum !== "" && main.Replace === void 0 && semver.valid(main.Version) !== null && (info.Path === main.Path || info.Path.startsWith(`${main.Path}/`)) && executableName(info.Path) === file ? Option.some({
10055
+ const { Path: importPath, Main: main } = info;
10056
+ const { Path: module, Version: version } = main;
10057
+ return importPath !== void 0 && module !== void 0 && version !== void 0 && main.Sum !== void 0 && main.Replace === void 0 && semver.valid(version) !== null && (importPath === module || importPath.startsWith(`${module}/`)) && executableName(importPath) === file ? Option.some({
10019
10058
  provider: "go-install",
10020
- id: info.Path,
10059
+ id: importPath,
10021
10060
  name: file,
10022
10061
  ecosystem: "go",
10023
- artifact: main.Path,
10024
- current: main.Version
10062
+ artifact: module,
10063
+ current: version
10025
10064
  }) : Option.none();
10026
10065
  };
10027
10066
  const make$8 = Effect.gen(function* () {
10028
10067
  const exec = yield* Exec;
10029
10068
  const fs = yield* FileSystem.FileSystem;
10030
- const envInvocation = go([
10069
+ const envInvocation = go("/", [
10031
10070
  "env",
10032
10071
  "-json",
10033
10072
  ...envKeys
@@ -10035,7 +10074,7 @@ const make$8 = Effect.gen(function* () {
10035
10074
  const goEnv = yield* Effect.cached(exec.stdout(envInvocation).pipe(Effect.flatMap(decodeGoJson(GoEnv, commandLine(envInvocation)))));
10036
10075
  const binDir = goEnv.pipe(Effect.map((env) => env.GOBIN || path.join(env.GOPATH.split(":")[0] ?? env.GOPATH, "bin")));
10037
10076
  const inspect = Effect.fn("GoInstall.inspect")(function* (dir, file) {
10038
- const invocation = go([
10077
+ const invocation = go("/", [
10039
10078
  "version",
10040
10079
  "-m",
10041
10080
  "-json",
@@ -10057,7 +10096,7 @@ const make$8 = Effect.gen(function* () {
10057
10096
  });
10058
10097
  const latest = Effect.fn("GoInstall.latest")(function* (modules) {
10059
10098
  if (modules.length === 0) return /* @__PURE__ */ new Map();
10060
- const invocation = go([
10099
+ const invocation = go("/", [
10061
10100
  "list",
10062
10101
  "-m",
10063
10102
  "-e",
@@ -10107,7 +10146,7 @@ const make$8 = Effect.gen(function* () {
10107
10146
  }),
10108
10147
  update: Effect.fn("GoInstall.update")(function* (pkg, target) {
10109
10148
  const dir = yield* binDir;
10110
- yield* exec.stdout(go([
10149
+ yield* exec.stdout(go("/", [
10111
10150
  "install",
10112
10151
  "--",
10113
10152
  `${pkg.id}@${target}`
@@ -10140,27 +10179,12 @@ const WorkFile = Schema.Struct({
10140
10179
  Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))),
10141
10180
  Replace: Schema.optionalKey(Schema.Array(Schema.Struct({ Old: Schema.Struct({ Path: Schema.String }) })))
10142
10181
  });
10143
- const goEnv = {
10144
- GOWORK: "off",
10145
- GIT_TERMINAL_PROMPT: "0"
10146
- };
10147
- const GoProxyEnv = Schema.Struct({
10148
- GOPROXY: Schema.optionalKey(Schema.String),
10149
- GOPRIVATE: Schema.optionalKey(Schema.String),
10150
- GONOPROXY: Schema.optionalKey(Schema.String)
10151
- });
10152
10182
  const directRequirements = (mod, local) => {
10153
10183
  const replaced = new Set((mod.Replace ?? []).map((replace) => replace.Old.Path));
10154
10184
  return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path) && !local.has(requirement.Path));
10155
10185
  };
10156
- const goIn = (cwd, args) => ({
10157
- command: "go",
10158
- args,
10159
- cwd,
10160
- env: goEnv
10161
- });
10162
10186
  const readModFile = (exec, cwd) => {
10163
- const edit = goIn(cwd, [
10187
+ const edit = go(cwd, [
10164
10188
  "mod",
10165
10189
  "edit",
10166
10190
  "-json"
@@ -10170,13 +10194,13 @@ const readModFile = (exec, cwd) => {
10170
10194
  const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set()) => Effect.gen(function* () {
10171
10195
  const exec = yield* Exec;
10172
10196
  const manifest = path.join(place, path.relative(root, path.join(moduleDir, "go.mod")));
10173
- const go = (args) => goIn(moduleDir, args);
10197
+ const inModule = (args) => go(moduleDir, args);
10174
10198
  const requirements = Effect.gen(function* () {
10175
10199
  return directRequirements(yield* readModFile(exec, moduleDir), local);
10176
10200
  });
10177
10201
  const selected = Effect.fn("GoModule.selected")(function* (paths, withUpdates) {
10178
10202
  if (paths.length === 0) return /* @__PURE__ */ new Map();
10179
- const list = go([
10203
+ const list = inModule([
10180
10204
  "list",
10181
10205
  "-m",
10182
10206
  ...withUpdates ? ["-u"] : [],
@@ -10202,12 +10226,10 @@ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set())
10202
10226
  selector: requirement.Version
10203
10227
  }
10204
10228
  });
10205
- const env = go([
10229
+ const env = inModule([
10206
10230
  "env",
10207
10231
  "-json",
10208
- "GOPROXY",
10209
- "GOPRIVATE",
10210
- "GONOPROXY"
10232
+ ...Object.keys(GoProxyEnv.fields)
10211
10233
  ]);
10212
10234
  const origin = exec.stdout(env).pipe(Effect.flatMap(decodeGoJson(GoProxyEnv, commandLine(env))), Effect.map(withGoOrigin));
10213
10235
  const list = Effect.gen(function* () {
@@ -10238,16 +10260,16 @@ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set())
10238
10260
  return Option.some(toPackage(requirement, entries.get(requirement.Path)));
10239
10261
  });
10240
10262
  const update = Effect.fn("GoModule.update")(function* (pkg, target) {
10241
- yield* exec.stdout(go(["get", `${pkg.id}@${target}`]));
10263
+ yield* exec.stdout(inModule(["get", `${pkg.id}@${target}`]));
10242
10264
  });
10243
10265
  const uninstall = Effect.fn("GoModule.uninstall")(function* (pkg) {
10244
- yield* exec.stdout(go([
10266
+ yield* exec.stdout(inModule([
10245
10267
  "mod",
10246
10268
  "edit",
10247
10269
  `-droprequire=${pkg.id}`
10248
10270
  ]));
10249
10271
  });
10250
- const reconcile = () => exec.stdout(go(["mod", "tidy"])).pipe(Effect.as(void 0));
10272
+ const reconcile = () => exec.stdout(inModule(["mod", "tidy"])).pipe(Effect.as(void 0));
10251
10273
  return {
10252
10274
  name: manifest,
10253
10275
  mode: "project",
@@ -10663,7 +10685,6 @@ const makeVp = makeGlobal({
10663
10685
  list: [
10664
10686
  "list",
10665
10687
  "-g",
10666
- "--depth=0",
10667
10688
  "--json"
10668
10689
  ],
10669
10690
  outdated: [
@@ -10796,7 +10817,6 @@ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10796
10817
  const client = yield* HttpClient.HttpClient;
10797
10818
  const context = yield* Effect.context();
10798
10819
  const scanned = scan(root, dir, nested).pipe(Effect.provideContext(context));
10799
- const read = (file) => readText(path.join(root, file)).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10800
10820
  const list = Effect.gen(function* () {
10801
10821
  const pins = yield* scanned;
10802
10822
  const notices = [];
@@ -10835,19 +10855,15 @@ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10835
10855
  manifest: pkg.declaration?.manifest ?? "",
10836
10856
  issue: `Node.js ${pkg.current} is no longer pinned there`
10837
10857
  });
10838
- const edited = /* @__PURE__ */ new Map();
10839
- for (const pin of group) {
10840
- const text = edited.get(pin.file) ?? (yield* read(pin.file));
10841
- const next = `${pin.prefix}${target}`;
10842
- const { parts, at } = versionLine(text);
10843
- const moved = pin.path === void 0 ? Result.succeed(parts.map((part, index) => index === at ? part.replace(`${pin.prefix}${pin.version}`, next) : part).join("")) : editYamlScalar(text, pin.path, next);
10844
- if (Result.isFailure(moved)) return yield* new EditRefused({
10845
- manifest: pin.file,
10846
- issue: moved.failure
10847
- });
10848
- edited.set(pin.file, moved.success);
10849
- }
10850
- for (const [file, text] of edited) yield* replaceText(path.join(root, file), text).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10858
+ yield* editFiles(root, group.map((pin) => ({
10859
+ file: pin.file,
10860
+ edit: (text) => {
10861
+ const next = `${pin.prefix}${target}`;
10862
+ if (pin.path !== void 0) return editYamlScalar(text, pin.path, next);
10863
+ const { parts, at } = versionLine(text);
10864
+ return Result.succeed(parts.map((part, index) => index === at ? part.replace(`${pin.prefix}${pin.version}`, next) : part).join(""));
10865
+ }
10866
+ }))).pipe(Effect.provideService(FileSystem.FileSystem, fs));
10851
10867
  });
10852
10868
  return {
10853
10869
  name: providerName,
@@ -11158,7 +11174,7 @@ const make$5 = (root, manager, place = "", claimed = []) => Effect.gen(function*
11158
11174
  message: line.failure
11159
11175
  });
11160
11176
  const target = Result.getOrUndefined(line);
11161
- if (target === void 0 || !isNewerStable(pkg.current, target)) return pkg;
11177
+ if (target === void 0) return pkg;
11162
11178
  const selector = parseSelector(pkg.name, pkg.declaration?.selector ?? "");
11163
11179
  if (selector === void 0 || moveTo(selector, target) === void 0) {
11164
11180
  notices.push({
@@ -11829,13 +11845,10 @@ const make = Effect.gen(function* () {
11829
11845
  "--show-python"
11830
11846
  ])).pipe(Effect.map(parseTools))], { concurrency: "unbounded" });
11831
11847
  const targets = new Map(outdated.flatMap((tool) => tool.fromPyPI && tool.latest !== void 0 ? [[tool.pkg.id, tool.latest]] : []));
11832
- const index = privatePypiIndex();
11848
+ const origin = withPypiOrigin(privatePypiIndex());
11833
11849
  return {
11834
11850
  packages: packages.map((listed) => {
11835
- const pkg = index === void 0 ? listed : {
11836
- ...listed,
11837
- privateRegistry: index
11838
- };
11851
+ const pkg = origin(listed);
11839
11852
  const target = targets.get(pkg.id);
11840
11853
  return target === void 0 ? pkg : withTarget(pkg, target);
11841
11854
  }),
@@ -11927,12 +11940,12 @@ const deprecationOf = (formula) => {
11927
11940
  if (formula.disabled) return `Homebrew disabled it (${formula.disable_reason ?? "no reason given"})`;
11928
11941
  if (formula.deprecated) return formula.deprecation_reason ?? "no reason given";
11929
11942
  };
11930
- const lookup$4 = Effect.fn("brew.lookup")(function* (client, wanted) {
11943
+ const lookup$3 = Effect.fn("brew.lookup")(function* (client, wanted) {
11931
11944
  if (wanted.name.includes("/")) return yield* new MetadataUnavailable({
11932
11945
  source: wanted.name,
11933
11946
  issue: "Homebrew's API only describes formulae and casks from the core taps"
11934
11947
  });
11935
- const kind = wanted.kind === "cask" ? "cask" : "formula";
11948
+ const kind = wanted.kind ?? "formula";
11936
11949
  const formula = yield* getJson(client, `https://formulae.brew.sh/api/${kind}/${wanted.name}.json`, Formula);
11937
11950
  const installs = formula.analytics?.install["30d"][wanted.name];
11938
11951
  const deprecated = deprecationOf(formula);
@@ -11960,7 +11973,7 @@ const Crate = Schema.Struct({
11960
11973
  yank_message: Schema.NullOr(Schema.String)
11961
11974
  }))
11962
11975
  });
11963
- const lookup$3 = Effect.fn("crates.lookup")(function* (client, wanted) {
11976
+ const lookup$2 = Effect.fn("crates.lookup")(function* (client, wanted) {
11964
11977
  const { crate, versions } = yield* getJson(client, `https://crates.io/api/v1/crates/${wanted.name}`, Crate);
11965
11978
  const relevant = versions.filter((version) => wanted.versions.includes(version.num));
11966
11979
  return {
@@ -11981,7 +11994,7 @@ const Repository = Schema.Struct({
11981
11994
  archived: Schema.Boolean
11982
11995
  });
11983
11996
  const Release = Schema.Struct({ published_at: Schema.NullOr(Schema.String) });
11984
- const lookup$2 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11997
+ const lookup$1 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11985
11998
  const token = yield* githubToken.pipe(Effect.provide(nodeLayer));
11986
11999
  const headers = githubHeaders(token);
11987
12000
  const base = `${githubApi}/repos/${wanted.name}`;
@@ -11997,41 +12010,6 @@ const lookup$2 = Effect.fn("githubActions.lookup")(function* (client, wanted) {
11997
12010
  withdrawn: {}
11998
12011
  };
11999
12012
  });
12000
- // src/registry/npm.ts
12001
- const Version = Schema.Struct({ deprecated: Schema.optionalKey(Schema.Unknown) });
12002
- const Packument = Schema.Struct({
12003
- description: Schema.optionalKey(Schema.String),
12004
- homepage: Schema.optionalKey(Schema.String),
12005
- repository: Schema.optionalKey(Schema.Union([Schema.String, Schema.Struct({ url: Schema.optionalKey(Schema.String) })])),
12006
- "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) })),
12007
- time: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
12008
- versions: Schema.optionalKey(Schema.Record(Schema.String, Version))
12009
- });
12010
- const Downloads = Schema.Struct({ downloads: Schema.Finite });
12011
- const message = (version) => {
12012
- const deprecated = version?.deprecated;
12013
- return Predicate.isString(deprecated) && deprecated !== "" ? Option.some(deprecated) : Option.none();
12014
- };
12015
- const lookup$1 = Effect.fn("npm.lookup")(function* (client, wanted) {
12016
- const path = wanted.name.replace("/", "%2F");
12017
- const [packument, downloads] = yield* Effect.all([getJson(client, `https://registry.npmjs.org/${path}`, Packument), getJson(client, `https://api.npmjs.org/downloads/point/last-week/${wanted.name}`, Downloads).pipe(Effect.option)], { concurrency: "unbounded" });
12018
- const versions = packument.versions ?? {};
12019
- const latest = packument["dist-tags"]?.latest;
12020
- const repository = Predicate.isString(packument.repository) ? packument.repository : packument.repository?.url;
12021
- const deprecated = latest === void 0 ? Option.none() : message(versions[latest]);
12022
- return {
12023
- ...packument.description === void 0 ? {} : { description: packument.description },
12024
- ...packument.homepage === void 0 ? {} : { homepage: packument.homepage },
12025
- ...repository === void 0 ? {} : { repository: repositoryUrl(repository) },
12026
- ...Option.isSome(downloads) ? { popularity: `${count(downloads.value.downloads)} downloads last week` } : {},
12027
- ...Option.isSome(deprecated) ? { deprecated: deprecated.value } : {},
12028
- released: pick$1(packument.time ?? {}, wanted.versions),
12029
- withdrawn: Object.fromEntries(wanted.versions.flatMap((version) => Option.match(message(versions[version]), {
12030
- onNone: () => [],
12031
- onSome: (reason) => [[version, reason]]
12032
- })))
12033
- };
12034
- });
12035
12013
  // src/registry/pypi.ts
12036
12014
  const Project = Schema.Struct({
12037
12015
  info: Schema.Struct({
@@ -12081,12 +12059,12 @@ const vendor = (source, issue) => (_, wanted) => Effect.fail(new MetadataUnavail
12081
12059
  issue: `${wanted.name} ${issue}`
12082
12060
  }));
12083
12061
  const sources = {
12084
- brew: lookup$4,
12085
- npm: lookup$1,
12086
- crates: lookup$3,
12062
+ brew: lookup$3,
12063
+ npm: lookup$4,
12064
+ crates: lookup$2,
12087
12065
  go: lookup$5,
12088
12066
  pypi: lookup,
12089
- "github-actions": lookup$2,
12067
+ "github-actions": lookup$1,
12090
12068
  macos: vendor("Software Update", "comes from Apple's Software Update, which has no registry"),
12091
12069
  apt: vendor("apt", "comes from your distribution's apt repositories, which upup does not read"),
12092
12070
  sparkle: vendor("Sparkle", "updates from its own Sparkle feed, which has no registry"),
@@ -12103,7 +12081,7 @@ const freshFor = 864e5;
12103
12081
  const wanted = (pkg) => ({
12104
12082
  name: pkg.artifact,
12105
12083
  versions: [pkg.current, ...pkg.target === void 0 ? [] : [pkg.target]],
12106
- ...isCask(pkg) ? { kind: "cask" } : {}
12084
+ ...kindOf$2(pkg) === "cask" ? { kind: "cask" } : {}
12107
12085
  });
12108
12086
  const layer$2 = (cacheDir) => Layer.effect(Registry, Effect.gen(function* () {
12109
12087
  const client = yield* HttpClient.HttpClient;
@@ -12440,15 +12418,16 @@ const auditTools = (home) => Action.implement(AuditActions, Effect.gen(function*
12440
12418
  const audits = yield* Audits;
12441
12419
  const ai = yield* Ai;
12442
12420
  const status = (pkg, mode) => Effect.gen(function* () {
12443
- const identity = identityOf(pkg, mode);
12444
- if (identity === void 0) {
12445
- const reason = unauditable(pkg) ?? "no update is offered";
12421
+ const audit = auditOf(pkg, mode);
12422
+ if (Result.isFailure(audit)) {
12423
+ const reason = audit.failure;
12446
12424
  return {
12447
12425
  key: rowKey(pkg),
12448
12426
  status: "not-auditable",
12449
12427
  reason: `${reason.charAt(0).toUpperCase()}${reason.slice(1)}.`
12450
12428
  };
12451
12429
  }
12430
+ const identity = audit.success;
12452
12431
  const job = (yield* audits.jobs)[jobKey(identity)];
12453
12432
  if (job?._tag === "Running") return {
12454
12433
  key: rowKey(pkg),
@@ -12481,17 +12460,17 @@ const auditTools = (home) => Action.implement(AuditActions, Effect.gen(function*
12481
12460
  }),
12482
12461
  package_changes: ({ key, path, ...where }) => Effect.gen(function* () {
12483
12462
  const { scope, pkg } = yield* resolve.one(where, key);
12484
- const identity = identityOf(pkg, scope.mode);
12485
- if (identity === void 0) {
12463
+ const audit = auditOf(pkg, scope.mode);
12464
+ if (Result.isFailure(audit)) {
12486
12465
  if (pkg.target === void 0) return yield* new ChangesUnavailable({ issue: "No update is offered." });
12487
- if (pkg.notes === void 0) return yield* new ChangesUnavailable({ issue: `Nothing to compare: ${unauditable(pkg) ?? "it has no exact update to compare"}${isAppUpdate(pkg) ? ", and its feed has no release notes" : ""}.` });
12466
+ if (pkg.notes === void 0) return yield* new ChangesUnavailable({ issue: `Nothing to compare: ${audit.failure}${isAppUpdate(pkg) ? ", and its feed has no release notes" : ""}.` });
12488
12467
  if (path !== void 0) return yield* new ChangesUnavailable({ issue: `${pkg.name}'s update has release notes but no files to diff; leave out path.` });
12489
12468
  return {
12490
12469
  ...versions(pkg),
12491
12470
  notes: pkg.notes
12492
12471
  };
12493
12472
  }
12494
- const view = yield* audits.changes(identity);
12473
+ const view = yield* audits.changes(audit.success);
12495
12474
  const files = view.sections.map((section) => ({
12496
12475
  path: sectionPath(section),
12497
12476
  ...changedLines(section),
@@ -12796,33 +12775,34 @@ const reasonFor = (result) => Option.match(result, {
12796
12775
  onNone: () => "it has no finished audit of these exact artifacts",
12797
12776
  onSome: (found) => found._tag === "Failed" ? `its audit failed: ${found.issue}` : `its audit found ${found.report.verdict}`
12798
12777
  });
12778
+ const approves = (result) => Option.isSome(result) && result.value._tag === "Reported" && result.value.report.verdict === "ok";
12799
12779
  const screen = (targets, mode, resultOf) => Effect.gen(function* () {
12800
- const identities = /* @__PURE__ */ new Map();
12801
- for (const target of targets) {
12802
- const identity = identityOf(target.pkg, mode);
12803
- if (identity !== void 0) identities.set(workspaceKey(identity), identity);
12804
- }
12805
- const results = new Map(yield* Effect.forEach(identities, ([key, identity]) => Effect.map(resultOf(identity), (result) => [key, result]), { concurrency: 4 }));
12806
- const approved = [];
12807
12780
  const refused = {};
12781
+ const groups = /* @__PURE__ */ new Map();
12808
12782
  for (const target of targets) {
12809
- if (isUnauditedUpdate(target.pkg)) {
12810
- approved.push(target);
12783
+ if (isUnauditedUpdate(target.pkg)) continue;
12784
+ const audit = auditOf(target.pkg, mode);
12785
+ if (Result.isFailure(audit)) {
12786
+ refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: audit.failure });
12811
12787
  continue;
12812
12788
  }
12813
- const identity = identityOf(target.pkg, mode);
12814
- const result = identity === void 0 ? void 0 : results.get(workspaceKey(identity));
12815
- if (result !== void 0 && Option.isSome(result)) {
12816
- const found = result.value;
12817
- if (found._tag === "Reported" && found.report.verdict === "ok") {
12818
- approved.push(target);
12819
- continue;
12820
- }
12821
- }
12822
- refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: result === void 0 ? unauditable(target.pkg) ?? "it has no exact update to audit" : reasonFor(result) });
12789
+ const key = workspaceKey(audit.success);
12790
+ const group = groups.get(key) ?? {
12791
+ identity: audit.success,
12792
+ members: []
12793
+ };
12794
+ group.members.push(target);
12795
+ groups.set(key, group);
12823
12796
  }
12797
+ yield* Effect.forEach(groups.values(), ({ identity, members }) => Effect.map(resultOf(identity), (found) => {
12798
+ if (approves(found)) return;
12799
+ for (const target of members) refused[rowKey(target.pkg)] = Outcome.Excluded({ reason: reasonFor(found) });
12800
+ }), {
12801
+ concurrency: 4,
12802
+ discard: true
12803
+ });
12824
12804
  return {
12825
- approved,
12805
+ approved: targets.filter((target) => !Object.hasOwn(refused, rowKey(target.pkg))),
12826
12806
  refused
12827
12807
  };
12828
12808
  });
@@ -13165,8 +13145,7 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
13165
13145
  selected: [],
13166
13146
  operation: machine$1.initialState,
13167
13147
  activity: {},
13168
- outcomes: {},
13169
- actions: {},
13148
+ ran: {},
13170
13149
  installed: {},
13171
13150
  removed: {},
13172
13151
  password: void 0,
@@ -13459,8 +13438,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13459
13438
  const reset = (mode, scope) => SubscriptionRef.update(state, (current) => {
13460
13439
  places.set(`${current.mode}:${current.scope}`, {
13461
13440
  selected: current.selected,
13462
- outcomes: current.outcomes,
13463
- actions: current.actions,
13441
+ ran: current.ran,
13464
13442
  installed: current.installed,
13465
13443
  removed: current.removed
13466
13444
  });
@@ -13531,13 +13509,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13531
13509
  const confirmed = targets.filter((target) => accepted.includes(rowKey(target.pkg)));
13532
13510
  const chosen = vetting ? vet(targets.filter((target) => !accepted.includes(rowKey(target.pkg))), current.mode).pipe(Effect.flatMap(({ approved, refused }) => update((latest) => ({
13533
13511
  ...latest,
13534
- outcomes: {
13535
- ...latest.outcomes,
13536
- ...refused
13537
- },
13538
- actions: {
13539
- ...latest.actions,
13540
- ...Object.fromEntries(Object.keys(refused).map((key) => [key, action]))
13512
+ ran: {
13513
+ ...latest.ran,
13514
+ ...Object.fromEntries(Object.entries(refused).map(([key, outcome]) => [key, {
13515
+ action,
13516
+ outcome
13517
+ }]))
13541
13518
  }
13542
13519
  })).pipe(Effect.as([...confirmed, ...approved])))) : Effect.succeed(targets);
13543
13520
  yield* FiberHandle.run(operation, chosen.pipe(Effect.tap((approved) => update((latest) => ({
@@ -13555,13 +13532,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13555
13532
  } : {
13556
13533
  ...latest,
13557
13534
  activity: without(latest.activity, event.key),
13558
- outcomes: {
13559
- ...latest.outcomes,
13560
- [event.key]: event.outcome
13561
- },
13562
- actions: {
13563
- ...latest.actions,
13564
- [event.key]: action
13535
+ ran: {
13536
+ ...latest.ran,
13537
+ [event.key]: {
13538
+ action,
13539
+ outcome: event.outcome
13540
+ }
13565
13541
  },
13566
13542
  installed: installedAfter(latest.installed, approved, event),
13567
13543
  removed: removedAfter(latest.removed, approved, event),
@@ -13587,10 +13563,10 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13587
13563
  if ((yield* SubscriptionRef.get(state)).about[key] !== void 0) return;
13588
13564
  yield* about.load(key, registry.lookup(pkg));
13589
13565
  });
13590
- const readTranscript = Effect.fn("Session.readTranscript")(function* (file) {
13591
- const known = (yield* SubscriptionRef.get(state)).transcripts[file];
13566
+ const readTranscript = Effect.fn("Session.readTranscript")(function* (paths) {
13567
+ const known = (yield* SubscriptionRef.get(state)).transcripts[paths.file];
13592
13568
  if (known !== void 0 && known._tag !== "Unavailable") return;
13593
- yield* transcripts.load(file, audits.transcript(file));
13569
+ yield* transcripts.load(paths.file, audits.transcript(paths));
13594
13570
  });
13595
13571
  const readChanges = Effect.fn("Session.readChanges")(function* (pkg) {
13596
13572
  const current = yield* SubscriptionRef.get(state);
@@ -13751,7 +13727,7 @@ const silenceNodeWarningsWhileTuiDraws = () => {
13751
13727
  };
13752
13728
  const loadTui = Effect.suspend(() => {
13753
13729
  silenceNodeWarningsWhileTuiDraws();
13754
- return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-DV_6wbrQ.mjs"));
13730
+ return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-Ri-yz-Sx.mjs"));
13755
13731
  });
13756
13732
  const open$1 = (begin, projectDir) => Effect.gen(function* () {
13757
13733
  const { launch } = yield* loadTui;