diff --git a/packages/cli/src/commands/identify.js b/packages/cli/src/commands/identify.js new file mode 100644 index 0000000000..6a2b42d05b --- /dev/null +++ b/packages/cli/src/commands/identify.js @@ -0,0 +1,14 @@ +/* global process */ +import os from 'os'; +import { E } from '@endo/far'; +import { withEndoBootstrap } from '../context.js'; + +export const identify = async ({ cancel, cancelled, sockPath, namePath }) => + withEndoBootstrap({ os, process }, async ({ bootstrap }) => { + const defaultHostFormulaIdentifier = 'host'; + const formulaId = await E(bootstrap).identifyFrom( + defaultHostFormulaIdentifier, + namePath, + ); + console.log(formulaId); + }); diff --git a/packages/cli/src/commands/show.js b/packages/cli/src/commands/show.js index 554da43b16..86d04bb95b 100644 --- a/packages/cli/src/commands/show.js +++ b/packages/cli/src/commands/show.js @@ -1,10 +1,14 @@ /* global process */ import os from 'os'; import { E } from '@endo/far'; -import { withEndoParty } from '../context.js'; +import { withEndoBootstrap } from '../context.js'; -export const show = async ({ cancel, cancelled, sockPath, name, partyNames }) => - withEndoParty(partyNames, { os, process }, async ({ party }) => { - const pet = await E(party).lookup(name); +export const show = async ({ cancel, cancelled, sockPath, namePath }) => + withEndoBootstrap({ os, process }, async ({ bootstrap }) => { + const defaultHostFormulaIdentifier = 'host'; + const pet = await E(bootstrap).lookupFrom( + defaultHostFormulaIdentifier, + namePath, + ); console.log(pet); }); diff --git a/packages/cli/src/endo.js b/packages/cli/src/endo.js index c624f328ee..466b638e31 100644 --- a/packages/cli/src/endo.js +++ b/packages/cli/src/endo.js @@ -341,18 +341,21 @@ export const main = async rawArgs => { }); program - .command('show ') + .command('identify ') + .description('prints the formulaId for the named path') + .action(async namePathString => { + const namePath = namePathString.split('.'); + const { identify } = await import('./commands/identify.js'); + return identify({ namePath }); + }); + + program + .command('show ') .description('prints the named value') - .option( - '-a,--as ', - 'Pose as named party (as named by current party)', - collect, - [], - ) - .action(async (name, cmd) => { - const { as: partyNames } = cmd.opts(); + .action(async (namePathString, cmd) => { + const namePath = namePathString.split('.'); const { show } = await import('./commands/show.js'); - return show({ name, partyNames }); + return show({ namePath }); }); program diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 9d3e8b28b5..b940b32d20 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -107,6 +107,51 @@ const makeEndoBootstrap = ( return `readable-blob-sha512:${sha512Hex}`; }; + /** @type {import('./types.js').IdentifyFromFn} */ + const identifyFrom = async (originFormulaIdentifier, namePath) => { + await null; + if (namePath.length === 0) { + return originFormulaIdentifier; + } + // Attempt to shortcut the identify by reading directly from the petStore. + const { type: formulaType, number: formulaNumber } = parseFormulaIdentifier( + originFormulaIdentifier, + ); + if (formulaType === 'host' || formulaType === 'host-id512') { + let storeFormulaIdentifier = `pet-store`; + if (formulaType === 'host-id512') { + storeFormulaIdentifier = `pet-store-id512:${formulaNumber}`; + } + // eslint-disable-next-line no-use-before-define + const petStore = await provideValueForFormulaIdentifier( + storeFormulaIdentifier, + ); + const result = await E(petStore).identify(namePath); + // Use result if present, otherwise fallback to identifying from the host. + if (result !== undefined) { + return result; + } + } + // eslint-disable-next-line no-use-before-define + const origin = await provideValueForFormulaIdentifier( + originFormulaIdentifier, + ); + return E(origin).identify(namePath); + }; + + /** @type {import('./types.js').LookupFromFn} */ + const lookupFrom = async (originFormulaIdentifier, namePath) => { + const formulaIdentifier = await identifyFrom( + originFormulaIdentifier, + namePath, + ); + if (formulaIdentifier === undefined) { + throw new Error(`Failed to lookup ${namePath.join('.')}`); + } + // eslint-disable-next-line no-use-before-define + return provideValueForFormulaIdentifier(formulaIdentifier); + }; + /** * @param {string} workerId512 */ @@ -413,6 +458,7 @@ const makeEndoBootstrap = ( const external = petStorePowers.makeOwnPetStore( 'pet-store', assertPetName, + identifyFrom, ); return { external, internal: undefined }; } else if (formulaIdentifier === 'pet-inspector') { @@ -469,6 +515,7 @@ const makeEndoBootstrap = ( const external = petStorePowers.makeIdentifiedPetStore( formulaNumber, assertPetName, + identifyFrom, ); return { external, internal: undefined }; } else if (formulaType === 'host-id512') { @@ -627,6 +674,7 @@ const makeEndoBootstrap = ( }); const makeMailbox = makeMailboxMaker({ + identifyFrom, formulaIdentifierForRef, provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, @@ -651,6 +699,24 @@ const makeEndoBootstrap = ( makeMailbox, }); + const allowedInspectorFormulaTypes = [ + 'eval-id512', + 'lookup-id512', + 'make-unconfined-id512', + 'make-bundle-id512', + 'guest-id512', + 'web-bundle', + ]; + + const allowedInspectorFormulaIdentificationsByType = { + eval: ['endowments', 'worker'], + lookup: ['hub'], + guest: ['host'], + 'make-bundle': ['bundle', 'powers', 'worker'], + 'make-unconfined': ['powers', 'worker'], + 'web-bundle': ['bundle', 'powers'], + }; + /** * Creates an inspector for the current party's pet store, used to create * inspectors for values therein. Notably, can provide references to otherwise @@ -665,28 +731,81 @@ const makeEndoBootstrap = ( petStoreFormulaIdentifier, ); + const identifyOnFormula = (formula, propertyName, namePath) => { + if ( + allowedInspectorFormulaIdentificationsByType[formula.type] === undefined + ) { + // Cannot provide a reference under the requested formula type. + return { formulaIdentifier: undefined, remainderPath: namePath }; + } + const allowedInspectorFormulaIdentifications = + allowedInspectorFormulaIdentificationsByType[formula.type]; + if (!allowedInspectorFormulaIdentifications.includes(propertyName)) { + // Cannot provide a reference for the requested property. + return { formulaIdentifier: undefined, remainderPath: namePath }; + } + if (formula.type === 'eval' && propertyName === 'endowments') { + // We consume an additional part of the path for the endowment name. + const [endowmentName, ...namePathRest] = namePath; + assertPetName(endowmentName); + const endowmentIndex = formula.names.indexOf(endowmentName); + if (endowmentIndex === -1) { + // Cannot provide a reference to the missing endowment. + return { formulaIdentifier: undefined, remainderPath: namePath }; + } + const formulaIdentifier = formula.values[endowmentIndex]; + return { formulaIdentifier, remainderPath: namePathRest }; + } + const formulaIdentifier = formula[propertyName]; + return { formulaIdentifier, remainderPath: namePath }; + }; + + /** @type {import('./types.js').IdentifyFn} */ + const identify = async maybeNamePath => { + const namePath = Array.isArray(maybeNamePath) + ? maybeNamePath + : [maybeNamePath]; + const [petName, propertyName, ...namePathRest] = namePath; + assertPetName(petName); + assertPetName(propertyName); + const entryFormulaIdentifier = petStore.identifyLocal(petName); + if (entryFormulaIdentifier === undefined) { + return undefined; + } + const { type: formulaType, number: formulaNumber } = + parseFormulaIdentifier(entryFormulaIdentifier); + // Identify is only supported on these types of formulas. + if (!allowedInspectorFormulaTypes.includes(formulaType)) { + return undefined; + } + const formula = await persistencePowers.readFormula( + formulaType, + formulaNumber, + ); + const { formulaIdentifier, remainderPath } = identifyOnFormula( + formula, + propertyName, + namePathRest, + ); + if (formulaIdentifier === undefined) { + return undefined; + } + return identifyFrom(formulaIdentifier, remainderPath); + }; + /** * @param {string} petName - The pet name to inspect. * @returns {Promise} An * inspector for the value of the given pet name. */ const lookup = async petName => { - const formulaIdentifier = petStore.lookup(petName); + const formulaIdentifier = petStore.identifyLocal(petName); if (formulaIdentifier === undefined) { throw new Error(`Unknown pet name ${petName}`); } const { type: formulaType, number: formulaNumber } = parseFormulaIdentifier(formulaIdentifier); - if ( - ![ - 'eval-id512', - 'lookup-id512', - 'make-unconfined-id512', - 'make-bundle-id512', - 'guest-id512', - 'web-bundle', - ].includes(formulaType) - ) { + if (!allowedInspectorFormulaTypes.includes(formulaType)) { return makeInspector(formulaType, formulaNumber, harden({})); } const formula = await persistencePowers.readFormula( @@ -765,6 +884,7 @@ const makeEndoBootstrap = ( const list = () => petStore.list(); const info = Far('Endo inspector facet', { + identify, lookup, list, }); @@ -783,6 +903,9 @@ const makeEndoBootstrap = ( host: () => provideValueForFormulaIdentifier('host'), + identifyFrom, + lookupFrom, + leastAuthority: () => leastAuthority, webPageJs: () => provideValueForFormulaIdentifier('web-page-js'), diff --git a/packages/daemon/src/guest.js b/packages/daemon/src/guest.js index 3ec17b4804..df7e3707bd 100644 --- a/packages/daemon/src/guest.js +++ b/packages/daemon/src/guest.js @@ -45,6 +45,7 @@ export const makeGuestMaker = ({ } const { + identify, lookup, reverseLookup, followMessages, @@ -80,6 +81,7 @@ export const makeGuestMaker = ({ /** @type {import('@endo/eventual-send').ERef} */ const guest = Far('EndoGuest', { has, + identify, lookup, reverseLookup, request, diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index cc81133491..bd899b36f6 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -39,9 +39,10 @@ export const makeHostMaker = ({ ); const { + identifyLocal, + identify, lookup, reverseLookup, - lookupFormulaIdentifierForName, listMessages, provideLookupFormula, followMessages, @@ -75,7 +76,7 @@ export const makeHostMaker = ({ /** @type {string | undefined} */ let formulaIdentifier; if (petName !== undefined) { - formulaIdentifier = lookupFormulaIdentifierForName(petName); + formulaIdentifier = identifyLocal(petName); } if (formulaIdentifier === undefined) { /** @type {import('./types.js').GuestFormula} */ @@ -129,7 +130,7 @@ export const makeHostMaker = ({ if (typeof workerName !== 'string') { throw new Error('worker name must be string'); } - let workerFormulaIdentifier = lookupFormulaIdentifierForName(workerName); + let workerFormulaIdentifier = identifyLocal(workerName); if (workerFormulaIdentifier === undefined) { const workerId512 = await randomHex512(); workerFormulaIdentifier = `worker-id512:${workerId512}`; @@ -156,7 +157,7 @@ export const makeHostMaker = ({ return `worker-id512:${workerId512}`; } assertPetName(workerName); - let workerFormulaIdentifier = lookupFormulaIdentifierForName(workerName); + let workerFormulaIdentifier = identifyLocal(workerName); if (workerFormulaIdentifier === undefined) { const workerId512 = await randomHex512(); workerFormulaIdentifier = `worker-id512:${workerId512}`; @@ -170,7 +171,7 @@ export const makeHostMaker = ({ * @param {string | 'NONE' | 'SELF' | 'ENDO'} partyName */ const providePowersFormulaIdentifier = async partyName => { - let guestFormulaIdentifier = lookupFormulaIdentifierForName(partyName); + let guestFormulaIdentifier = identifyLocal(partyName); if (guestFormulaIdentifier === undefined) { const guest = await provideGuest(partyName); guestFormulaIdentifier = formulaIdentifierForRef.get(guest); @@ -216,9 +217,7 @@ export const makeHostMaker = ({ const petNamePath = petNamePathFrom(petNameOrPath); if (petNamePath.length === 1) { - const formulaIdentifier = lookupFormulaIdentifierForName( - petNamePath[0], - ); + const formulaIdentifier = identifyLocal(petNamePath[0]); if (formulaIdentifier === undefined) { throw new Error(`Unknown pet name ${q(petNamePath[0])}`); } @@ -308,8 +307,7 @@ export const makeHostMaker = ({ workerName, ); - const bundleFormulaIdentifier = - lookupFormulaIdentifierForName(bundleName); + const bundleFormulaIdentifier = identifyLocal(bundleName); if (bundleFormulaIdentifier === undefined) { throw new TypeError(`Unknown pet name for bundle: ${bundleName}`); } @@ -364,7 +362,7 @@ export const makeHostMaker = ({ /** @type {string | undefined} */ let formulaIdentifier; if (petName !== undefined) { - formulaIdentifier = lookupFormulaIdentifierForName(petName); + formulaIdentifier = identifyLocal(petName); } if (formulaIdentifier === undefined) { const id512 = await randomHex512(); @@ -393,8 +391,7 @@ export const makeHostMaker = ({ * @param {string | 'NONE' | 'SELF' | 'ENDO'} powersName */ const provideWebPage = async (webPageName, bundleName, powersName) => { - const bundleFormulaIdentifier = - lookupFormulaIdentifierForName(bundleName); + const bundleFormulaIdentifier = identifyLocal(bundleName); if (bundleFormulaIdentifier === undefined) { throw new Error(`Unknown pet name: ${q(bundleName)}`); } @@ -442,6 +439,7 @@ export const makeHostMaker = ({ /** @type {import('./types.js').EndoHost} */ const host = Far('EndoHost', { has, + identify, lookup, reverseLookup, listMessages, diff --git a/packages/daemon/src/mail.js b/packages/daemon/src/mail.js index 3290280b5d..5965c5d837 100644 --- a/packages/daemon/src/mail.js +++ b/packages/daemon/src/mail.js @@ -1,6 +1,5 @@ // @ts-check -import { E } from '@endo/far'; import { makePromiseKit } from '@endo/promise-kit'; import { makeChangeTopic } from './pubsub.js'; import { makeIteratorRef } from './reader-ref.js'; @@ -9,6 +8,7 @@ import { assertPetName } from './pet-name.js'; const { quote: q } = assert; export const makeMailboxMaker = ({ + identifyFrom, provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, formulaIdentifierForRef, @@ -35,12 +35,27 @@ export const makeMailboxMaker = ({ /** * @param {string} petName + * @returns {string | undefined} */ - const lookupFormulaIdentifierForName = petName => { + const identifyLocal = petName => { if (Object.hasOwn(specialNames, petName)) { return specialNames[petName]; } - return petStore.lookup(petName); + return petStore.identifyLocal(petName); + }; + + /** @type {import('./types.js').IdentifyFn} */ + const identify = async maybeNamePath => { + const namePath = Array.isArray(maybeNamePath) + ? maybeNamePath + : [maybeNamePath]; + const [headName, ...namePathRest] = namePath; + const formulaIdentifier = identifyLocal(headName); + if (formulaIdentifier === undefined) { + return undefined; + } else { + return identifyFrom(formulaIdentifier, namePathRest); + } }; /** @@ -48,20 +63,12 @@ export const makeMailboxMaker = ({ * @returns {Promise} The value resolved by the pet name path. */ const lookup = async (...petNamePath) => { - const [headName, ...tailNames] = petNamePath; - const formulaIdentifier = lookupFormulaIdentifierForName(headName); - if (formulaIdentifier === undefined) { - throw new TypeError(`Unknown pet name: ${q(headName)}`); - } - // Behold, recursion: - return tailNames.reduce( - (currentValue, petName) => E(currentValue).lookup(petName), - provideValueForFormulaIdentifier(formulaIdentifier), - ); + const formulaIdentifier = await identify(petNamePath); + return provideValueForFormulaIdentifier(formulaIdentifier); }; const terminate = async petName => { - const formulaIdentifier = lookupFormulaIdentifierForName(petName); + const formulaIdentifier = identifyLocal(petName); if (formulaIdentifier === undefined) { throw new TypeError(`Unknown pet name: ${q(petName)}`); } @@ -113,7 +120,7 @@ export const makeMailboxMaker = ({ // naming hub's formula identifier and the pet name path. // A "naming hub" is an objected with a variadic lookup method. At present, // the only such objects are guests and hosts. - const hubFormulaIdentifier = lookupFormulaIdentifierForName('SELF'); + const hubFormulaIdentifier = identifyLocal('SELF'); const digester = makeSha512(); digester.updateText(`${hubFormulaIdentifier},${petNamePath.join(',')}`); const lookupFormulaNumber = digester.digestHex(); @@ -263,7 +270,7 @@ export const makeMailboxMaker = ({ ) => { if (responseName !== undefined) { /** @type {string | undefined} */ - let formulaIdentifier = senderPetStore.lookup(responseName); + let formulaIdentifier = senderPetStore.identifyLocal(responseName); if (formulaIdentifier === undefined) { formulaIdentifier = await requestFormulaIdentifier( what, @@ -302,7 +309,7 @@ export const makeMailboxMaker = ({ if (resolveRequest === undefined) { throw new Error(`No pending request for number ${messageNumber}`); } - const formulaIdentifier = lookupFormulaIdentifierForName(resolutionName); + const formulaIdentifier = identifyLocal(resolutionName); if (formulaIdentifier === undefined) { throw new TypeError( `No formula exists for the pet name ${q(resolutionName)}`, @@ -357,8 +364,7 @@ export const makeMailboxMaker = ({ * @param {Array} petNames */ const send = async (recipientName, strings, edgeNames, petNames) => { - const recipientFormulaIdentifier = - lookupFormulaIdentifierForName(recipientName); + const recipientFormulaIdentifier = identifyLocal(recipientName); if (recipientFormulaIdentifier === undefined) { throw new Error(`Unknown pet name for party: ${recipientName}`); } @@ -390,7 +396,7 @@ export const makeMailboxMaker = ({ } const formulaIdentifiers = petNames.map(petName => { - const formulaIdentifier = lookupFormulaIdentifierForName(petName); + const formulaIdentifier = identifyLocal(petName); if (formulaIdentifier === undefined) { throw new Error(`Unknown pet name ${q(petName)}`); } @@ -469,8 +475,7 @@ export const makeMailboxMaker = ({ * @param {string} responseName */ const request = async (recipientName, what, responseName) => { - const recipientFormulaIdentifier = - lookupFormulaIdentifierForName(recipientName); + const recipientFormulaIdentifier = identifyLocal(recipientName); if (recipientFormulaIdentifier === undefined) { throw new Error(`Unknown pet name for party: ${recipientName}`); } @@ -550,10 +555,11 @@ export const makeMailboxMaker = ({ }; return harden({ + identifyLocal, + identify, lookup, reverseLookup, reverseLookupFormulaIdentifier, - lookupFormulaIdentifierForName, provideLookupFormula, followMessages, listMessages, diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index 30bbaec6e9..70c51607a7 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -19,9 +19,14 @@ export const makePetStoreMaker = (filePowers, locator) => { /** * @param {string} petNameDirectoryPath * @param {(name: string) => void} assertValidName + * @param {import('./types.js').IdentifyFromFn} identifyFrom * @returns {Promise>} */ - const makePetStoreAtPath = async (petNameDirectoryPath, assertValidName) => { + const makePetStoreAtPath = async ( + petNameDirectoryPath, + assertValidName, + identifyFrom, + ) => { /** @type {Map} */ const petNames = new Map(); /** @type {Map>} */ @@ -67,11 +72,27 @@ export const makePetStoreMaker = (filePowers, locator) => { return petNames.has(petName); }; - /** @param {string} petName */ - const lookup = petName => { + /** + * @param {string} petName + * @returns {string | undefined} + */ + const identifyLocal = petName => { assertValidName(petName); - const formulaIdentifier = petNames.get(petName); - return formulaIdentifier; + return petNames.get(petName); + }; + + /** @type {import('./types.js').IdentifyFn} */ + const identify = async maybeNamePath => { + const namePath = Array.isArray(maybeNamePath) + ? maybeNamePath + : [maybeNamePath]; + const [headName, ...namePathRest] = namePath; + const formulaIdentifier = identifyLocal(headName); + if (formulaIdentifier === undefined) { + return undefined; + } else { + return identifyFrom(formulaIdentifier, namePathRest); + } }; /** @@ -259,7 +280,8 @@ export const makePetStoreMaker = (filePowers, locator) => { /** @type {import('./types.js').PetStore} */ const petStore = { has, - lookup, + identifyLocal, + identify, reverseLookup, list, follow, @@ -276,8 +298,9 @@ export const makePetStoreMaker = (filePowers, locator) => { /** * @param {string} id * @param {(name: string) => void} assertValidName + * @param {import('./types.js').IdentifyFromFn} identifyFrom */ - const makeIdentifiedPetStore = (id, assertValidName) => { + const makeIdentifiedPetStore = (id, assertValidName, identifyFrom) => { if (!validIdPattern.test(id)) { throw new Error(`Invalid identifier for pet store ${q(id)}`); } @@ -289,16 +312,25 @@ export const makePetStoreMaker = (filePowers, locator) => { prefix, suffix, ); - return makePetStoreAtPath(petNameDirectoryPath, assertValidName); + return makePetStoreAtPath( + petNameDirectoryPath, + assertValidName, + identifyFrom, + ); }; /** * @param {string} name * @param {(name: string) => void} assertValidName + * @param {import('./types.js').IdentifyFromFn} identifyFrom */ - const makeOwnPetStore = (name, assertValidName) => { + const makeOwnPetStore = (name, assertValidName, identifyFrom) => { const petNameDirectoryPath = filePowers.joinPath(locator.statePath, name); - return makePetStoreAtPath(petNameDirectoryPath, assertValidName); + return makePetStoreAtPath( + petNameDirectoryPath, + assertValidName, + identifyFrom, + ); }; return { diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 0a3e4a6654..7a0c893158 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -176,8 +176,24 @@ export interface Controller { terminator: Terminator; } +export type IdentifyFromFn = ( + originIdentifier: string, + namePath: string[], +) => Promise; + +export type LookupFromFn = ( + originIdentifier: string, + namePath: string[], +) => Promise; + +export type IdentifyFn = ( + namePath: string | string[], +) => Promise; + export interface PetStore { has(petName: string): boolean; + identifyLocal(petName: string): string | undefined; + identify: IdentifyFn; list(): Array; follow(): Promise>>; listEntries(): Array<[string, FormulaIdentifierRecord]>; @@ -191,7 +207,6 @@ export interface PetStore { write(petName: string, formulaIdentifier: string): Promise; remove(petName: string); rename(fromPetName: string, toPetName: string); - lookup(petName: string): string | undefined; reverseLookup(formulaIdentifier: string): Array; } @@ -226,6 +241,7 @@ export interface EndoGuest { } export interface EndoHost { + identify: IdentifyFn; listMessages(): Promise>; followMessages(): ERef>; lookup(...petNamePath: string[]): Promise; @@ -308,10 +324,12 @@ export type PetStorePowers = { makeIdentifiedPetStore: ( id: string, assertValidName: AssertValidNameFn, + identifyFrom: IdentifyFromFn, ) => Promise>; makeOwnPetStore: ( name: string, assertValidName: AssertValidNameFn, + identifyFrom: IdentifyFromFn, ) => Promise>; };