@agoric/deploy-script-support 0.10.4-u13.0 → 0.10.4-u14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [0.10.4-u14.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/deploy-script-support@0.10.4-u13.0...@agoric/deploy-script-support@0.10.4-u14.0) (2024-02-27)
7
+
8
+
9
+ ### Features
10
+
11
+ * **deploy-script-support:** generalize `extractCoreProposalBundles` ([5de1e93](https://github.com/Agoric/agoric-sdk/commit/5de1e93747146a4c9cd4e3d3d77a0f43033c72fb))
12
+ * **deploy-script-support:** Write out bundle file names in machine readable file ([2a68ca1](https://github.com/Agoric/agoric-sdk/commit/2a68ca148f637ca88c553b75834496ac6ebea841))
13
+ * **extract-proposal:** organize proposals into steps ([eaa6b4f](https://github.com/Agoric/agoric-sdk/commit/eaa6b4fdc5c8ce5c377ac8ed2b88971f78641536))
14
+ * better diagnostic for bad proposal ([19725e5](https://github.com/Agoric/agoric-sdk/commit/19725e5a132056a314d90975fffbb89053de8829))
15
+
16
+
17
+
6
18
  ### [0.10.4-u13.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/deploy-script-support@0.10.4-u12.0...@agoric/deploy-script-support@0.10.4-u13.0) (2023-12-07)
7
19
 
8
20
  **Note:** Version bump only for package @agoric/deploy-script-support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/deploy-script-support",
3
- "version": "0.10.4-u13.0",
3
+ "version": "0.10.4-u14.0",
4
4
  "description": "Helpers and other support for writing deploy scripts",
5
5
  "type": "module",
6
6
  "main": "src/helpers.js",
@@ -35,12 +35,12 @@
35
35
  "homepage": "https://github.com/Agoric/agoric-sdk#readme",
36
36
  "dependencies": {
37
37
  "@agoric/assert": "^0.6.1-u11wf.0",
38
- "@agoric/ertp": "^0.16.3-u13.0",
39
- "@agoric/import-manager": "^0.3.12-u13.0",
40
- "@agoric/internal": "^0.4.0-u13.0",
41
- "@agoric/notifier": "^0.6.3-u13.0",
42
- "@agoric/store": "^0.9.3-u13.0",
43
- "@agoric/zoe": "^0.26.3-u13.0",
38
+ "@agoric/ertp": "^0.16.3-u14.0",
39
+ "@agoric/import-manager": "^0.3.12-u14.0",
40
+ "@agoric/internal": "^0.4.0-u14.0",
41
+ "@agoric/notifier": "^0.6.3-u14.0",
42
+ "@agoric/store": "^0.9.3-u14.0",
43
+ "@agoric/zoe": "^0.26.3-u14.0",
44
44
  "@endo/base64": "0.2.31",
45
45
  "@endo/bundle-source": "2.5.2-upstream-rollup",
46
46
  "@endo/far": "0.2.18",
@@ -50,7 +50,7 @@
50
50
  "@endo/zip": "0.2.31"
51
51
  },
52
52
  "devDependencies": {
53
- "@agoric/vats": "^0.15.2-u13.0",
53
+ "@agoric/vats": "^0.15.2-u14.0",
54
54
  "@endo/init": "0.5.56",
55
55
  "ava": "^5.2.0",
56
56
  "import-meta-resolve": "^2.2.1"
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "access": "public"
70
70
  },
71
- "gitHead": "5a6cdeb0c18ae9700d706445acf402f8d1e873c3"
71
+ "gitHead": "b3a6f3374cb3bddab39fc6d6f426429cae6c29c6"
72
72
  }
@@ -8,7 +8,20 @@ const t = 'makeCoreProposalBehavior';
8
8
  * @typedef {*} BootstrapPowers
9
9
  */
10
10
 
11
- // These permits apply to `allPowers` in `behavior` below.
11
+ /**
12
+ * @typedef {import('./externalTypes.js').ManifestBundleRef} ManifestBundleRef
13
+ * @typedef {[methodName: string, ...args: unknown[]]} FlatMethargs
14
+ * @typedef {Record<string, Record<string, unknown>>} Manifest
15
+ */
16
+
17
+ /**
18
+ * These permits are expected to be the minimum powers required by the
19
+ * `coreProposalBehavior` function returned from `makeCoreProposalBehavior`.
20
+ * They are merged with all of the manifest getter's permits to produce the
21
+ * total permits needed by the resulting core proposal (such as might be---and
22
+ * generally are---written into a *-permit.json file).
23
+ * @see {@link ./writeCoreProposal.js}
24
+ */
12
25
  export const permits = {
13
26
  consume: { agoricNamesAdmin: t, vatAdminSvc: t, zoe: t },
14
27
  evaluateBundleCap: t,
@@ -23,22 +36,22 @@ export const permits = {
23
36
  * for catching bugs. Thus, this maker must not reference any other modules or
24
37
  * definitions.
25
38
  *
26
- * @param {object} opts
27
- * @param {{ bundleName: string } | { bundleID: string }} opts.manifestBundleRef
28
- * @param {[string, ...unknown[]]} opts.getManifestCall
29
- * @param {Record<string, Record<string, unknown>>} [opts.overrideManifest]
30
- * @param {typeof import('@endo/far').E} opts.E
31
- * @param {(...args: unknown[]) => void} [opts.log]
32
- * @param {(ref: unknown) => Promise<unknown>} [opts.restoreRef]
39
+ * @param {object} inputs
40
+ * @param {ManifestBundleRef} inputs.manifestBundleRef
41
+ * @param {FlatMethargs} inputs.getManifestCall
42
+ * @param {Manifest} [inputs.customManifest]
43
+ * @param {typeof import('@endo/far').E} inputs.E
44
+ * @param {(...args: unknown[]) => void} [inputs.log]
45
+ * @param {(ref: import('./externalTypes.js').ManifestBundleRef) => Promise<import('@agoric/zoe/src/zoeService/utils.js').Installation<unknown>>} [inputs.customRestoreRef]
33
46
  * @returns {(vatPowers: unknown) => Promise<unknown>}
34
47
  */
35
48
  export const makeCoreProposalBehavior = ({
36
49
  manifestBundleRef,
37
- getManifestCall,
38
- overrideManifest,
50
+ getManifestCall: [manifestGetterName, ...manifestGetterArgs],
51
+ customManifest,
39
52
  E,
40
53
  log = console.info,
41
- restoreRef: overrideRestoreRef,
54
+ customRestoreRef,
42
55
  }) => {
43
56
  const { entries, fromEntries } = Object;
44
57
 
@@ -56,10 +69,32 @@ export const makeCoreProposalBehavior = ({
56
69
  return fromEntries(ents);
57
70
  };
58
71
 
59
- /** @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} allPowers */
60
- const behavior = async allPowers => {
61
- // NOTE: If updating any of these names extracted from `allPowers`, you must
62
- // change `permits` above to reflect their accessibility.
72
+ const makeRestoreRef = (vatAdminSvc, zoe) => {
73
+ /** @type {(ref: import('./externalTypes.js').ManifestBundleRef) => Promise<Installation<unknown>>} */
74
+ const defaultRestoreRef = async bundleRef => {
75
+ // extract-proposal.js creates these records, and bundleName is
76
+ // the optional name under which the bundle was installed into
77
+ // config.bundles
78
+ const bundleIdP =
79
+ 'bundleName' in bundleRef
80
+ ? E(vatAdminSvc).getBundleIDByName(bundleRef.bundleName)
81
+ : bundleRef.bundleID;
82
+ const bundleID = await bundleIdP;
83
+ const label = bundleID.slice(0, 8);
84
+ return E(zoe).installBundleID(bundleID, label);
85
+ };
86
+ return defaultRestoreRef;
87
+ };
88
+
89
+ /** @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} powers */
90
+ const coreProposalBehavior = async powers => {
91
+ // NOTE: `powers` is expected to match or be a superset of the above `permits` export,
92
+ // which should therefore be kept in sync with this deconstruction code.
93
+ // HOWEVER, do note that this function is invoked with at least the *union* of powers
94
+ // required by individual moduleBehaviors declared by the manifest getter, which is
95
+ // necessary so it can use `runModuleBehaviors` to provide the appropriate subset to
96
+ // each one (see ./writeCoreProposal.js).
97
+ // Handle `powers` with the requisite care.
63
98
  const {
64
99
  consume: { vatAdminSvc, zoe, agoricNamesAdmin },
65
100
  evaluateBundleCap,
@@ -67,49 +102,45 @@ export const makeCoreProposalBehavior = ({
67
102
  modules: {
68
103
  utils: { runModuleBehaviors },
69
104
  },
70
- } = allPowers;
71
- const [exportedGetManifest, ...manifestArgs] = getManifestCall;
72
-
73
- const defaultRestoreRef = async ref => {
74
- // extract-proposal.js creates these records, and bundleName is
75
- // the name under which the bundle was installed into
76
- // config.bundles
77
- const p = ref.bundleName
78
- ? E(vatAdminSvc).getBundleIDByName(ref.bundleName)
79
- : ref.bundleID;
80
- const bundleID = await p;
81
- const label = bundleID.slice(0, 8);
82
- return E(zoe).installBundleID(bundleID, label);
83
- };
84
- const restoreRef = overrideRestoreRef || defaultRestoreRef;
105
+ } = powers;
85
106
 
86
107
  // Get the on-chain installation containing the manifest and behaviors.
87
- console.info('evaluateBundleCap', {
108
+ log('evaluateBundleCap', {
88
109
  manifestBundleRef,
89
- exportedGetManifest,
110
+ manifestGetterName,
90
111
  vatAdminSvc,
91
112
  });
92
113
  let bcapP;
93
114
  if ('bundleName' in manifestBundleRef) {
94
115
  bcapP = E(vatAdminSvc).getNamedBundleCap(manifestBundleRef.bundleName);
95
- } else {
116
+ } else if ('bundleID' in manifestBundleRef) {
96
117
  bcapP = E(vatAdminSvc).getBundleCap(manifestBundleRef.bundleID);
118
+ } else {
119
+ const keys = Reflect.ownKeys(manifestBundleRef).map(key =>
120
+ typeof key === 'string' ? JSON.stringify(key) : String(key),
121
+ );
122
+ const keysStr = `[${keys.join(', ')}]`;
123
+ throw Error(
124
+ `bundleRef must have own bundleName or bundleID, missing in ${keysStr}`,
125
+ );
97
126
  }
98
127
  const bundleCap = await bcapP;
99
128
 
100
- const manifestNS = await evaluateBundleCap(bundleCap);
129
+ const proposalNS = await evaluateBundleCap(bundleCap);
101
130
 
102
- console.error('execute', {
103
- exportedGetManifest,
104
- behaviors: Object.keys(manifestNS),
131
+ // Get the manifest and its metadata.
132
+ log('execute', {
133
+ manifestGetterName,
134
+ bundleExports: Object.keys(proposalNS),
105
135
  });
136
+ const restoreRef = customRestoreRef || makeRestoreRef(vatAdminSvc, zoe);
106
137
  const {
107
138
  manifest,
108
139
  options: rawOptions,
109
140
  installations: rawInstallations,
110
- } = await manifestNS[exportedGetManifest](
141
+ } = await proposalNS[manifestGetterName](
111
142
  harden({ restoreRef }),
112
- ...manifestArgs,
143
+ ...manifestGetterArgs,
113
144
  );
114
145
 
115
146
  // Await references in the options or installations.
@@ -117,20 +148,24 @@ export const makeCoreProposalBehavior = ({
117
148
  [rawOptions, rawInstallations].map(shallowlyFulfilled),
118
149
  );
119
150
 
120
- // Publish the installations for behavior dependencies.
121
- const installAdmin = E(agoricNamesAdmin).lookupAdmin('installation');
122
- await Promise.all(
123
- entries(installations || {}).map(([key, value]) => {
124
- produceInstallations[key].resolve(value);
125
- return E(installAdmin).update(key, value);
126
- }),
127
- );
151
+ // Publish the installations for our dependencies.
152
+ const installationEntries = entries(installations || {});
153
+ if (installationEntries.length > 0) {
154
+ const installAdmin = E(agoricNamesAdmin).lookupAdmin('installation');
155
+ await Promise.all(
156
+ installationEntries.map(([key, value]) => {
157
+ produceInstallations[key].resolve(value);
158
+ return E(installAdmin).update(key, value);
159
+ }),
160
+ );
161
+ }
128
162
 
129
- // Evaluate the manifest for our behaviors.
163
+ // Evaluate the manifest.
130
164
  return runModuleBehaviors({
131
- allPowers,
132
- behaviors: manifestNS,
133
- manifest: overrideManifest || manifest,
165
+ // Remember that `powers` may be arbitrarily broad.
166
+ allPowers: powers,
167
+ behaviors: proposalNS,
168
+ manifest: customManifest || manifest,
134
169
  makeConfig: (name, _permit) => {
135
170
  log('coreProposal:', name);
136
171
  return { options };
@@ -138,22 +173,31 @@ export const makeCoreProposalBehavior = ({
138
173
  });
139
174
  };
140
175
 
141
- // Make the behavior the completion value.
142
- return behavior;
176
+ return coreProposalBehavior;
143
177
  };
144
178
 
145
- export const makeEnactCoreProposalsFromBundleRef =
146
- ({ makeCoreProposalArgs, E }) =>
147
- async allPowers => {
179
+ /**
180
+ * @param {object} inputs
181
+ * @param {Array<{ ref: ManifestBundleRef, call: FlatMethargs, customManifest?: Manifest }>} inputs.metadataRecords
182
+ * @param {typeof import('@endo/far').E} inputs.E
183
+ */
184
+ export const makeEnactCoreProposalsFromBundleRef = ({ metadataRecords, E }) => {
185
+ /**
186
+ * @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} powers
187
+ * @returns {Promise<void>}
188
+ */
189
+ const enactCoreProposals = async powers => {
148
190
  await Promise.all(
149
- makeCoreProposalArgs.map(async ({ ref, call, overrideManifest }) => {
150
- const subBehavior = makeCoreProposalBehavior({
191
+ metadataRecords.map(async ({ ref, call, customManifest }) => {
192
+ const coreProposalBehavior = makeCoreProposalBehavior({
151
193
  manifestBundleRef: ref,
152
194
  getManifestCall: call,
153
- overrideManifest,
195
+ customManifest,
154
196
  E,
155
197
  });
156
- return subBehavior(allPowers);
198
+ return coreProposalBehavior(powers);
157
199
  }),
158
200
  );
159
201
  };
202
+ return enactCoreProposals;
203
+ };
@@ -18,6 +18,9 @@ export const start = () => {
18
18
  });
19
19
  };
20
20
 
21
+ /**
22
+ * @param {{ zoe: ERef<ZoeService> }} opts
23
+ */
21
24
  const makeBundler = ({ zoe }) => {
22
25
  /** @type { Map<string, [string, Uint8Array]>} */
23
26
  const nameToContent = new Map();
@@ -16,30 +16,29 @@ export {};
16
16
  */
17
17
 
18
18
  /**
19
- * @typedef BundleHandle
20
- * @property {string} [bundleName]
19
+ * @typedef {{fileName?: string} & ({ bundleName: string } | { bundleID: string}) } ManifestBundleRef
21
20
  */
22
21
 
23
22
  /**
24
23
  * @callback PublishBundleRef
25
- * @param {ERef<BundleHandle>} bundle
26
- * @returns {Promise<BundleHandle>}
24
+ * @param {ERef<ManifestBundleRef>} bundle
25
+ * @returns {Promise<ManifestBundleRef>}
27
26
  */
28
27
 
29
28
  /**
30
- * @callback InstallBundle
29
+ * @callback InstallEntrypoint
31
30
  * @param {string} srcSpec
32
- * @param {string} bundlePath
33
- * @param {any} [opts]
34
- * @returns {BundleHandle}
31
+ * @param {string} [bundlePath]
32
+ * @param {unknown} [opts]
33
+ * @returns {Promise<ManifestBundleRef>}
35
34
  */
36
35
 
37
36
  /**
38
37
  * @callback ProposalBuilder
39
38
  * @param {{
40
39
  * publishRef: PublishBundleRef,
41
- * install: InstallBundle,
42
- * wrapInstall?: <T>(f: T) => T }
40
+ * install: InstallEntrypoint,
41
+ * wrapInstall?: <T extends InstallEntrypoint>(f: T) => T }
43
42
  * } powers
44
43
  * @param {...any} args
45
44
  * @returns {Promise<ProposalResult>}
@@ -11,21 +11,47 @@ import {
11
11
  } from './coreProposalBehavior.js';
12
12
 
13
13
  /**
14
- * @typedef {string | { module: string, entrypoint: string, args?: Array<unknown> }} ConfigProposal
14
+ * @typedef {string | {module: string, entrypoint?: string, args?: Array<unknown>}} ConfigProposal
15
15
  */
16
16
 
17
- const { details: X, Fail } = assert;
17
+ /**
18
+ * @typedef {{steps: ConfigProposal[][]}} SequentialCoreProposals
19
+ * @typedef {ConfigProposal[] | SequentialCoreProposals} CoreProposals
20
+ */
21
+
22
+ const { Fail } = assert;
18
23
 
19
24
  const req = createRequire(import.meta.url);
20
25
 
26
+ /**
27
+ * @param {...(CoreProposals | undefined | null)} args
28
+ * @returns {SequentialCoreProposals}
29
+ */
30
+ export const mergeCoreProposals = (...args) => {
31
+ /** @type {ConfigProposal[][]} */
32
+ const steps = [];
33
+ for (const coreProposal of args) {
34
+ if (!coreProposal) {
35
+ continue;
36
+ }
37
+ if ('steps' in coreProposal) {
38
+ steps.push(...coreProposal.steps);
39
+ } else {
40
+ steps.push(coreProposal);
41
+ }
42
+ }
43
+ return harden({ steps });
44
+ };
45
+ harden(mergeCoreProposals);
46
+
21
47
  /**
22
48
  * @param {(ModuleSpecifier | FilePath)[]} paths
23
49
  * @typedef {string} ModuleSpecifier
24
50
  * @typedef {string} FilePath
25
51
  */
26
52
  const pathResolve = (...paths) => {
27
- const fileName = paths.pop();
28
- assert(fileName, '>=1 paths required');
53
+ const fileName = /** @type {string} */ (paths.pop());
54
+ fileName || Fail`base name required`;
29
55
  try {
30
56
  return req.resolve(fileName, {
31
57
  paths,
@@ -40,6 +66,19 @@ const findModule = (initDir, srcSpec) =>
40
66
  ? pathResolve(initDir, srcSpec)
41
67
  : req.resolve(srcSpec);
42
68
 
69
+ /**
70
+ * @param {{ bundleID?: string, bundleName?: string }} handle - mutated then hardened
71
+ * @param {string} sourceSpec - the specifier of a module to load
72
+ * @param {string} key - the key of the proposal
73
+ * @param {string} piece - the piece of the proposal
74
+ * @returns {Promise<[string, any]>}
75
+ */
76
+ const namedHandleToBundleSpec = async (handle, sourceSpec, key, piece) => {
77
+ handle.bundleName = `coreProposal${String(key)}_${piece}`;
78
+ harden(handle);
79
+ return harden([handle.bundleName, { sourceSpec }]);
80
+ };
81
+
43
82
  /**
44
83
  * Format core proposals to be run at bootstrap:
45
84
  * SwingSet `bundles` configuration
@@ -51,188 +90,227 @@ const findModule = (initDir, srcSpec) =>
51
90
  * but for sim-chain and such, they can be declared statically in
52
91
  * the chain configuration, in which case they are run at bootstrap.
53
92
  *
54
- * @param {ConfigProposal[]} coreProposals - governance
93
+ * @param {CoreProposals} coreProposals - governance
55
94
  * proposals to run at chain bootstrap for scenarios such as sim-chain.
56
95
  * @param {FilePath} [dirname]
57
- * @param {typeof makeEnactCoreProposalsFromBundleRef} [makeEnactCoreProposals]
58
- * @param {(i: number) => number} [getSequenceForProposal]
96
+ * @param {object} [opts]
97
+ * @param {typeof makeEnactCoreProposalsFromBundleRef} [opts.makeEnactCoreProposals]
98
+ * @param {(key: PropertyKey) => PropertyKey} [opts.getSequenceForProposal]
99
+ * @param {typeof namedHandleToBundleSpec} [opts.handleToBundleSpec]
59
100
  */
60
101
  export const extractCoreProposalBundles = async (
61
102
  coreProposals,
62
103
  dirname = '.',
63
- makeEnactCoreProposals = makeEnactCoreProposalsFromBundleRef,
64
- getSequenceForProposal,
104
+ opts,
65
105
  ) => {
66
- if (!getSequenceForProposal) {
67
- // Deterministic proposal numbers.
68
- getSequenceForProposal = i => i;
69
- }
106
+ const {
107
+ makeEnactCoreProposals = makeEnactCoreProposalsFromBundleRef,
108
+ getSequenceForProposal = key => key,
109
+ handleToBundleSpec = namedHandleToBundleSpec,
110
+ } = opts || {};
70
111
 
71
112
  dirname = pathResolve(dirname);
72
113
  dirname = await fs.promises
73
114
  .stat(dirname)
74
115
  .then(stbuf => (stbuf.isDirectory() ? dirname : path.dirname(dirname)));
75
116
 
76
- /** @type {Map<{ bundleName?: string }, { source: string, bundle?: string }>} */
117
+ /** @type {Map<{ bundleID?: string, bundleName?: string }, { source: string, bundle?: string }>} */
77
118
  const bundleHandleToAbsolutePaths = new Map();
78
119
 
120
+ const proposalSteps =
121
+ 'steps' in coreProposals ? coreProposals.steps : [coreProposals];
79
122
  const bundleToSource = new Map();
80
- const extracted = await Promise.all(
81
- coreProposals.map(async (coreProposal, i) => {
82
- // console.debug(`Parsing core proposal:`, coreProposal);
83
-
84
- /** @type {string} */
85
- let entrypoint;
86
- /** @type {unknown[]} */
87
- let args;
88
- /** @type {string} */
89
- let module;
90
- if (typeof coreProposal === 'string') {
91
- module = coreProposal;
92
- entrypoint = 'defaultProposalBuilder';
93
- args = [];
94
- } else {
95
- ({ module, entrypoint, args = [] } = coreProposal);
96
- }
97
-
98
- typeof module === 'string' ||
99
- Fail`coreProposal module ${module} must be string`;
100
- typeof entrypoint === 'string' ||
101
- Fail`coreProposal entrypoint ${entrypoint} must be string`;
102
- Array.isArray(args) || Fail`coreProposal args ${args} must be array`;
103
-
104
- const thisProposalBundleHandles = new Set();
105
- assert(getSequenceForProposal);
106
- const thisProposalSequence = getSequenceForProposal(i);
107
- const initPath = findModule(dirname, module);
108
- const initDir = path.dirname(initPath);
109
- /** @type {Record<string, import('./externalTypes.js').ProposalBuilder>} */
110
- const ns = await import(initPath);
111
- const install = (srcSpec, bundlePath) => {
112
- const absoluteSrc = findModule(initDir, srcSpec);
113
- const bundleHandle = {};
114
- const absolutePaths = { source: absoluteSrc };
115
- if (bundlePath) {
116
- const absoluteBundle = pathResolve(initDir, bundlePath);
117
- absolutePaths.bundle = absoluteBundle;
118
- const oldSource = bundleToSource.get(absoluteBundle);
119
- if (oldSource) {
120
- assert.equal(
121
- oldSource,
122
- absoluteSrc,
123
- X`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`,
124
- );
123
+ const extractedSteps = await Promise.all(
124
+ proposalSteps.map((proposalStep, i) =>
125
+ Promise.all(
126
+ proposalStep.map(async (coreProposal, j) => {
127
+ const key = `${i}.${j}`;
128
+ // console.debug(`Parsing core proposal:`, coreProposal);
129
+
130
+ /** @type {string} */
131
+ let entrypoint;
132
+ /** @type {unknown[]} */
133
+ let args;
134
+ /** @type {string} */
135
+ let module;
136
+ if (typeof coreProposal === 'string') {
137
+ module = coreProposal;
138
+ entrypoint = 'defaultProposalBuilder';
139
+ args = [];
125
140
  } else {
126
- bundleToSource.set(absoluteBundle, absoluteSrc);
127
- }
128
- }
129
- // Don't harden the bundleHandle since we need to set the bundleName on
130
- // its unique identity later.
131
- thisProposalBundleHandles.add(bundleHandle);
132
- bundleHandleToAbsolutePaths.set(bundleHandle, harden(absolutePaths));
133
- return bundleHandle;
134
- };
135
- /** @type {import('./externalTypes.js').PublishBundleRef} */
136
- const publishRef = async handleP => {
137
- const handle = await handleP;
138
- bundleHandleToAbsolutePaths.has(handle) ||
139
- Fail`${handle} not in installed bundles`;
140
- return handle;
141
- };
142
- const proposal = await ns[entrypoint]({ publishRef, install }, ...args);
143
-
144
- // Add the proposal bundle handles in sorted order.
145
- const bundleSpecEntries = [...thisProposalBundleHandles.keys()]
146
- .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)])
147
- .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => {
148
- if (a < b) {
149
- return -1;
141
+ ({
142
+ module,
143
+ entrypoint = 'defaultProposalBuilder',
144
+ args = [],
145
+ } = coreProposal);
150
146
  }
151
- if (a > b) {
152
- return 1;
153
- }
154
- return 0;
155
- })
156
- .map(([handle, absolutePaths], j) => {
157
- // Transform the bundle handle identity into a bundleName reference.
158
- handle.bundleName = `coreProposal${thisProposalSequence}_${j}`;
159
- harden(handle);
160
-
161
- /** @type {[string, { sourceSpec: string }]} */
162
- const specEntry = [
163
- handle.bundleName,
164
- { sourceSpec: absolutePaths.source },
165
- ];
166
- return specEntry;
167
- });
168
-
169
- // Now that we've assigned all the bundleNames and hardened the
170
- // handles, we can extract the behavior bundle.
171
- const { sourceSpec, getManifestCall } = await deeplyFulfilledObject(
172
- harden(proposal),
173
- );
174
-
175
- const behaviorSource = pathResolve(initDir, sourceSpec);
176
- const behaviors = await import(behaviorSource);
177
- const [exportedGetManifest, ...manifestArgs] = getManifestCall;
178
- const { manifest: overrideManifest } = await behaviors[
179
- exportedGetManifest
180
- ](harden({ restoreRef: () => null }), ...manifestArgs);
181
-
182
- const behaviorBundleHandle = harden({
183
- bundleName: `coreProposal${thisProposalSequence}_behaviors`,
184
- });
185
- const behaviorAbsolutePaths = harden({
186
- source: behaviorSource,
187
- });
188
- bundleHandleToAbsolutePaths.set(
189
- behaviorBundleHandle,
190
- behaviorAbsolutePaths,
191
- );
192
-
193
- bundleSpecEntries.unshift([
194
- behaviorBundleHandle.bundleName,
195
- { sourceSpec: behaviorAbsolutePaths.source },
196
- ]);
197
-
198
- return harden({
199
- ref: behaviorBundleHandle,
200
- call: getManifestCall,
201
- overrideManifest,
202
- bundleSpecs: bundleSpecEntries,
203
- });
204
- }),
147
+
148
+ typeof module === 'string' ||
149
+ Fail`coreProposal module ${module} must be string`;
150
+ typeof entrypoint === 'string' ||
151
+ Fail`coreProposal entrypoint ${entrypoint} must be string`;
152
+ Array.isArray(args) || Fail`coreProposal args ${args} must be array`;
153
+
154
+ const thisProposalBundleHandles = new Set();
155
+ assert(getSequenceForProposal);
156
+ const thisProposalSequence = getSequenceForProposal(key);
157
+ const initPath = findModule(dirname, module);
158
+ const initDir = path.dirname(initPath);
159
+ /** @type {Record<string, import('./externalTypes.js').ProposalBuilder>} */
160
+ const ns = await import(initPath);
161
+ const install = (srcSpec, bundlePath) => {
162
+ const absoluteSrc = findModule(initDir, srcSpec);
163
+ const bundleHandle = {};
164
+ const absolutePaths = { source: absoluteSrc };
165
+ if (bundlePath) {
166
+ const absoluteBundle = pathResolve(initDir, bundlePath);
167
+ absolutePaths.bundle = absoluteBundle;
168
+ const oldSource = bundleToSource.get(absoluteBundle);
169
+ if (oldSource) {
170
+ oldSource === absoluteSrc ||
171
+ Fail`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`;
172
+ } else {
173
+ bundleToSource.set(absoluteBundle, absoluteSrc);
174
+ }
175
+ }
176
+ // Don't harden the bundleHandle since we need to set the bundleName on
177
+ // its unique identity later.
178
+ thisProposalBundleHandles.add(bundleHandle);
179
+ bundleHandleToAbsolutePaths.set(
180
+ bundleHandle,
181
+ harden(absolutePaths),
182
+ );
183
+ return bundleHandle;
184
+ };
185
+ /** @type {import('./externalTypes.js').PublishBundleRef} */
186
+ const publishRef = async handleP => {
187
+ const handle = await handleP;
188
+ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
189
+ // @ts-ignore xxx types
190
+ bundleHandleToAbsolutePaths.has(handle) ||
191
+ Fail`${handle} not in installed bundles`;
192
+ return handle;
193
+ };
194
+ const proposal = await ns[entrypoint](
195
+ {
196
+ publishRef,
197
+ // @ts-expect-error not statically verified to return a full obj
198
+ install,
199
+ },
200
+ ...args,
201
+ );
202
+
203
+ // Add the proposal bundle handles in sorted order.
204
+ const bundleSpecEntries = await Promise.all(
205
+ [...thisProposalBundleHandles.keys()]
206
+ .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)])
207
+ .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => {
208
+ if (a < b) {
209
+ return -1;
210
+ }
211
+ if (a > b) {
212
+ return 1;
213
+ }
214
+ return 0;
215
+ })
216
+ .map(async ([handle, absolutePaths], k) => {
217
+ // Transform the bundle handle identity into a bundleName reference.
218
+ const specEntry = await handleToBundleSpec(
219
+ handle,
220
+ absolutePaths.source,
221
+ thisProposalSequence,
222
+ String(k),
223
+ );
224
+ harden(handle);
225
+ return specEntry;
226
+ }),
227
+ );
228
+
229
+ // Now that we've assigned all the bundleNames and hardened the
230
+ // handles, we can extract the behavior bundle.
231
+ const { sourceSpec, getManifestCall } = await deeplyFulfilledObject(
232
+ harden(proposal),
233
+ );
234
+
235
+ const proposalSource = pathResolve(initDir, sourceSpec);
236
+ const proposalNS = await import(proposalSource);
237
+ const [manifestGetterName, ...manifestGetterArgs] = getManifestCall;
238
+ manifestGetterName in proposalNS ||
239
+ Fail`proposal ${proposalSource} missing export ${manifestGetterName}`;
240
+ const { manifest: customManifest } = await proposalNS[
241
+ manifestGetterName
242
+ ](harden({ restoreRef: () => null }), ...manifestGetterArgs);
243
+
244
+ const behaviorBundleHandle = {};
245
+ const specEntry = await handleToBundleSpec(
246
+ behaviorBundleHandle,
247
+ proposalSource,
248
+ thisProposalSequence,
249
+ 'proposalNS',
250
+ );
251
+ bundleSpecEntries.unshift(specEntry);
252
+
253
+ bundleHandleToAbsolutePaths.set(
254
+ behaviorBundleHandle,
255
+ harden({
256
+ source: proposalSource,
257
+ }),
258
+ );
259
+
260
+ return /** @type {const} */ ([
261
+ key,
262
+ {
263
+ ref: behaviorBundleHandle,
264
+ call: getManifestCall,
265
+ customManifest,
266
+ bundleSpecs: bundleSpecEntries,
267
+ },
268
+ ]);
269
+ }),
270
+ ),
271
+ ),
205
272
  );
206
273
 
207
274
  // Extract all the bundle specs in already-sorted order.
208
275
  const bundles = Object.fromEntries(
209
- extracted.flatMap(({ bundleSpecs }) => bundleSpecs),
276
+ extractedSteps.flatMap(step =>
277
+ step.flatMap(([_key, { bundleSpecs }]) => bundleSpecs),
278
+ ),
210
279
  );
211
280
  harden(bundles);
212
281
 
213
- // Extract the manifest references and calls.
214
- const makeCPArgs = extracted.map(({ ref, call, overrideManifest }) => ({
215
- ref,
216
- call,
217
- overrideManifest,
218
- }));
219
- harden(makeCPArgs);
282
+ const codeSteps = extractedSteps.map(extractedStep => {
283
+ // Extract the manifest references and calls.
284
+ const metadataRecords = extractedStep.map(([_key, extractedSpec]) => {
285
+ const { ref, call, customManifest } = extractedSpec;
286
+ return { ref, call, customManifest };
287
+ });
288
+ harden(metadataRecords);
220
289
 
221
- const code = `\
222
- // This is generated by @agoric/cosmic-swingset/src/extract-proposal.js - DO NOT EDIT
290
+ const code = `\
291
+ // This is generated by @agoric/deploy-script-support/src/extract-proposal.js - DO NOT EDIT
223
292
  /* eslint-disable */
224
293
 
225
- const makeCoreProposalArgs = harden(${stringify(makeCPArgs, true)});
226
-
227
- const makeCoreProposalBehavior = ${makeCoreProposalBehavior};
294
+ const metadataRecords = harden(${stringify(metadataRecords, true)});
228
295
 
229
- (${makeEnactCoreProposals})({ makeCoreProposalArgs, E });
296
+ // Make an enactCoreProposals function and "export" it by way of script completion value.
297
+ // It is constructed by an IIFE to ensure the absence of global bindings for
298
+ // makeCoreProposalBehavior and makeEnactCoreProposals (the latter referencing the former),
299
+ // which may not be necessary but preserves behavior pre-dating
300
+ // https://github.com/Agoric/agoric-sdk/pull/8712 .
301
+ const enactCoreProposals = ((
302
+ makeCoreProposalBehavior = ${makeCoreProposalBehavior},
303
+ makeEnactCoreProposals = ${makeEnactCoreProposals},
304
+ ) => makeEnactCoreProposals({ metadataRecords, E }))();
305
+ enactCoreProposals;
230
306
  `;
307
+ return defangAndTrim(code);
308
+ });
231
309
 
232
310
  // console.debug('created bundles from proposals:', coreProposals, bundles);
233
- return {
311
+ return harden({
234
312
  bundles,
235
- code: defangAndTrim(code),
313
+ codeSteps,
236
314
  bundleHandleToAbsolutePaths,
237
- };
315
+ });
238
316
  };
@@ -13,6 +13,9 @@
13
13
  import { E } from '@endo/far';
14
14
  import url from 'url';
15
15
 
16
+ /** @typedef {ReturnType<import('./endo-pieces-contract.js')['start']>['publicFacet']} BundleMaker */
17
+ /** @typedef {ReturnType<BundleMaker['makeBundler']>} Bundler */
18
+
16
19
  export const makeGetBundlerMaker =
17
20
  (homeP, { lookup, bundleSource }) =>
18
21
  async ({
@@ -20,6 +23,7 @@ export const makeGetBundlerMaker =
20
23
  log = console.log,
21
24
  } = {}) => {
22
25
  const { board: optionalBoard, zoe, scratch } = await homeP;
26
+ /** @type {() => Promise<BundleMaker>} */
23
27
  const lookupOrCreate = async () => {
24
28
  // Locate the bundler maker if any already exists at the given path.
25
29
  let bundlerMaker = await lookup(JSON.parse(BUNDLER_MAKER_LOOKUP));
package/src/offer.js CHANGED
@@ -19,7 +19,7 @@ import { AmountMath } from '@agoric/ertp/src/amountMath.js';
19
19
  * @param {ERef<any>} walletAdmin - an internal type of the
20
20
  * wallet, not defined here
21
21
  * @param {ERef<ZoeService>} zoe
22
- * @param {ERef<Purse>} zoeInvitationPurse
22
+ * @param {ERef<Purse<'set'>>} zoeInvitationPurse
23
23
  */
24
24
  export const makeOfferAndFindInvitationAmount = (
25
25
  walletAdmin,
@@ -28,7 +28,7 @@ export const makeOfferAndFindInvitationAmount = (
28
28
  ) => {
29
29
  /**
30
30
  * @param {Record<string, any>} invitationDetailsCriteria
31
- * @returns {Promise<Amount>} invitationAmount
31
+ * @returns {Promise<Amount<'set'>>} invitationAmount
32
32
  */
33
33
  const findInvitationAmount = async invitationDetailsCriteria => {
34
34
  const invitationAmount = await E(zoeInvitationPurse).getCurrentAmount();
@@ -7,6 +7,25 @@ import { createBundles } from '@agoric/internal/src/node/createBundles.js';
7
7
  import { defangAndTrim, mergePermits, stringify } from './code-gen.js';
8
8
  import { makeCoreProposalBehavior, permits } from './coreProposalBehavior.js';
9
9
 
10
+ /**
11
+ * @callback WriteCoreProposal
12
+ * @param {string} filePrefix
13
+ * @param {import('./externalTypes.js').ProposalBuilder} proposalBuilder
14
+ * @returns {Promise<void>}
15
+ */
16
+
17
+ /**
18
+ *
19
+ * @param {*} homeP
20
+ * @param {*} endowments
21
+ * @param {{
22
+ * getBundlerMaker: () => Promise<import('./getBundlerMaker.js').BundleMaker>,
23
+ * getBundleSpec: (...args: *) => Promise<import('./externalTypes.js').ManifestBundleRef>,
24
+ * log?: typeof console.log,
25
+ * writeFile?: typeof fs.promises.writeFile
26
+ * }} io
27
+ * @returns {WriteCoreProposal}
28
+ */
10
29
  export const makeWriteCoreProposal = (
11
30
  homeP,
12
31
  endowments,
@@ -20,6 +39,7 @@ export const makeWriteCoreProposal = (
20
39
  const { bundleSource, pathResolve } = endowments;
21
40
 
22
41
  let bundlerCache;
42
+ /** @returns {import('./getBundlerMaker.js').Bundler} */
23
43
  const getBundler = () => {
24
44
  if (!bundlerCache) {
25
45
  bundlerCache = E(getBundlerMaker()).makeBundler({
@@ -32,15 +52,15 @@ export const makeWriteCoreProposal = (
32
52
  const mergeProposalPermit = async (proposal, additionalPermits) => {
33
53
  const {
34
54
  sourceSpec,
35
- getManifestCall: [exportedGetManifest, ...manifestArgs],
55
+ getManifestCall: [manifestGetterName, ...manifestGetterArgs],
36
56
  } = proposal;
37
57
 
38
- const manifestNs = await import(pathResolve(sourceSpec));
58
+ const proposalNS = await import(pathResolve(sourceSpec));
39
59
 
40
60
  // We only care about the manifest, not any restoreRef calls.
41
- const { manifest } = await manifestNs[exportedGetManifest](
42
- { restoreRef: x => `restoreRef:${x}` },
43
- ...manifestArgs,
61
+ const { manifest } = await proposalNS[manifestGetterName](
62
+ harden({ restoreRef: x => `restoreRef:${x}` }),
63
+ ...manifestGetterArgs,
44
64
  );
45
65
 
46
66
  const mergedPermits = mergePermits(manifest);
@@ -50,8 +70,18 @@ export const makeWriteCoreProposal = (
50
70
  };
51
71
  };
52
72
 
53
- let mutex = Promise.resolve();
73
+ let mutex =
74
+ /** @type {Promise<import('./externalTypes.js').ManifestBundleRef | undefined>} */ (
75
+ Promise.resolve()
76
+ );
77
+ /** @type {WriteCoreProposal} */
54
78
  const writeCoreProposal = async (filePrefix, proposalBuilder) => {
79
+ /**
80
+ *
81
+ * @param {string} entrypoint
82
+ * @param {string} [bundlePath]
83
+ * @returns {Promise<NodeModule>}
84
+ */
55
85
  const getBundle = async (entrypoint, bundlePath) => {
56
86
  if (!bundlePath) {
57
87
  return bundleSource(pathResolve(entrypoint));
@@ -62,20 +92,36 @@ export const makeWriteCoreProposal = (
62
92
  return ns.default;
63
93
  };
64
94
 
65
- // Install an entrypoint.
95
+ const bundles = [];
96
+
97
+ /**
98
+ * Install an entrypoint.
99
+ *
100
+ * @param {string} entrypoint
101
+ * @param {string} [bundlePath]
102
+ * @param {unknown} [opts]
103
+ * @returns {Promise<import('./externalTypes.js').ManifestBundleRef>}
104
+ */
66
105
  const install = async (entrypoint, bundlePath, opts) => {
67
106
  const bundle = getBundle(entrypoint, bundlePath);
68
107
 
69
108
  // Serialise the installations.
70
- mutex = E.when(mutex, () => {
109
+ mutex = E.when(mutex, async () => {
71
110
  // console.log('installing', { filePrefix, entrypoint, bundlePath });
72
- return getBundleSpec(bundle, getBundler, opts);
111
+ const spec = await getBundleSpec(bundle, getBundler, opts);
112
+ bundles.push({
113
+ entrypoint,
114
+ ...spec,
115
+ });
116
+ return spec;
73
117
  });
118
+ // @ts-expect-error xxx mutex type narrowing
74
119
  return mutex;
75
120
  };
76
121
 
77
122
  // Await a reference then publish to the board.
78
123
  const cmds = [];
124
+ /** @param {Promise<import('./externalTypes.js').ManifestBundleRef>} refP */
79
125
  const publishRef = async refP => {
80
126
  const { fileName, ...ref } = await refP;
81
127
  if (fileName) {
@@ -93,7 +139,7 @@ export const makeWriteCoreProposal = (
93
139
  // console.log('created', { filePrefix, sourceSpec, getManifestCall });
94
140
 
95
141
  // Extract the top-level permit.
96
- const { permits: proposalPermit, manifest: overrideManifest } =
142
+ const { permits: proposalPermit, manifest: customManifest } =
97
143
  await mergeProposalPermit(proposal, permits);
98
144
 
99
145
  // Get an install
@@ -106,10 +152,14 @@ export const makeWriteCoreProposal = (
106
152
 
107
153
  const manifestBundleRef = ${stringify(manifestBundleRef)};
108
154
  const getManifestCall = harden(${stringify(getManifestCall, true)});
109
- const overrideManifest = ${stringify(overrideManifest, true)};
110
-
111
- // Make the behavior the completion value.
112
- (${makeCoreProposalBehavior})({ manifestBundleRef, getManifestCall, overrideManifest, E });
155
+ const customManifest = ${stringify(customManifest, true)};
156
+
157
+ // Make a behavior function and "export" it by way of script completion value.
158
+ // It is constructed by an anonymous invocation to ensure the absence of a global binding
159
+ // for makeCoreProposalBehavior, which may not be necessary but preserves behavior pre-dating
160
+ // https://github.com/Agoric/agoric-sdk/pull/8712 .
161
+ const behavior = (${makeCoreProposalBehavior})({ manifestBundleRef, getManifestCall, customManifest, E });
162
+ behavior;
113
163
  `;
114
164
 
115
165
  const trimmed = defangAndTrim(code);
@@ -125,6 +175,18 @@ const overrideManifest = ${stringify(overrideManifest, true)};
125
175
  log(`creating ${proposalJsFile}`);
126
176
  await writeFile(proposalJsFile, trimmed);
127
177
 
178
+ const plan = {
179
+ name: filePrefix,
180
+ script: proposalJsFile,
181
+ permit: proposalPermitJsonFile,
182
+ bundles,
183
+ };
184
+
185
+ await writeFile(
186
+ `${filePrefix}-plan.json`,
187
+ `${JSON.stringify(plan, null, 2)}\n`,
188
+ );
189
+
128
190
  log(`\
129
191
  You can now run a governance submission command like:
130
192
  agd tx gov submit-proposal swingset-core-eval ${proposalPermitJsonFile} ${proposalJsFile} \\