Sign In

ses

Package Overview
Dependencies
Maintainers
7
Versions
116
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ses - npm Package Compare versions

Comparing version
1.14.0
to
1.15.0
+110
-141
dist/types.d.cts

@@ -6,2 +6,4 @@ /**

import '@endo/immutable-arraybuffer/shim.js';
/* eslint-disable no-restricted-globals, vars-on-top, no-var */

@@ -191,9 +193,19 @@

interface Stringable {
toString(): string;
}
/** @deprecated */
type StringPayload = Stringable;
/**
* A call to the `details` template literal makes and returns a fresh details
* token, which is a frozen empty object associated with the arguments of that
* `details` template literal expression.
* A call to the {@link details} template literal makes and returns a fresh
* DetailsToken, which is a frozen empty object associated with the arguments of
* that expression.
*/
export type DetailsToken = Record<any, never>;
/** Either a plain string, or made by the `details` template literal tag. */
/**
* A plain string, or a {@link DetailsToken} from the {@link details} template
* literal tag.
*/
export type Details = string | DetailsToken;

@@ -205,3 +217,3 @@

* the constructor. Rather, the `errorName` determines how this error is
* identified in the causal console log's output.
* identified in an associated console's output.
*/

@@ -286,6 +298,2 @@ errorName?: string;

interface StringablePayload {
toString(): string;
}
/**

@@ -314,17 +322,15 @@ * TypeScript does not treat `AggregateErrorConstructor` as a subtype of

/**
* Makes and returns an `assert` function object that shares the bookkeeping
* state defined by this module with other `assert` function objects made by
* `makeAssert`. This state is per-module-instance and is exposed by the
* `loggedErrorHandler` above. We refer to `assert` as a "function object"
* because it can be called directly as a function, but also has methods that
* can be called.
* Makes and returns an `assert` function object that shares the
* per-module-instance bookkeeping state defined by this module with other
* `assert` function objects made by `makeAssert`. We refer to `assert` as a
* "function object" because it can be called directly as a function, but also
* has callable methods of its own.
*
* If `optRaise` is provided, the returned `assert` function object will call
* `optRaise(reason)` before throwing the error. This enables `optRaise` to
* engage in even more violent termination behavior, like terminating the vat,
* that prevents execution from reaching the following throw. However, if
* `optRaise` returns normally, which would be unusual, the throw following
* `optRaise(reason)` would still happen.
* If `raise` is provided, the returned `assert` function object will call
* `raise(reason)` before throwing an assertion-failure error. This enables
* `raise` to engage in even more violent termination behavior, like process
* termination, that prevents execution from reaching the following throw.
* However, if `raise(reason)` returns normally, which would be unusual, that
* throw still happens.
*/
// Behold: recursion.
// eslint-disable-next-line no-use-before-define

@@ -334,5 +340,5 @@ export type MakeAssert = (raise?: Raise, unredacted?: boolean) => Assert;

export type BaseAssert = (
/** The truthy/falsy value we're testing */
flag: any,
/** The details of what was asserted */
/** The condition whose truthiness is being asserted */
condition: any,
/** The details associated with assertion failure (falsy condition) */
details?: Details,

@@ -342,3 +348,3 @@ /** An optional alternate error constructor to use */

options?: AssertMakeErrorOptions,
) => asserts flag;
) => asserts condition;

@@ -349,5 +355,3 @@ export interface AssertionFunctions extends BaseAssert {

/**
* The `assert.equal` method
*
* Assert that two values must be `Object.is`.
* Assert that two values are the same as observed by `Object.is`.
*/

@@ -359,3 +363,3 @@ equal<T>(

expected: T,
/** The details of what was asserted */
/** The details associated with assertion failure (`Object.is` returning false) */
details?: Details,

@@ -368,8 +372,5 @@ /** An optional alternate error constructor to use */

/**
* The `assert.string` method.
*
* Assert that a value is a primitive string.
* `assert.string(v)` is equivalent to `assert.typeof(v, 'string')`. We
* special case this one because it is the most frequently used.
*
* Assert an expected typeof result.
*/

@@ -383,10 +384,5 @@ string(

/**
* The `assert.fail` method.
*
* Fail an assertion, recording full details to the console and
* raising an exception with a message in which `details` substitution values
* have been redacted.
*
* The optional `optDetails` can be a string for backwards compatibility
* with the nodejs assertion library.
* Fail an assertion, raising an exception with a `message` in which unquoted
* `details` substitution values may have been redacted into `typeof` types
* but are still available for logging to an associated console.
*/

@@ -404,7 +400,7 @@ fail(

/**
* Aka the `makeError` function as imported from `@endo/errors`
*
* Recording unredacted details for the console.
* Create an error with a `message` in which unquoted {@link details}
* substitution values may have been redacted into lossy `typeof` output but
* are still available for logging to an associated console.
*/
error(
makeError(
/** The details of what was asserted */

@@ -418,7 +414,4 @@ details?: Details,

/**
* Aka the `annotateError` function as imported from `@endo/errors`
*
* Annotate an error with details, potentially to be used by an
* augmented console such as the causal console of `console.js`, to
* provide extra information associated with logged errors.
* Associate `details` with `error`, potentially to be logged by an associated
* console for providing extra information about the error.
*/

@@ -428,16 +421,13 @@ note(error: Error, details: Details): void;

/**
* Use the `details` function as a template literal tag to create
* informative error messages. The assertion functions take such messages
* as optional arguments:
* ```js
* assert(sky.isBlue(), details`${sky.color} should be "blue"`);
* ```
* or following the normal convention to locally rename `details` to `X`
* and `quote` to `q` like `const { details: X, quote: q } = assert;`:
* ```js
* assert(sky.isBlue(), X`${sky.color} should be "blue"`);
* ```
* Use as a template literal tag to create an opaque {@link DetailsToken} for
* use with other assertion functions that might redact unquoted substitution
* values (i.e., those that are not output from the {@link quote} function)
* into lossy `typeof` output but still preserve them for logging to an
* associated console.
*
* The normal convention is to locally rename `details` to `X` like
* `const { details: X, quote: q, Fail } = assert;`.
* However, note that in most cases it is preferable to instead use the `Fail`
* template literal tag (which has the same input signature as `details`
* but automatically creates and throws an error):
* template literal tag (which has the same input signature but automatically
* creates and throws an error):
* ```js

@@ -447,12 +437,2 @@ * sky.isBlue() || Fail`${sky.color} should be "blue"`;

*
* The details template tag returns a `DetailsToken` object that can print
* itself with the formatted message in two ways.
* It will report full details to the console, but
* mask embedded substitution values with their typeof information in the thrown error
* to prevent revealing secrets up the exceptional path. In the example
* above, the thrown error may reveal only that `sky.color` is a string,
* whereas the same diagnostic printed to the console reveals that the
* sky was green. This masking can be disabled for an individual substitution value
* using `quote`.
*
* The `raw` property of an input template array is ignored, so a simple

@@ -467,33 +447,28 @@ * array of strings may be provided directly.

/**
* Use the `Fail` function as a template literal tag to efficiently
* create and throw a `details`-style error only when a condition is not satisfied.
* Use as a template literal tag to create and throw an error in whose
* `message` unquoted substitution values (i.e., those that are not output
* from the {@link quote} function) may have been redacted into lossy `typeof`
* output but are still available for logging to an associated console.
*
* For example, using the normal convention to locally rename properties like
* `const { quote: q, Fail } = assert;`:
* ```js
* condition || Fail`...complaint...`;
* sky.isBlue() || Fail`${sky.color} should be "blue"`;
* ```
* This avoids the overhead of creating usually-unnecessary errors like
*
* This `||` pattern saves the cost of creating {@link DetailsToken} and/or
* error instances when the asserted condition holds, but can weaken
* TypeScript static reasoning due to
* https://github.com/microsoft/TypeScript/issues/51426 . Where this is a
* problem, instead express the assertion as
* ```js
* assert(condition, details`...complaint...`);
* if (!sky.isBlue()) {
* // This `throw` does not affect runtime behavior since `Fail` throws, but
* // might be needed to improve static analysis.
* throw Fail`${sky.color} should be "blue"`;
* }
* ```
* while improving readability over alternatives like
* ```js
* condition || assert.fail(details`...complaint...`);
* ```
*
* However, due to current weakness in TypeScript, static reasoning
* is less powerful with the `||` patterns than with an `assert` call.
* Until/unless https://github.com/microsoft/TypeScript/issues/51426 is fixed,
* for `||`-style assertions where this loss of static reasoning is a problem,
* instead express the assertion as
* ```js
* if (!condition) {
* Fail`...complaint...`;
* }
* ```
* or, if needed,
* ```js
* if (!condition) {
* // `throw` is noop since `Fail` throws, but it improves static analysis
* throw Fail`...complaint...`;
* }
* ```
* The `raw` property of an input template array is ignored, so a simple
* array of strings may be provided directly.
*/

@@ -503,31 +478,35 @@ Fail(template: TemplateStringsArray | string[], ...args: any): never;

/**
* To "declassify" and quote a substitution value used in a
* ``` details`...` ``` template literal, enclose that substitution expression
* in a call to `quote`. This makes the value appear quoted
* (as if with `JSON.stringify`) in the message of the thrown error. The
* payload itself is still passed unquoted to the console as it would be
* without `quote`.
* Wrap a value such that its use as a substitution value in a template
* literal tagged with {@link details} or {@link Fail} will result in it
* appearing quoted (in a way similar to but more general than
* `JSON.stringify`) rather than redacted in the `message` of errors based on
* the resulting {@link DetailsToken}.
*
* For example, the following will reveal the expected sky color, but not the
* actual incorrect sky color, in the thrown error's message:
* This does not affect representation in output of an associated console,
* which still logs the value as it would without `quote`, but *does* reveal
* it to functions in the propagation path of such errors.
*
* For example, the following will reveal the expected value in the thrown
* error's `message`, but only the _type_ of the actual incorrect value (using
* the normal convention to locally rename properties like
* `const { quote: q, Fail } = assert;`):
* ```js
* sky.color === expectedColor || Fail`${sky.color} should be ${quote(expectedColor)}`;
* actual === expected || Fail`${actual} should be ${q(expected)}`;
* ```
*
* The normal convention is to locally rename `details` to `X` and `quote` to `q`
* like `const { details: X, quote: q } = assert;`, so the above example would then be
* ```js
* sky.color === expectedColor || Fail`${sky.color} should be ${q(expectedColor)}`;
* ```
* The optional `space` parameter matches that of `JSON.stringify`, and is
* used to request insertion of non-semantic line feeds, indentation, and
* separating spaces in the output for improving readability of objects and
* arrays.
*/
quote(
/** What to declassify */
payload: any,
spaces?: string | number,
): /** The declassified and quoted payload */ StringablePayload;
quote(value: any, space?: string | number): Stringable;
/**
* Embed a string directly into error details without wrapping punctuation.
* Wrap a string such that its use as a substitution value in a template
* literal tagged with {@link details} or {@link Fail} will be treated
* literally rather than being quoted or redacted.
*
* To avoid injection attacks that exploit quoting confusion, this must NEVER
* be used with data that is possibly attacker-controlled.
*
* As a further safeguard, we fall back to quoting any input that is not a

@@ -542,8 +521,3 @@ * string of sufficiently word-like parts separated by isolated spaces (rather

*/
bare(
/** What to declassify */
payload: any,
spaces?: string | number,
): /** The declassified payload without quotes (beware confusion hazard) */
StringablePayload;
bare(text: string, spaces?: string | number): Stringable;
}

@@ -553,21 +527,16 @@

makeAssert: MakeAssert;
error: AssertionUtilities['makeError'];
}
/**
* assert that expr is truthy, with an optional details to describe
* the assertion. It is a tagged template literal like
* ```js
* assert(expr, details`....`);`
* ```
* Assert that `condition` is truthy, with optional details associated with
* assertion failure (falsy condition).
*
* The literal portions of the template are assumed non-sensitive, as
* are the `typeof` types of the substitution values. These are
* assembled into the thrown error message. The actual contents of the
* substitution values are assumed sensitive, to be revealed to
* the console only. We assume only the virtual platform's owner can read
* what is written to the console, where the owner is in a privileged
* position over computation running on that platform.
*
* The optional `optDetails` can be a string for backwards compatibility
* with the nodejs assertion library.
* The literal portions of the template used to make a {@link DetailsToken} are
* assumed non-sensitive, as are the `typeof` output for substitution values.
* These are assembled into the error `message`. The actual contents of the
* substitution values are assumed sensitive and usually redacted, to be
* revealed only to an associated console. We assume only the virtual platform's
* owner can read what is written to the console, where the owner is in a
* privileged position over computation running on that platform.
*/

@@ -574,0 +543,0 @@ export type Assert = AssertionFunctions &

{
"name": "ses",
"version": "1.14.0",
"version": "1.15.0",
"description": "Hardened JavaScript for Fearless Cooperation",

@@ -83,3 +83,3 @@ "keywords": [

"clean": "rm -rf dist",
"cover": "c8 ava",
"cover": "c8 ava --config test/_ava-ses.config.js",
"demo": "python3 -m http.server",

@@ -103,24 +103,17 @@ "lint": "yarn lint:types && yarn lint:eslint",

"devDependencies": {
"@babel/generator": "^7.26.3",
"@babel/parser": "~7.26.2",
"@babel/traverse": "~7.25.9",
"@babel/types": "~7.26.0",
"@endo/compartment-mapper": "^1.6.3",
"@endo/module-source": "^1.3.3",
"@endo/test262-runner": "^0.1.48",
"@types/babel__traverse": "^7.20.5",
"ava": "^6.1.3",
"babel-eslint": "^10.1.0",
"c8": "^7.14.0",
"@babel/generator": "^7.28.3",
"@babel/parser": "~7.28.3",
"@babel/traverse": "~7.28.3",
"@babel/types": "~7.28.2",
"@endo/compartment-mapper": "^2.0.0",
"@endo/module-source": "^1.4.0",
"@endo/test262-runner": "^0.1.49",
"ava": "catalog:dev",
"c8": "catalog:dev",
"core-js": "^3.31.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.31.0",
"eslint": "catalog:dev",
"hermes-engine-cli": "^0.12.0",
"prettier": "^3.5.3",
"terser": "^5.16.6",
"tsd": "^0.31.2",
"typescript": "~5.8.3"
"tsd": "catalog:dev",
"typescript": "~5.9.2"
},

@@ -155,3 +148,3 @@ "files": [

},
"gitHead": "9815aea9541f241389d2135c6097a7442bdffa17"
"gitHead": "f91329e8616a19f131d009356a5f11ef11c839cc"
}

@@ -118,3 +118,3 @@ # SES

See [`lockdown` options](docs/lockdown.md) for configuration options to
See [`lockdown` options](../../docs/lockdown.md) for configuration options to
`lockdown`. However, all of these have sensible defaults that should

@@ -674,3 +674,3 @@ work for most projects out of the box.

console uses these side tables to output more informative diagnostics.
[Logging Errors](./src/error/README.md) explains the design.
[Logging Errors](../../docs/errors.md) explains the design.

@@ -677,0 +677,0 @@ ### Controlling Module-Loading Errors

import { globalThis } from './commons.js';
import { assert } from './error/assert.js';
import { makeAssert } from './error/assert.js';
globalThis.assert = assert;
globalThis.assert = makeAssert(undefined, true);

@@ -80,3 +80,2 @@ /**

fromEntries,
hasOwn,
} = Object;

@@ -89,2 +88,3 @@

matchAll: matchAllSymbol,
replace: replaceSymbol,
unscopables: unscopablesSymbol,

@@ -172,6 +172,23 @@ keyFor: symbolKeyFor,

/**
* @deprecated Use `hasOwn` instead
*/
export const objectHasOwnProperty = hasOwn;
// See https://github.com/endojs/endo/issues/2930
if (!('hasOwn' in Object)) {
const ObjectPrototypeHasOwnProperty = objectPrototype.hasOwnProperty;
const hasOwnShim = (obj, key) => {
if (obj === undefined || obj === null) {
// We need to add this extra test because of differences in
// the order in which `hasOwn` vs `hasOwnProperty` validates
// arguments.
throw TypeError('Cannot convert undefined or null to object');
}
return apply(ObjectPrototypeHasOwnProperty, obj, [key]);
};
defineProperty(Object, 'hasOwn', {
value: hasOwnShim,
writable: true,
enumerable: false,
configurable: true,
});
}
export const { hasOwn } = Object;
//

@@ -217,5 +234,31 @@ export const arrayFilter = uncurryThis(arrayPrototype.filter);

//
export const regexpTest = uncurryThis(regexpPrototype.test);
/**
* `regexpExec` is provided in exclusion of `regexpTest`, which would be
* vulnerable to RegExp.prototype poisoning.
*/
export const regexpExec = uncurryThis(regexpPrototype.exec);
/**
* @type { &
* ((thisArg: RegExp, string: string, replaceValue: string) => string) &
* ((thisArg: RegExp, string: string, replacer: (substring: string, ...args: any[]) => string) => string)
* }
*/
export const regexpReplace = /** @type {any} */ (
uncurryThis(regexpPrototype[replaceSymbol])
);
export const matchAllRegExp = uncurryThis(regexpPrototype[matchAllSymbol]);
const { _regexpConstructor, ...regexpDescriptors } =
getOwnPropertyDescriptors(regexpPrototype);
arrayForEach(ownKeys(regexpDescriptors), key => {
const desc = regexpDescriptors[/** @type {any} */ (key)];
desc.configurable = false;
if (desc.writable) desc.writable = false;
});
/**
* Protect a RegExp instance against RegExp.prototype poisoning ("exec",
* "flags", Symbol.replace, etc.).
* @type {<T extends RegExp>(regexp: T) => T}
*/
export const sealRegexp = regexp =>
seal(defineProperties(regexp, regexpDescriptors));
//

@@ -226,20 +269,15 @@ export const stringEndsWith = uncurryThis(stringPrototype.endsWith);

export const stringMatch = uncurryThis(stringPrototype.match);
export const generatorNext = uncurryThis(generatorPrototype.next);
export const generatorThrow = uncurryThis(generatorPrototype.throw);
// `stringReplace` is intentionally omitted because it would be vulnerable to
// RegExp.prototype poisoning; use `regexpReplace(re, str, replacer)` instead
// (and `sealRegexp` on its regular expressions).
export const stringSearch = uncurryThis(stringPrototype.search);
export const stringSlice = uncurryThis(stringPrototype.slice);
/**
* @type { &
* ((thisArg: string, searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string) => string) &
* ((thisArg: string, searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string) => string)
* }
* `stringSplit` should only be used with a string separator; regular
* expressions are vulnerable to RegExp.prototype poisoning.
* @type {(thisArg: string, separator: string, limit?: number) => string[]}
*/
export const stringReplace = /** @type {any} */ (
uncurryThis(stringPrototype.replace)
export const stringSplit = /** @type {any} */ (
uncurryThis(stringPrototype.split)
);
export const stringSearch = uncurryThis(stringPrototype.search);
export const stringSlice = uncurryThis(stringPrototype.slice);
export const stringSplit =
/** @type {(thisArg: string, splitter: string | RegExp | { [Symbol.split](string: string, limit?: number): string[]; }, limit?: number) => string[]} */ (
uncurryThis(stringPrototype.split)
);
export const stringStartsWith = uncurryThis(stringPrototype.startsWith);

@@ -260,2 +298,5 @@ export const iterateString = uncurryThis(stringPrototype[iteratorSymbol]);

//
export const generatorNext = uncurryThis(generatorPrototype.next);
export const generatorThrow = uncurryThis(generatorPrototype.throw);
//
const { all } = Promise;

@@ -276,11 +317,2 @@ export const promiseAll = promises => apply(all, Promise, [promises]);

/**
* getConstructorOf()
* Return the constructor from an instance.
*
* @param {Function} fn
*/
export const getConstructorOf = fn =>
reflectGet(getPrototypeOf(fn), 'constructor');
/**
* TODO Consolidate with `isPrimitive` that's currently in `@endo/pass-style`.

@@ -338,8 +370,35 @@ * Layering constraints make this tricky, which is why we haven't yet figured

const er1StackDesc = getOwnPropertyDescriptor(Error('er1'), 'stack');
const er2StackDesc = getOwnPropertyDescriptor(TypeError('er2'), 'stack');
// The error repair mechanism is very similar to code in
// pass-style/src/error.js and these implementations should be kept in sync.
/**
* We gratuitiously construct a TypeError instance using syntax in order to
* obviate the possibility that code that ran before SES (for which we are
* irreducable vulnerable) may have replaced the global TypeError constructor.
* We treat the nature of this error instance as the source of truth for the
* nature of runtime constructed errors on the platform, particularly whether
* such errors will have an own "stack" property with getters and setters.
* At time of writing (2025) we know of no comparable mechanism for obtaining a
* host-generated base Error instance, but we corroborate the nature of the
* global Error constructor's instances and refuse to initialize SES in an
* environment where the syntactic TypeError and global Error produce
* inconsistent "stack" properties.
* @returns {TypeError}
*/
const makeTypeError = () => {
try {
// @ts-expect-error deliberate TypeError
null.null;
throw TypeError('obligatory'); // To convince the type flow inferrence.
} catch (error) {
return error;
}
};
const errorStackDesc = getOwnPropertyDescriptor(Error('obligatory'), 'stack');
const typeErrorStackDesc = getOwnPropertyDescriptor(makeTypeError(), 'stack');
let feralStackGetter;
let feralStackSetter;
if (er1StackDesc && er2StackDesc && er1StackDesc.get) {
if (typeErrorStackDesc && typeErrorStackDesc.get) {
// We should only encounter this case on v8 because of its problematic

@@ -355,6 +414,7 @@ // error own stack accessor behavior.

// This is therefore the case that we repair.
typeof er1StackDesc.get === 'function' &&
er1StackDesc.get === er2StackDesc.get &&
typeof er1StackDesc.set === 'function' &&
er1StackDesc.set === er2StackDesc.set
errorStackDesc &&
typeof typeErrorStackDesc.get === 'function' &&
typeErrorStackDesc.get === errorStackDesc.get &&
typeof typeErrorStackDesc.set === 'function' &&
typeErrorStackDesc.set === errorStackDesc.set
) {

@@ -364,4 +424,4 @@ // Otherwise, we have own stack accessor properties that are outside

// before we know how to repair them.
feralStackGetter = freeze(er1StackDesc.get);
feralStackSetter = freeze(er1StackDesc.set);
feralStackGetter = freeze(typeErrorStackDesc.get);
feralStackSetter = freeze(typeErrorStackDesc.set);
} else {

@@ -368,0 +428,0 @@ // See https://github.com/endojs/endo/blob/master/packages/ses/error-codes/SES_UNEXPECTED_ERROR_OWN_STACK_ACCESSOR.md

@@ -81,2 +81,3 @@ import { toStringTagSymbol, iteratorSymbol } from './commons.js';

},
'%IteratorPrototype%': {

@@ -95,2 +96,4 @@ toString: true,

export const moderateEnablements = {
...minEnablements,
'%ObjectPrototype%': {

@@ -108,2 +111,6 @@ toString: true,

'%IteratorPrototype%': {
[iteratorSymbol]: true, // is sometimes used in custom iterators and generators implementations eg. @rive-app/canvas
},
// Function.prototype has no 'prototype' property to enable.

@@ -174,10 +181,2 @@ // Function instances have their own 'name' and 'length' properties

},
'%IteratorPrototype%': {
toString: true,
// https://github.com/tc39/proposal-iterator-helpers
constructor: true,
// https://github.com/tc39/proposal-iterator-helpers
[toStringTagSymbol]: true,
},
};

@@ -184,0 +183,0 @@

@@ -28,5 +28,6 @@ // Copyright (C) 2019 Agoric, under Apache License 2.0

isError,
regexpTest,
regexpExec,
regexpReplace,
sealRegexp,
stringIndexOf,
stringReplace,
stringSlice,

@@ -51,7 +52,7 @@ stringStartsWith,

/**
* @import {BaseAssert, Assert, AssertionFunctions, AssertionUtilities, StringablePayload, DetailsToken, MakeAssert} from '../../types.js'
* @import {LogArgs, NoteCallback, LoggedErrorHandler} from "./internal-types.js";
* @import {BaseAssert, Assert, AssertionFunctions, AssertionUtilities, DeprecatedAssertionUtilities, Stringable, DetailsToken, MakeAssert} from '../../types.js';
* @import {LogArgs, NoteCallback, LoggedErrorHandler} from './internal-types.js';
*/
// For our internal debugging purposes, uncomment
// For internal debugging purposes, uncomment
// const internalDebugConsole = console;

@@ -61,11 +62,15 @@

/** @type {WeakMap<StringablePayload, any>} */
/**
* Maps the result of a `quote` or `bare` call back to its input value.
*
* @type {WeakMap<Stringable, any>}
*/
const declassifiers = new WeakMap();
/** @type {AssertionUtilities['quote']} */
const quote = (payload, spaces = undefined) => {
const quote = (value, spaces = undefined) => {
const result = freeze({
toString: freeze(() => bestEffortStringify(payload, spaces)),
toString: freeze(() => bestEffortStringify(value, spaces)),
});
weakmapSet(declassifiers, result, payload);
weakmapSet(declassifiers, result, value);
return result;

@@ -80,10 +85,10 @@ };

*/
const bare = (payload, spaces = undefined) => {
if (typeof payload !== 'string' || !regexpTest(canBeBare, payload)) {
return quote(payload, spaces);
const bare = (text, spaces = undefined) => {
if (typeof text !== 'string' || !regexpExec(canBeBare, text)) {
return quote(text, spaces);
}
const result = freeze({
toString: freeze(() => payload),
toString: freeze(() => text),
});
weakmapSet(declassifiers, result, payload);
weakmapSet(declassifiers, result, text);
return result;

@@ -96,15 +101,16 @@ };

/**
* @typedef {object} HiddenDetails
* @typedef {{ template: TemplateStringsArray | string[], args: any[] }} DetailsParts
*
* Captures the arguments passed to the `details` template string tag.
* The contents of a `details` template literal tag: literal strings (always at
* least one) and arbitrary substitution values from in between them.
*
* @property {TemplateStringsArray | string[]} template
* @property {any[]} args
* Unquoted substitution values are sensitive (and are redacted in error
* `message` strings), so a DetailsPart must not leak outside of this file.
*/
/**
* @type {WeakMap<DetailsToken, HiddenDetails>}
* Maps the result of a `details` tagged template literal back to a record of
* that template literal's contents.
*
* Maps from a details token which a `details` template literal returned
* to a record of the contents of that template literal expression.
* @type {WeakMap<DetailsToken, DetailsParts>}
*/

@@ -114,3 +120,6 @@ const hiddenDetailsMap = new WeakMap();

/**
* @param {HiddenDetails} hiddenDetails
* Construct an error message string from `details` template literal contents,
* replacing unquoted substitution values with redactions.
*
* @param {DetailsParts} hiddenDetails
* @returns {string}

@@ -136,12 +145,10 @@ */

/**
* Give detailsTokens a toString behavior. To minimize the overhead of
* creating new detailsTokens, we do this with an
* inherited `this` sensitive `toString` method, even though we normally
* avoid `this` sensitivity. To protect the method from inappropriate
* `this` application, it does something interesting only for objects
* registered in `redactedDetails`, which should be exactly the detailsTokens.
* Define `toString` behavior for DetailsToken. To minimize the overhead of
* creating new instances, we do this with an inherited `this`-sensitive method,
* even though we normally avoid such sensitivity. To protect the method from
* inappropriate application, it verifies that `this` is registered in
* `redactedDetails` before doing interesting work.
*
* The printing behavior must not reveal anything redacted, so we just use
* the same `getMessageString` we use to construct the redacted message
* string for a thrown assertion error.
* The behavior must not reveal anything redacted, so we use `getMessageString`
* to return the same value as the message for a thrown assertion-failure error.
*/

@@ -166,4 +173,5 @@ const DetailsTokenProto = freeze({

* There are some unconditional uses of `redactedDetails` in this module. All
* of them should be uses where the template literal has no redacted
* substitution values. In those cases, the two are equivalent.
* of them should be uses where the template literal has no redacted (unquoted)
* substitution values. In those cases, `redactedDetails` is equivalent to
* `unredactedDetails`.
*

@@ -173,6 +181,4 @@ * @type {AssertionUtilities['details']}

const redactedDetails = (template, ...args) => {
// Keep in mind that the vast majority of calls to `details` creates
// a details token that is never used, so this path must remain as fast as
// possible. Hence we store what we've got with little processing, postponing
// all the work to happen only if needed, for example, if an assertion fails.
// In case the result of this call is never used, perform as little processing
// as possible here to keep things fast.
const detailsToken = freeze({ __proto__: DetailsTokenProto });

@@ -207,4 +213,11 @@ weakmapSet(hiddenDetailsMap, detailsToken, { template, args });

const leadingSpacePattern = sealRegexp(/^ /);
const trailingSpacePattern = sealRegexp(/ $/);
/**
* @param {HiddenDetails} hiddenDetails
* Get arguments suitable for a console logger function (e.g., `console.error`)
* from `details` template literal contents, unquoting quoted substitution
* values.
*
* @param {DetailsParts} hiddenDetails
* @returns {LogArgs}

@@ -219,10 +232,18 @@ */

}
// Remove the extra spaces (since console.error puts them
// between each cause).
const priorWithoutSpace = stringReplace(arrayPop(logArgs) || '', / $/, '');
if (priorWithoutSpace !== '') {
arrayPush(logArgs, priorWithoutSpace);
// Remove substitution-adjacent spaces from template fixed-string parts
// (since console logging inserts its own argument-separating spaces).
const prevLiteralPart = regexpReplace(
trailingSpacePattern,
arrayPop(logArgs) || '',
'',
);
if (prevLiteralPart !== '') {
arrayPush(logArgs, prevLiteralPart);
}
const nextWithoutSpace = stringReplace(template[i + 1], /^ /, '');
arrayPush(logArgs, arg, nextWithoutSpace);
const nextLiteralPart = regexpReplace(
leadingSpacePattern,
template[i + 1],
'',
);
arrayPush(logArgs, arg, nextLiteralPart);
}

@@ -236,7 +257,7 @@ if (logArgs[logArgs.length - 1] === '') {

/**
* Maps from an error object to arguments suitable for a privileged console
* logger function such as `console.error`, including values that may be
* redacted in the error's `message`.
*
* @type {WeakMap<Error, LogArgs>}
*
* Maps from an error object to the log args that are a more informative
* alternative message for that error. When logging the error, these
* log args should be preferred to `error.message`.
*/

@@ -312,16 +333,13 @@ const hiddenMessageLogArgs = new WeakMap();

}
const droppedNote = create(objectPrototype, restDescs);
const dropped = create(objectPrototype, restDescs);
const droppedDetails = redactedDetails`originally with properties ${quote(dropped)}`;
// eslint-disable-next-line no-use-before-define
note(
error,
redactedDetails`originally with properties ${quote(droppedNote)}`,
);
note(error, droppedDetails);
}
for (const name of ownKeys(error)) {
// @ts-expect-error TS still confused by symbols as property names
// @ts-expect-error TypeScript is still confused by symbols as property keys
const desc = descs[name];
if (desc && hasOwn(desc, 'get')) {
defineProperty(error, name, {
value: error[name], // invoke the getter to convert to data property
});
const value = error[name]; // invokes the getter
defineProperty(error, name, { value });
}

@@ -333,3 +351,3 @@ }

/**
* @type {AssertionUtilities['error']}
* @type {AssertionUtilities['makeError']}
*/

@@ -346,5 +364,5 @@ const makeError = (

) => {
// Promote string-valued `optDetails` into a minimal DetailsParts
// consisting of that string as the sole literal part with no substitutions.
if (typeof optDetails === 'string') {
// If it is a string, use it as the literal part of the template so
// it doesn't get quoted.
optDetails = redactedDetails([optDetails]);

@@ -365,9 +383,7 @@ }

} else {
error = /** @type {ErrorConstructor} */ (errConstructor)(
messageString,
opts,
);
const ErrorCtor = /** @type {ErrorConstructor} */ (errConstructor);
error = ErrorCtor(messageString, opts);
// Since we need to tolerate `errors` on an AggregateError, we may as well
// tolerate it on all errors.
if (errors !== undefined) {
// Since we need to tolerate `errors` on an AggregateError, may as
// well tolerate it on all errors.
defineProperty(error, 'errors', {

@@ -395,7 +411,6 @@ value: errors,

const { addLogArgs, takeLogArgsArray } = makeNoteLogArgsArrayKit();
const { addLogArgs: addNoteLogArgs, takeLogArgsArray: takeAllNoteLogArgs } =
makeNoteLogArgsArrayKit();
/**
* @type {WeakMap<Error, NoteCallback[]>}
*
* An augmented console will normally only take the hidden noteArgs array once,

@@ -409,10 +424,12 @@ * when it logs the error being annotated. Once that happens, further

* are independent.
*
* @type {WeakMap<Error, NoteCallback[]>}
*/
const hiddenNoteCallbackArrays = new WeakMap();
const hiddenNoteCallbacks = new WeakMap();
/** @type {AssertionUtilities['note']} */
const note = (error, detailsNote) => {
// Promote string-valued `detailsNote` into a minimal DetailsParts consisting
// of that string as the sole literal part with no substitutions.
if (typeof detailsNote === 'string') {
// If it is a string, use it as the literal part of the template so
// it doesn't get quoted.
detailsNote = redactedDetails([detailsNote]);

@@ -425,3 +442,3 @@ }

const logArgs = getLogArgs(hiddenDetails);
const callbacks = weakmapGet(hiddenNoteCallbackArrays, error);
const callbacks = weakmapGet(hiddenNoteCallbacks, error);
if (callbacks !== undefined) {

@@ -432,3 +449,3 @@ for (const callback of callbacks) {

} else {
addLogArgs(error, logArgs);
addNoteLogArgs(error, logArgs);
}

@@ -467,17 +484,17 @@ };

takeMessageLogArgs: error => {
const result = weakmapGet(hiddenMessageLogArgs, error);
const logArgs = weakmapGet(hiddenMessageLogArgs, error);
weakmapDelete(hiddenMessageLogArgs, error);
return result;
return logArgs;
},
takeNoteLogArgsArray: (error, callback) => {
const result = takeLogArgsArray(error);
const logArgsArray = takeAllNoteLogArgs(error);
if (callback !== undefined) {
const callbacks = weakmapGet(hiddenNoteCallbackArrays, error);
const callbacks = weakmapGet(hiddenNoteCallbacks, error);
if (callbacks) {
arrayPush(callbacks, callback);
} else {
weakmapSet(hiddenNoteCallbackArrays, error, [callback]);
weakmapSet(hiddenNoteCallbacks, error, [callback]);
}
}
return result || [];
return logArgsArray || [];
},

@@ -493,3 +510,3 @@ };

*/
const makeAssert = (optRaise = undefined, unredacted = false) => {
export const makeAssert = (optRaise = undefined, unredacted = false) => {
const details = unredacted ? unredactedDetails : redactedDetails;

@@ -506,3 +523,2 @@ const assertFailedDetails = details`Check failed`;

if (optRaise !== undefined) {
// @ts-ignore returns `never` doesn't mean it isn't callable
optRaise(reason);

@@ -517,14 +533,12 @@ }

// Don't freeze or export `baseAssert` until we add methods.
// TODO If I change this from a `function` function to an arrow
// function, I seem to get type errors from TypeScript. Why?
// Don't freeze or export `assert` until we add methods.
/** @type {BaseAssert} */
function baseAssert(
flag,
const assert = (
condition,
optDetails = undefined,
errConstructor = undefined,
options = undefined,
) {
flag || fail(optDetails, errConstructor, options);
}
) => {
condition || fail(optDetails, errConstructor, options);
};

@@ -571,10 +585,13 @@ /** @type {AssertionFunctions['equal']} */

// Note that "assert === baseAssert"
/** @type {Assert} */
const assert = assign(baseAssert, {
error: makeError,
fail,
/** @type {Pick<AssertionFunctions, keyof AssertionFunctions>} */
const assertionFunctions = {
equal,
typeof: assertTypeof,
string: assertString,
fail,
};
/** @type {AssertionUtilities} */
const assertionUtilities = {
makeError,
note,

@@ -585,8 +602,16 @@ details,

bare,
makeAssert,
};
/** @type {DeprecatedAssertionUtilities} */
const deprecated = { error: makeError, makeAssert };
/** @type {Assert} */
const finishedAssert = assign(assert, {
...assertionFunctions,
...assertionUtilities,
...deprecated,
});
return freeze(assert);
return freeze(finishedAssert);
};
freeze(makeAssert);
export { makeAssert };

@@ -593,0 +618,0 @@ /** @type {Assert} */

@@ -24,3 +24,3 @@ // @ts-check

/** @import {StringablePayload} from '../../types.js' */
/** @import {Stringable} from '../../types.js' */

@@ -30,3 +30,3 @@ /**

*
* @param {(string | StringablePayload)[]} terms
* @param {(string | Stringable)[]} terms
* @param {"and" | "or"} conjunction

@@ -33,0 +33,0 @@ */

@@ -14,3 +14,2 @@ import {

regexpExec,
regexpTest,
weakmapGet,

@@ -106,8 +105,8 @@ weakmapSet,

export const filterFileName = fileName => {
if (!fileName) {
// Stack frames with no fileName should appear in concise stack traces.
return true;
if (fileName === null) {
// Seems to suppress builtins like `Array.every (<anonymous>)`
return false;
}
for (const filter of FILENAME_CENSORS) {
if (regexpTest(filter, fileName)) {
if (regexpExec(filter, fileName)) {
return false;

@@ -123,5 +122,5 @@ }

// Everything to the right of `/.../` is kept. Thus
// `'Object.bar (/vat-v1/.../eventual-send/test/deep-send.test.js:13:21)'`
// `'Object.bar (/vat-v1/.../errors/test/deep-send.test.js:13:21)'`
// simplifies to
// `'Object.bar (eventual-send/test/deep-send.test.js:13:21)'`.
// `'Object.bar (errors/test/deep-send.test.js:13:21)'`.
//

@@ -136,5 +135,5 @@ // See thread starting at

// Everything to the right of `.../` is kept. Thus
// `'Object.bar (.../eventual-send/test/deep-send.test.js:13:21)'`
// `'Object.bar (.../errors/test/deep-send.test.js:13:21)'`
// simplifies to
// `'Object.bar (eventual-send/test/deep-send.test.js:13:21)'`.
// `'Object.bar (errors/test/deep-send.test.js:13:21)'`.
//

@@ -150,5 +149,5 @@ // See thread starting at

// everything to its right is kept. Thus
// `'Object.bar (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/eventual-send/test/deep-send.test.js:13:21)'`
// `'Object.bar (/Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/errors/test/deep-send.test.js:13:21)'`
// simplifies to
// `'Object.bar (packages/eventual-send/test/deep-send.test.js:13:21)'`.
// `'Object.bar (packages/errors/test/deep-send.test.js:13:21)'`.
// Note that `/packages/` is a convention for monorepos encouraged by

@@ -163,3 +162,3 @@ // lerna.

// the right of `file://` is kept. Thus
// `'Object.bar (file:///Users/markmiller/src/ongithub/endojs/endo/packages/eventual-send/test/deep-send.test.js:13:21)'` is unchanged but
// `'Object.bar (file:///Users/markmiller/src/ongithub/endojs/endo/packages/errors/test/deep-send.test.js:13:21)'` is unchanged but
// `'Object.bar (file://test/deep-send.test.js:13:21)'`

@@ -231,2 +230,6 @@

// eslint-disable-next-line @endo/no-polymorphic-call
if (callSite.getFunctionName()?.startsWith('__HIDE_')) {
return false;
}
// eslint-disable-next-line @endo/no-polymorphic-call
return filterFileName(callSite.getFileName());

@@ -233,0 +236,0 @@ }

// @ts-check
/** @import {GenericErrorConstructor, AssertMakeErrorOptions, DetailsToken, StringablePayload} from '../../types.js' */
/** @import {GenericErrorConstructor, AssertMakeErrorOptions, DetailsToken, Stringable} from '../../types.js' */

@@ -5,0 +5,0 @@ /**

@@ -171,3 +171,2 @@ import {

const ab = new ArrayBuffer(0);
// @ts-expect-error TODO How do I add sliceToImmutable to ArrayBuffer type?
// eslint-disable-next-line @endo/no-polymorphic-call

@@ -174,0 +173,0 @@ const iab = ab.sliceToImmutable();

@@ -28,2 +28,3 @@ // Copyright (C) 2018 Agoric

stringSplit,
symbolFor,
noEvalEvaluate,

@@ -367,2 +368,26 @@ getOwnPropertyNames,

// Install Object[@harden] or abort.
const symbolForHarden = symbolFor('harden');
const priorHarden = intrinsics.Object[symbolForHarden];
if (priorHarden) {
// By convention, if a module like @endo/harden gets used before lockdown,
// it will install itself as a non-configurable, non-writable property over
// Object[@harden] so that versions of SES predating the introduction of
// Object[@harden] will fail to lockdown because they cannot remove an
// unknown intrinsic.
// All newer versions explicitly check for Object[@harden] (here).
// The @endo/harden implementation additionally captures a stack trace
// where harden was first used to assist developers in tracking down the
// hardened module that was initialized before lockdown.
if (priorHarden.lockdownError) {
throw priorHarden.lockdownError;
}
// And in the event a library installs Object[@harden] without leaving a
// hint, we fall back to a generic lockdown error.
throw new TypeError(
'Cannot lockdown (repairIntrinsics) if a prior harden implementation has been used and installed. Check for libraries using @endo/harden before lockdown.',
);
}
intrinsics.Object[symbolForHarden] = tamedHarden;
const hostIntrinsics = { __proto__: null };

@@ -409,15 +434,12 @@

// The default `assert` installed by `assert-shim.js` does not redact errors,
// leaving `lockdown` or `repairIntrinsics` with the obligation to replace it
// with a redacting version, unless the caller opts-out with errorTaming set
// to `unsafe` or `unsafe-debug`.
// The inverse was true through version 1.13.0, except the configuration
// was disregarded and the redacting `assert` left in place if lexical
// `assert` differed from `globalThis.assert`.
// @ts-ignore assert is absent on globalThis type def.
if (
(errorTaming === 'unsafe' || errorTaming === 'unsafe-debug') &&
globalThis.assert === assert
) {
// If errorTaming is 'unsafe' or 'unsafe-debug' we replace the
// global assert with
// one whose `details` template literal tag does not redact
// unmarked substitution values. IOW, it blabs information that
// was supposed to be secret from callers, as an aid to debugging
// at a further cost in safety.
// @ts-ignore assert is absent on globalThis type def.
globalThis.assert = makeAssert(undefined, true);
if (errorTaming !== 'unsafe' && errorTaming !== 'unsafe-debug') {
globalThis.assert = makeAssert();
}

@@ -424,0 +446,0 @@

@@ -357,2 +357,21 @@ /** @import {ModuleExportsNamespace} from '../types.js' */

const wireUpExportNotifier = (exportName, notify) => {
if (!notifiers[exportName] && notify !== false) {
notifiers[exportName] = notify;
// exported live binding state
let value;
const update = newValue => (value = newValue);
notify(update);
exportsProps[exportName] = {
get() {
return value;
},
set: undefined,
enumerable: true,
configurable: false,
};
}
};
// Per the calling convention for the moduleFunctor generated from

@@ -411,5 +430,5 @@ // an ESM, the `imports` function gets called once up front

if (reexportMap[specifier]) {
// Make named reexports candidates too.
// Set up reexport notifiers instantly so they are available in cycles.
for (const [localName, exportedName] of reexportMap[specifier]) {
candidateAll[exportedName] = importNotifiers[localName];
wireUpExportNotifier(exportedName, importNotifiers[localName]);
}

@@ -420,18 +439,3 @@ }

for (const [exportName, notify] of entries(candidateAll)) {
if (!notifiers[exportName] && notify !== false) {
notifiers[exportName] = notify;
// exported live binding state
let value;
const update = newValue => (value = newValue);
notify(update);
exportsProps[exportName] = {
get() {
return value;
},
set: undefined,
enumerable: true,
configurable: false,
};
}
wireUpExportNotifier(exportName, notify);
}

@@ -438,0 +442,0 @@

@@ -510,2 +510,7 @@ /* eslint-disable no-restricted-globals */

values: fn,
'RegisteredSymbol(harden)': {
...fn,
// Installed with hardenTaming: 'unsafe'
isFake: 'boolean',
},
// https://github.com/tc39/proposal-accessible-object-hasownproperty

@@ -512,0 +517,0 @@ hasOwn: fn,

@@ -1,2 +0,2 @@

import { functionBind, globalThis } from './commons.js';
import { globalThis } from './commons.js';
import { assert } from './error/assert.js';

@@ -8,5 +8,37 @@

/* eslint-disable @endo/no-polymorphic-call */
/**
* To address https://github.com/endojs/endo/issues/2908,
* the `consoleReporter` uses the current `console` rather
* than the original one.
*
* @type {GroupReporter}
*/
const consoleReporter = {
warn(...args) {
globalThis.console.warn(...args);
},
error(...args) {
globalThis.console.error(...args);
},
...(globalThis.console?.groupCollapsed
? {
groupCollapsed(...args) {
globalThis.console.groupCollapsed(...args);
},
}
: undefined),
...(globalThis.console?.groupEnd
? {
groupEnd() {
globalThis.console.groupEnd();
},
}
: undefined),
};
/* eslint-enable @endo/no-polymorphic-call */
/**
* Creates a suitable reporter for internal errors and warnings out of the
* Node.js console.error to ensure all messages to go stderr, including the
* Node.js console.error to ensure all messages go to stderr, including the
* group label.

@@ -55,14 +87,18 @@ * Accounts for the extra space introduced by console.error as a delimiter

}
if (
reporting === 'console' ||
globalThis.window === globalThis ||
globalThis.importScripts !== undefined
) {
return console;
}
if (globalThis.console !== undefined) {
if (
reporting === 'console' || // asks for console explicitly
globalThis.window === globalThis || // likely on browser
globalThis.importScripts !== undefined // likely on worker
) {
// reporter just delegates directly to the current console
return consoleReporter;
}
assert(reporting === 'platform');
// On Node.js, we send all feedback to stderr, regardless of purported level.
const console = globalThis.console;
const error = functionBind(console.error, console);
return makeReportPrinter(error);
// This uses `consoleReporter.error` instead of `console.error` because we
// want the constructed reporter to use the `console.error` of the current
// `console`, not the `console` that was installed when the reporter
// was created.
return makeReportPrinter(consoleReporter.error);
}

@@ -69,0 +105,0 @@ if (globalThis.print !== undefined) {

@@ -7,11 +7,14 @@ import {

hasOwn,
regexpTest,
regexpExec,
Set,
setHas,
} from './commons.js';
/**
* keywords
* In JavaScript you cannot use these reserved words as variables.
* See 11.6.1 Identifier Names
* reservedNames
* In JavaScript you cannot use reserved words as variable names (except for
* "eval", which is specially reserved to prevent shadowing).
* https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#sec-identifier-names
*/
const keywords = [
const reservedNames = new Set([
// 11.6.2.1 Keywords

@@ -77,4 +80,7 @@ 'await',

'arguments',
];
// Reserved by us
'eval',
]);
/**

@@ -94,20 +100,8 @@ * identifierPattern

* isValidIdentifierName()
* What variable names might it bring into scope? These include all
* property names which can be variable names, including the names
* of inherited properties. It excludes symbols and names which are
* keywords. We drop symbols safely. Currently, this shim refuses
* service if any of the names are keywords or keyword-like. This is
* safe and only prevent performance optimization.
* Is a value allowed as an arbitrary identifier name?
*
* @param {string} name
*/
export const isValidIdentifierName = name => {
// Ensure we have a valid identifier. We use regexpTest rather than
// /../.test() to guard against the case where RegExp has been poisoned.
return (
name !== 'eval' &&
!arrayIncludes(keywords, name) &&
regexpTest(identifierPattern, name)
);
};
export const isValidIdentifierName = name =>
!setHas(reservedNames, name) && !!regexpExec(identifierPattern, name);

@@ -143,3 +137,3 @@ /*

* getScopeConstants()
* What variable names might it bring into scope? These include all
* What variable names might be brought into scope? These include all
* property names which can be variable names, including the names

@@ -149,3 +143,3 @@ * of inherited properties. It excludes symbols and names which are

* service if any of the names are keywords or keyword-like. This is
* safe and only prevent performance optimization.
* safe and only affects performance optimization.
*

@@ -152,0 +146,0 @@ * @param {object} globalObject

@@ -6,3 +6,4 @@ // @ts-check

SyntaxError,
stringReplace,
regexpReplace,
sealRegexp,
stringSearch,

@@ -39,3 +40,5 @@ stringSlice,

const htmlCommentPattern = new FERAL_REG_EXP(`(?:${'<'}!--|--${'>'})`, 'g');
const htmlCommentPattern = sealRegexp(
new FERAL_REG_EXP(`(?:${'<'}!--|--${'>'})`, 'g'),
);

@@ -105,3 +108,3 @@ /**

const replaceFn = match => (match[0] === '<' ? '< ! --' : '-- >');
return stringReplace(src, htmlCommentPattern, replaceFn);
return regexpReplace(htmlCommentPattern, src, replaceFn);
};

@@ -111,5 +114,4 @@

const importPattern = new FERAL_REG_EXP(
'(^|[^.]|\\.\\.\\.)\\bimport(\\s*(?:\\(|/[/*]))',
'g',
const importPattern = sealRegexp(
new FERAL_REG_EXP('(^|[^.]|\\.\\.\\.)\\bimport(\\s*(?:\\(|/[/*]))', 'g'),
);

@@ -177,3 +179,3 @@

const replaceFn = (_, p1, p2) => `${p1}__import__${p2}`;
return stringReplace(src, importPattern, replaceFn);
return regexpReplace(importPattern, src, replaceFn);
};

@@ -180,0 +182,0 @@

+110
-141

@@ -6,2 +6,4 @@ /**

import '@endo/immutable-arraybuffer/shim.js';
/* eslint-disable no-restricted-globals, vars-on-top, no-var */

@@ -191,9 +193,19 @@

interface Stringable {
toString(): string;
}
/** @deprecated */
type StringPayload = Stringable;
/**
* A call to the `details` template literal makes and returns a fresh details
* token, which is a frozen empty object associated with the arguments of that
* `details` template literal expression.
* A call to the {@link details} template literal makes and returns a fresh
* DetailsToken, which is a frozen empty object associated with the arguments of
* that expression.
*/
export type DetailsToken = Record<any, never>;
/** Either a plain string, or made by the `details` template literal tag. */
/**
* A plain string, or a {@link DetailsToken} from the {@link details} template
* literal tag.
*/
export type Details = string | DetailsToken;

@@ -205,3 +217,3 @@

* the constructor. Rather, the `errorName` determines how this error is
* identified in the causal console log's output.
* identified in an associated console's output.
*/

@@ -286,6 +298,2 @@ errorName?: string;

interface StringablePayload {
toString(): string;
}
/**

@@ -314,17 +322,15 @@ * TypeScript does not treat `AggregateErrorConstructor` as a subtype of

/**
* Makes and returns an `assert` function object that shares the bookkeeping
* state defined by this module with other `assert` function objects made by
* `makeAssert`. This state is per-module-instance and is exposed by the
* `loggedErrorHandler` above. We refer to `assert` as a "function object"
* because it can be called directly as a function, but also has methods that
* can be called.
* Makes and returns an `assert` function object that shares the
* per-module-instance bookkeeping state defined by this module with other
* `assert` function objects made by `makeAssert`. We refer to `assert` as a
* "function object" because it can be called directly as a function, but also
* has callable methods of its own.
*
* If `optRaise` is provided, the returned `assert` function object will call
* `optRaise(reason)` before throwing the error. This enables `optRaise` to
* engage in even more violent termination behavior, like terminating the vat,
* that prevents execution from reaching the following throw. However, if
* `optRaise` returns normally, which would be unusual, the throw following
* `optRaise(reason)` would still happen.
* If `raise` is provided, the returned `assert` function object will call
* `raise(reason)` before throwing an assertion-failure error. This enables
* `raise` to engage in even more violent termination behavior, like process
* termination, that prevents execution from reaching the following throw.
* However, if `raise(reason)` returns normally, which would be unusual, that
* throw still happens.
*/
// Behold: recursion.
// eslint-disable-next-line no-use-before-define

@@ -334,5 +340,5 @@ export type MakeAssert = (raise?: Raise, unredacted?: boolean) => Assert;

export type BaseAssert = (
/** The truthy/falsy value we're testing */
flag: any,
/** The details of what was asserted */
/** The condition whose truthiness is being asserted */
condition: any,
/** The details associated with assertion failure (falsy condition) */
details?: Details,

@@ -342,3 +348,3 @@ /** An optional alternate error constructor to use */

options?: AssertMakeErrorOptions,
) => asserts flag;
) => asserts condition;

@@ -349,5 +355,3 @@ export interface AssertionFunctions extends BaseAssert {

/**
* The `assert.equal` method
*
* Assert that two values must be `Object.is`.
* Assert that two values are the same as observed by `Object.is`.
*/

@@ -359,3 +363,3 @@ equal<T>(

expected: T,
/** The details of what was asserted */
/** The details associated with assertion failure (`Object.is` returning false) */
details?: Details,

@@ -368,8 +372,5 @@ /** An optional alternate error constructor to use */

/**
* The `assert.string` method.
*
* Assert that a value is a primitive string.
* `assert.string(v)` is equivalent to `assert.typeof(v, 'string')`. We
* special case this one because it is the most frequently used.
*
* Assert an expected typeof result.
*/

@@ -383,10 +384,5 @@ string(

/**
* The `assert.fail` method.
*
* Fail an assertion, recording full details to the console and
* raising an exception with a message in which `details` substitution values
* have been redacted.
*
* The optional `optDetails` can be a string for backwards compatibility
* with the nodejs assertion library.
* Fail an assertion, raising an exception with a `message` in which unquoted
* `details` substitution values may have been redacted into `typeof` types
* but are still available for logging to an associated console.
*/

@@ -404,7 +400,7 @@ fail(

/**
* Aka the `makeError` function as imported from `@endo/errors`
*
* Recording unredacted details for the console.
* Create an error with a `message` in which unquoted {@link details}
* substitution values may have been redacted into lossy `typeof` output but
* are still available for logging to an associated console.
*/
error(
makeError(
/** The details of what was asserted */

@@ -418,7 +414,4 @@ details?: Details,

/**
* Aka the `annotateError` function as imported from `@endo/errors`
*
* Annotate an error with details, potentially to be used by an
* augmented console such as the causal console of `console.js`, to
* provide extra information associated with logged errors.
* Associate `details` with `error`, potentially to be logged by an associated
* console for providing extra information about the error.
*/

@@ -428,16 +421,13 @@ note(error: Error, details: Details): void;

/**
* Use the `details` function as a template literal tag to create
* informative error messages. The assertion functions take such messages
* as optional arguments:
* ```js
* assert(sky.isBlue(), details`${sky.color} should be "blue"`);
* ```
* or following the normal convention to locally rename `details` to `X`
* and `quote` to `q` like `const { details: X, quote: q } = assert;`:
* ```js
* assert(sky.isBlue(), X`${sky.color} should be "blue"`);
* ```
* Use as a template literal tag to create an opaque {@link DetailsToken} for
* use with other assertion functions that might redact unquoted substitution
* values (i.e., those that are not output from the {@link quote} function)
* into lossy `typeof` output but still preserve them for logging to an
* associated console.
*
* The normal convention is to locally rename `details` to `X` like
* `const { details: X, quote: q, Fail } = assert;`.
* However, note that in most cases it is preferable to instead use the `Fail`
* template literal tag (which has the same input signature as `details`
* but automatically creates and throws an error):
* template literal tag (which has the same input signature but automatically
* creates and throws an error):
* ```js

@@ -447,12 +437,2 @@ * sky.isBlue() || Fail`${sky.color} should be "blue"`;

*
* The details template tag returns a `DetailsToken` object that can print
* itself with the formatted message in two ways.
* It will report full details to the console, but
* mask embedded substitution values with their typeof information in the thrown error
* to prevent revealing secrets up the exceptional path. In the example
* above, the thrown error may reveal only that `sky.color` is a string,
* whereas the same diagnostic printed to the console reveals that the
* sky was green. This masking can be disabled for an individual substitution value
* using `quote`.
*
* The `raw` property of an input template array is ignored, so a simple

@@ -467,33 +447,28 @@ * array of strings may be provided directly.

/**
* Use the `Fail` function as a template literal tag to efficiently
* create and throw a `details`-style error only when a condition is not satisfied.
* Use as a template literal tag to create and throw an error in whose
* `message` unquoted substitution values (i.e., those that are not output
* from the {@link quote} function) may have been redacted into lossy `typeof`
* output but are still available for logging to an associated console.
*
* For example, using the normal convention to locally rename properties like
* `const { quote: q, Fail } = assert;`:
* ```js
* condition || Fail`...complaint...`;
* sky.isBlue() || Fail`${sky.color} should be "blue"`;
* ```
* This avoids the overhead of creating usually-unnecessary errors like
*
* This `||` pattern saves the cost of creating {@link DetailsToken} and/or
* error instances when the asserted condition holds, but can weaken
* TypeScript static reasoning due to
* https://github.com/microsoft/TypeScript/issues/51426 . Where this is a
* problem, instead express the assertion as
* ```js
* assert(condition, details`...complaint...`);
* if (!sky.isBlue()) {
* // This `throw` does not affect runtime behavior since `Fail` throws, but
* // might be needed to improve static analysis.
* throw Fail`${sky.color} should be "blue"`;
* }
* ```
* while improving readability over alternatives like
* ```js
* condition || assert.fail(details`...complaint...`);
* ```
*
* However, due to current weakness in TypeScript, static reasoning
* is less powerful with the `||` patterns than with an `assert` call.
* Until/unless https://github.com/microsoft/TypeScript/issues/51426 is fixed,
* for `||`-style assertions where this loss of static reasoning is a problem,
* instead express the assertion as
* ```js
* if (!condition) {
* Fail`...complaint...`;
* }
* ```
* or, if needed,
* ```js
* if (!condition) {
* // `throw` is noop since `Fail` throws, but it improves static analysis
* throw Fail`...complaint...`;
* }
* ```
* The `raw` property of an input template array is ignored, so a simple
* array of strings may be provided directly.
*/

@@ -503,31 +478,35 @@ Fail(template: TemplateStringsArray | string[], ...args: any): never;

/**
* To "declassify" and quote a substitution value used in a
* ``` details`...` ``` template literal, enclose that substitution expression
* in a call to `quote`. This makes the value appear quoted
* (as if with `JSON.stringify`) in the message of the thrown error. The
* payload itself is still passed unquoted to the console as it would be
* without `quote`.
* Wrap a value such that its use as a substitution value in a template
* literal tagged with {@link details} or {@link Fail} will result in it
* appearing quoted (in a way similar to but more general than
* `JSON.stringify`) rather than redacted in the `message` of errors based on
* the resulting {@link DetailsToken}.
*
* For example, the following will reveal the expected sky color, but not the
* actual incorrect sky color, in the thrown error's message:
* This does not affect representation in output of an associated console,
* which still logs the value as it would without `quote`, but *does* reveal
* it to functions in the propagation path of such errors.
*
* For example, the following will reveal the expected value in the thrown
* error's `message`, but only the _type_ of the actual incorrect value (using
* the normal convention to locally rename properties like
* `const { quote: q, Fail } = assert;`):
* ```js
* sky.color === expectedColor || Fail`${sky.color} should be ${quote(expectedColor)}`;
* actual === expected || Fail`${actual} should be ${q(expected)}`;
* ```
*
* The normal convention is to locally rename `details` to `X` and `quote` to `q`
* like `const { details: X, quote: q } = assert;`, so the above example would then be
* ```js
* sky.color === expectedColor || Fail`${sky.color} should be ${q(expectedColor)}`;
* ```
* The optional `space` parameter matches that of `JSON.stringify`, and is
* used to request insertion of non-semantic line feeds, indentation, and
* separating spaces in the output for improving readability of objects and
* arrays.
*/
quote(
/** What to declassify */
payload: any,
spaces?: string | number,
): /** The declassified and quoted payload */ StringablePayload;
quote(value: any, space?: string | number): Stringable;
/**
* Embed a string directly into error details without wrapping punctuation.
* Wrap a string such that its use as a substitution value in a template
* literal tagged with {@link details} or {@link Fail} will be treated
* literally rather than being quoted or redacted.
*
* To avoid injection attacks that exploit quoting confusion, this must NEVER
* be used with data that is possibly attacker-controlled.
*
* As a further safeguard, we fall back to quoting any input that is not a

@@ -542,8 +521,3 @@ * string of sufficiently word-like parts separated by isolated spaces (rather

*/
bare(
/** What to declassify */
payload: any,
spaces?: string | number,
): /** The declassified payload without quotes (beware confusion hazard) */
StringablePayload;
bare(text: string, spaces?: string | number): Stringable;
}

@@ -553,21 +527,16 @@

makeAssert: MakeAssert;
error: AssertionUtilities['makeError'];
}
/**
* assert that expr is truthy, with an optional details to describe
* the assertion. It is a tagged template literal like
* ```js
* assert(expr, details`....`);`
* ```
* Assert that `condition` is truthy, with optional details associated with
* assertion failure (falsy condition).
*
* The literal portions of the template are assumed non-sensitive, as
* are the `typeof` types of the substitution values. These are
* assembled into the thrown error message. The actual contents of the
* substitution values are assumed sensitive, to be revealed to
* the console only. We assume only the virtual platform's owner can read
* what is written to the console, where the owner is in a privileged
* position over computation running on that platform.
*
* The optional `optDetails` can be a string for backwards compatibility
* with the nodejs assertion library.
* The literal portions of the template used to make a {@link DetailsToken} are
* assumed non-sensitive, as are the `typeof` output for substitution values.
* These are assembled into the error `message`. The actual contents of the
* substitution values are assumed sensitive and usually redacted, to be
* revealed only to an associated console. We assume only the virtual platform's
* owner can read what is written to the console, where the owner is in a
* privileged position over computation running on that platform.
*/

@@ -574,0 +543,0 @@ export type Assert = AssertionFunctions &

# Logging Errors
Summary
* Writing defensive programs under SES requires carefully considering what an error reveals to code positioned to catch those errors up the call chain.
* To that end, SES introduces an `assert` global with functions that add to errors annotations that will be hidden from callers. SES also tames the `Error` constructor to hide the `stack` to parent callers when possible (currently: v8, SpiderMonkey, XS).
* SES tames the global `console` and grants it the ability to reveal error annotations and stacks to the actual console.
* Both `assert` and `console` are powerful globals that SES does not implicitly carry into child compartments. When creating a child compartment, add `assert` to the compartment’s globals. Either add `console` too, or add a wrapper that annotates the console with a topic.
* SES hides annotations and stack traces by default. To reveal them, SES uses mechanisms like `process.on("uncaughtException")` in Node.js to catch the error and log it back to the `console` tamed by `lockdown`.
We refer to the enhanced `console`, installed by default by the ses shim, as the *causal console*, because the annotations it reveals are often used to show causality information. For example, with the [`TRACK_TURNS=enabled`](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#track_turns) and [`DEBUG=track-turns`](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#debug) environment options set
```sh
# in bash syntax
export DEBUG=track-turns
export TRACK_TURNS=enabled
```
the @endo/eventual-send package will use annotations to show where previous `E` operations (either eventual sends or `E.when`) in previous turns *locally in the same vat* caused the turn with the current error. This is sometimes called "deep asynchronous stacks".
* In the scope of the Agoric software ecosystem, this architecture will allow us to eventually introduce a more powerful distributed causal `console` that can meaningfully capture stack traces for a distributed debugger, based on the design of [Causeway](https://github.com/Agoric/agoric-sdk/issues/1318#issuecomment-662127549).
## Goals, non-goals, and partial goals
Aside from IDE-based debuggers, the normal JavaScript developer debugging experience rests on the interplay of three widespread building blocks:
* Thrown errors which carry stack traces and explanatory messages.
* An `assert` convenience library for turning violated conditions into diagnostic errors.
* A built-in `console` for producing diagnostic logging information for the developer to look at, or even, in the browser, interact with.
We are building a distributed secure JavaScript system running on both blockchain and non-chain platforms. Blockchains require determinism, so all validators reproduce the same computation. Despite these constraints&mdash;secure, distributed, deterministic&mdash;we wish to provide the JavaScript developer with a debugging experience at least on par with their current `console` based expectations, and as familiar as possible, so they can hit the ground running.
The logging systems described at [survey of logging frameworks - Issue #1318](https://github.com/Agoric/agoric-sdk/issues/1318) mostly have very different goals: to produce symbolic records to be post-processed into useful diagnostic information. We still need such a logging system in addition to the system described here. The `console` directly produces information for the developer to look at and possibly interact with. Producing this experience severely constrains the additional symbolic information we can include to aid post-processing, if this additional information would add distracting visual noise.
## Configuration variations
This directory is a system of three related abstractions:
* `Error` Errors carry hidden diagnostic information.
* `assert` Assertions cause and annotate errors.
* `console` Consoles show an enhanced view of logged errors.
This system must behave well in a variety of configurations:
* After `lockdown` is imported but before repairs.
* `assert` added to global scope of start compartment.
* After repair or `lockdown()` in the start compartment
* All combinations of relevant `lockdown` taming options (`errorTaming` and `consoleTaming`).
* In *created compartments*, i.e., non-start compartments created after `lockdown()`. In our recommended practice, typically
* All created compartments implicitly share the same safe `Error` constructor.
* All compartments explicitly share the same `assert`.
* Each compartment explicitly has its own `filteringConsole` in a tree, enabling filtering by compartment (topic-like) and severity level (`debug`, `log`, `info`, `warn`, and `error`).
Of these configurations, we are primarily concerned with the post-lockdown, default-safe-taming options, created compartment, recommended endowment pattern. This one must have strong simple security and determinism properties. Variations must differ in understandable ways.
## Hiding and Revealing Local Diagnostic Information
A pervasive concern is hiding diagnostic information&mdash;both for confidentiality and for deterministic replay. Code that obtains access to an error object, for example by catching it, should not have access this hidden diagnostic information. However, the console system produces logging output, typically for human developers to look at to help track down problems. The `console` interface should ideally be a write-only interface when considered by itself. We consider the viewer of log information produced by the console the way we consider the operator of a debug interface of an IDE. We view both as at a meta-level outside the computational system producing the diagnostic information. This system has several categories of hidden diagnostic information:
* **Error stacks**. JavaScript errors capture the callstack at the time an error was created. In normal unsafe JavaScript this is available from error objects themselves via `error.stack`. This general availability violates caller encapsulation, threatening security. The contents of these callstacks are unspecified, non-deterministic, and differ from engine to engine, threatening determinism.
* **Detailed error message data**. JavaScript errors carry a `message` string determined when the error is created, and used to convey further diagnostic information to human developers. However, data dependent values that a human developer may find useful may also reveal information that should not be accessible to code from the error object. Our `assert` provides a `details` template literal tag for creating informative error messages made visible on the logging output, but partially censored in the `message` string carried by error objects.
* **Error annotations**. An error can indicate problems at multiple levels of abstraction. An error thrown by low level code may be diagnostic of a problem explained in low level concepts. A higher level caller may wish to add an explanation in terms of its own higher level concepts. However, if it mearly catches the low level error and then throws a high level error, the low level information is lost. Instead, the catch clause can annotate the low level error with high level diagnostic information and then rethrow the low level error.
All the above forms of diagnostic information&mdash;error stacks, detailed error messages data, error annotations&mdash;are kept in side tables, hidden from normal code, but used by the console system to display a more informative error log. All these tables are per-realm rather than per-compartment, so an error thrown by code in compartment A, annotated by code in compartment B, and logged by code in compartment C will be displayed or not only according to the compartment C console's filters. The compartment of origin of the other information is irrelevant. In support of this, there is normally only one global safe `Error` constructor shared by all created compartments, one global `assert` shared by all compartments, and one *root* console, which is the console of the start compartment. The `Error` constructor system shares the stack trace side table with the root console. The `assert` shares the detailed message data and annotation side tables with the root console.
To minimize visual noise, none of the following directly produces any logging output: throwing errors, assertion failure, or error annotation. They record information silently, only to be displayed if reached from an explicit `console` logging action. These logging actions are the root of a graph of this additional accumulated information. The logging action arguments include errors. These errors have both a detailed message that include errors, and detailed message annotations that include errors. Those errors likewise... The console logs this extra information once, with a unique tag per error. Any further occurrences of that error output that unique tag, from which to look up such previous log output.
Before repair or `lockdown`, we assume there is some prior "system" console bound to the global `console` in the start compartment. `{ consoleTaming: 'unsafe' }` leaves this unsafe system console in place. This system console is completely ignorant of the extra information in these side tables, which is therefore never seen. This includes the hidden data in the detailed messages. Instead, the system console shows the abbreviated `message` text produced by the `details` template literal that omits censored data. When combined with the default `{ errorTaming: 'safe' }`, the system console may not see error stack information. The `{ errorTaming: 'unsafe' }` or `{ errorTaming: 'unsafe-details' }` setting does not remove error stacks from error objects, in which case the system console will find it as usual.
The default `{ consoleTaming: 'safe' }` setting replaces the system console with a root console that does use all these side tables to generate a more informative log. This root console wraps the prior system console. This root console outputs its log information only by invoking this wrapped system console, which therefore determines how this log information is made available to the external world. To support determinism, we will also need to support a no-op console setting, as explained at [deterministic handling of adversarial code calling console.log with a Proxy #1852](https://github.com/Agoric/agoric-sdk/issues/1852) and [Need no-op console setting for determinism #487](https://github.com/Agoric/SES-shim/issues/487).
SES considers both `assert` and `console` to be powerful objects, appearing initially in the start compartment, and not permitted for implicit propagation to created compartments. Rather, we recommend an endowment pattern where the global `assert` is passed forward as-is, but only filtered forms of the `console` are. As compartments create each other in a tree, they create a corresponding filtering tree of consoles. Information sent to any compartment's console is then sent up the filtering tree. Only information that survives all the filters in its path arrive at the root console, producing log output. The others have no effect. Given the expected pattern of a compartment per package, the per-compartment console filter is effectly a topic filter, treating the package identity as a topic. We plan to also support coordinated stack-frame filters, as explained at [Need source-prefix-based stackframe filter #488](https://github.com/Agoric/SES-shim/issues/488).
For security and determinism, we normally reason from the *in-band frame of reference* where the console logging output does not exist, is not an effect, and `console` operations are write-only. Within this frame of reference, the `assert` and `console` powers are not very powerful. They are almost as safe as the permitted, powerless, shared primordials, which is why we're willing to recommend this endowment pattern be habitual.
## Hiding and Revealing Distributed Diagnostic Information
This section explains our *plans* to build a distributed logging experience on top of this system. Also tracked at [Support stack-tracking serialization of error objects #1863](https://github.com/Agoric/agoric-sdk/issues/1863).
Only a local system will have a meaningful notion of "the developer" that should see all hidden diagnostic information. Our overall system is a decentralized fabric of multiple mutually suspicious platforms, including both public and private chains, and public and private non-chains. Alice running private chain A may or may not be willing to release A's logs to Bob, running public chain B, even if it would help Bob diagnose a problem. Our system must support Bob in both scenarios. When Bob can get all the relevant logs, we wish his debugging experience to approximate as close as possible the pleasure of the local debugging experience. When Bob can only get some logs, his ability to debug should degrade gracefully.
Our comm system sends errors by copy. At the level of abstraction of the distributed computation, an error serialized and sent by Alice is the "same" as the error as received and unserialized by Bob. At the JavaScript level of abstraction, they are of course distinct objects. Alice's system holds all this extra hidden information about the error she's sending, that her console uses to output useful diagnostic information. Alice's comm system therefore cannot simply serialize and send this information to untrusted Bob. Instead, Alice's comm system should generate identifying information which allegedly identifies this error. Alice's comm system should include this identifier in the serialization of the error, and it should arrange to locally log the association of this error with this identifier. Bob's comm system, on unserializing the error, should annotate this new error with this identifying information from the unserialization of this error.
If Bob's computation then causes that error to be logged, its local stack trace will uselessly identify the unserialializer as the code that created the error. But the annotation should inform Bob that he should go ask Alice for the logs containing the identified error. With more tooling to make such arrangements more automatic and immediate, the relevant portions of Alice's log could be made to appear to Bob as-if they are available in his own local diagnostic information.
However, the above description violates one of our constraints: The automatic logging of a sent error to Alice's log is noisy, especially if neither that error nor its remote copy would ever otherwise be logged. Ideally, this would instead be handled by that [other kind of logging system](https://github.com/Agoric/agoric-sdk/issues/1318) that produces symbolic output to be post-processed into useful diagnostic information. However, this particular special case is uniquely urgent and might not wait for us to build that other kind of logging system. As one possible mechanism, the comm system could maintain a bounded in-memory table of sent errors. If Bob's request arrive while the identified error is still in Alice's table, and Alice wishes to reveal this info to Bob, Alice can log it then.
## Hiding and Revealing Asynchronous Diagnostic Information
This section explains our *plans* to build a logging experience on top of this system that supports local and distributed asynchrony. Also tracked at [Support deep stacks for local asynchronous log-based debugging #1862](https://github.com/Agoric/agoric-sdk/issues/1862) and [Support distributed deep stacks for log-based debugging #1864](https://github.com/Agoric/agoric-sdk/issues/1864).
JavaScript itself is not a dustributed language, but it is a highly asynchronous language. Our distributed computational model&mdash;communicating event loops&mdash;pushes much of our code into making heavy use of this asynchrony. For such code, individual synchronous call stacks are often short and uninformative. [Causeway](https://github.com/Agoric/agoric-sdk/issues/1318#issuecomment-662127549) shows that the asynchronous and distributed analog of synchronous stack traces is a directed acyclic graph of prior causal events, each with their local synchronous stack at their moment of causation. To capture this well requires instrumenting the promise system in ways impossible for user code. However, the most important causal paths are
* the [eventual-send](https://github.com/tc39/proposal-eventual-send) operations by [handled promise](../../../eventual-send/README.md), whether expressed by `E()` or `~.`.
* the `.then` operation. However, we replace the builtin `Promise.prototype.then` at our peril. Many built in operations implicitly invoke the original binding of `Promise.prototype.then` in ways we cannot override. However, our same eventual-send package already provides a safer alternative `E.when` operation.
Restricting the instrumentation to these two operations gives predictability and preserves platform independence, but at the loss of some useful diagnostic information. This loss may encourage programmers to shift from their current habits to eventual-send and `E.when`, which would be a good thing anyway.
(Note that some IDE debug experiences now include deep stacks over `await` boundaries. However, the engine provides to access to this mechanism from JavaScript. Without it, these stacks are impossible to capture without an invasive code transform.)
Ideally, the diagnostic information produced by such instrumentation should be sent to [other kind of logging system](https://github.com/Agoric/agoric-sdk/issues/1318), for post-processing by other tools. But it is at least possible to encode it in the system described here. The instrumentation would add enough overhead to eventual-send and `E.when` that it should not be the default setting. When the instrumentation is on, each `E()` or `E.when` operation would create a hidden error, to be associated with the turn it causes. When that turn does such an action, the hidden error it similarly creates would be annotated with the hidden error from the action that created this turn. Looking forward, this records a causal tree of events. Looking backward, it creates a linear "deep stack" of events&mdash;a sequence of shallow stacks. All this extra bookkeeping remains silent until an error is logged. Once an error is logged, its deep stack is included in the recursive logging of its annotations.
(However, this pattern of use will accumulate deep annotation trees&mdash;too deep to keep in memory. Instead we would need to bound the number of annotations we remember, which would require a different data structure.)
## Unreal logging
For deterministically replayable computation, we could support the full debugging experience even if the "real" computation never logs anything. Such a no-op logging system would not need any side tables, and so has no problem with side table memory pressure. Such a no-op logging system never examines logged objects, and so does not create a communications channel. Instead, all logging only happens offline, under instrumented deterministic replay, and only for computation containing a mystery to be diagnosed. Under this scenario, even expensive instrumentation may be very affordable. Under this scenario, if Alice gives Bob enough information to deterministically replay the relevant chain A computation, she's effectively given Bob all the logging information he could ever want. Under this scenario, no mechanism is needed to exempt logging output for on-chain determinism rules, since there would be no on-chain logging output.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display