@telorun/kernel 0.83.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -0
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +8 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts +22 -0
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +75 -3
- package/dist/evaluation-context.js.map +1 -1
- package/dist/instance-sensitive-paths.d.ts +61 -0
- package/dist/instance-sensitive-paths.d.ts.map +1 -0
- package/dist/instance-sensitive-paths.js +116 -0
- package/dist/instance-sensitive-paths.js.map +1 -0
- package/dist/invocation-contract-binding.d.ts +3 -0
- package/dist/invocation-contract-binding.d.ts.map +1 -1
- package/dist/invocation-contract-binding.js +9 -1
- package/dist/invocation-contract-binding.js.map +1 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +25 -0
- package/dist/kernel.js.map +1 -1
- package/package.json +2 -2
- package/src/controllers/resource-definition/resource-definition-controller.ts +14 -0
- package/src/evaluation-context.ts +80 -5
- package/src/instance-sensitive-paths.ts +123 -0
- package/src/invocation-contract-binding.ts +12 -0
- package/src/kernel.ts +29 -1
|
@@ -30,6 +30,11 @@ import {
|
|
|
30
30
|
import { RuntimeError } from "@telorun/sdk";
|
|
31
31
|
import { evalPathCovers } from "@telorun/analyzer";
|
|
32
32
|
import { effectOwnerOf, executeReturnedChain } from "./effect-scope.js";
|
|
33
|
+
import {
|
|
34
|
+
REDACTED,
|
|
35
|
+
redactSensitive,
|
|
36
|
+
sensitivePathsOfInstance,
|
|
37
|
+
} from "./instance-sensitive-paths.js";
|
|
33
38
|
import {
|
|
34
39
|
classifyInitFailures,
|
|
35
40
|
isDeferral,
|
|
@@ -142,6 +147,12 @@ function localDependencyNames(refs: ResourceRef[]): string[] {
|
|
|
142
147
|
* whole-value match is redacted. */
|
|
143
148
|
const MIN_SUBSTRING_SCRUB_LEN = 5;
|
|
144
149
|
|
|
150
|
+
/** A live resource instance, as opposed to the raw config value a slot held
|
|
151
|
+
* before Phase-5 injection — the same duck-test the reference-resolving
|
|
152
|
+
* controllers use. */
|
|
153
|
+
const hasSnapshotMethod = (value: unknown): boolean =>
|
|
154
|
+
!!value && typeof (value as { snapshot?: unknown }).snapshot === "function";
|
|
155
|
+
|
|
145
156
|
export function buildResolvedProperties(
|
|
146
157
|
resource: ResourceManifest,
|
|
147
158
|
secretValues: Set<string>,
|
|
@@ -752,6 +763,43 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
752
763
|
return def?.status;
|
|
753
764
|
}
|
|
754
765
|
|
|
766
|
+
/**
|
|
767
|
+
* The configured values a merge-form inheriting child publishes over the
|
|
768
|
+
* parent instance's reading, or undefined when the kind declares none.
|
|
769
|
+
*
|
|
770
|
+
* Such a child IS the parent instance — the inherited controller returns it
|
|
771
|
+
* verbatim — so `snapshot()` is the parent's and a field the parent never
|
|
772
|
+
* declared would be readable from nowhere. The field NAMES come from
|
|
773
|
+
* `publishedOwnFields`, stamped onto the definition at registration in the
|
|
774
|
+
* scope that declared the `extends` alias; resolving the chain here would
|
|
775
|
+
* resolve it in the READING module's scope, which may have no alias for the
|
|
776
|
+
* parent's library. Done here rather than by rebinding the instance's
|
|
777
|
+
* `snapshot()` because this is definition-derived data joined into a reading —
|
|
778
|
+
* exactly what `statusSchemaOf` already does — and a second kernel cannot
|
|
779
|
+
* rebind a trait method.
|
|
780
|
+
*
|
|
781
|
+
* Two values are withheld rather than published, both conservatively: a
|
|
782
|
+
* runtime-eval field is still a `CompiledValue` at this point (only compile
|
|
783
|
+
* paths are expanded at create), and a live instance is a collaborator rather
|
|
784
|
+
* than a reading. Publishing either would put a wrong-typed value in the CEL
|
|
785
|
+
* scope, which is the failure this overlay exists to remove.
|
|
786
|
+
*/
|
|
787
|
+
private ownFieldOverlay(
|
|
788
|
+
kind: string,
|
|
789
|
+
resource: Record<string, unknown>,
|
|
790
|
+
): Record<string, unknown> | undefined {
|
|
791
|
+
const def = this.getDefinition?.(this.resolveKindSafe(kind)) ?? this.getDefinition?.(kind);
|
|
792
|
+
const fields = (def as { publishedOwnFields?: string[] } | undefined)?.publishedOwnFields;
|
|
793
|
+
if (!fields?.length) return undefined;
|
|
794
|
+
let overlay: Record<string, unknown> | undefined;
|
|
795
|
+
for (const field of fields) {
|
|
796
|
+
const value = resource[field];
|
|
797
|
+
if (value === undefined || isCompiledValue(value) || hasSnapshotMethod(value)) continue;
|
|
798
|
+
(overlay ??= {})[field] = value;
|
|
799
|
+
}
|
|
800
|
+
return overlay;
|
|
801
|
+
}
|
|
802
|
+
|
|
755
803
|
/**
|
|
756
804
|
* Re-read a resource's `snapshot()` and republish it, joined with whatever
|
|
757
805
|
* observed state the resource has reported. The single publication path — the
|
|
@@ -760,11 +808,14 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
760
808
|
*/
|
|
761
809
|
async publishSnapshot(name: string): Promise<void> {
|
|
762
810
|
const entry = this.resourceInstances.get(name) ?? this.createdInstances.get(name);
|
|
763
|
-
if (!entry
|
|
764
|
-
const snap = (await Promise.resolve(entry.instance.snapshot())) as
|
|
765
|
-
| Record<string, unknown>
|
|
766
|
-
| undefined;
|
|
811
|
+
if (!entry) return;
|
|
767
812
|
const kind = entry.resource.kind as string;
|
|
813
|
+
const own = this.ownFieldOverlay(kind, entry.resource);
|
|
814
|
+
if (!entry.instance.snapshot && !own) return;
|
|
815
|
+
const snapshot = entry.instance.snapshot
|
|
816
|
+
? ((await Promise.resolve(entry.instance.snapshot())) as Record<string, unknown> | undefined)
|
|
817
|
+
: undefined;
|
|
818
|
+
const snap = own ? { ...(snapshot ?? {}), ...own } : snapshot;
|
|
768
819
|
const props = buildPublishedProps(snap, {
|
|
769
820
|
kind,
|
|
770
821
|
name,
|
|
@@ -1606,6 +1657,30 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
1606
1657
|
: undefined;
|
|
1607
1658
|
// Capture the root CEL scope once, on the trace's root span's terminal event.
|
|
1608
1659
|
const rootScope = tracing && parentInvocationId === undefined ? this.traceRootScope() : undefined;
|
|
1660
|
+
// What a payload may say about this call. A credential is a dispatched
|
|
1661
|
+
// invocable, so its material is an invoke OUTPUT — and inputs and outputs
|
|
1662
|
+
// ride the debug wire on every call under `--inspect`, i.e. every watch
|
|
1663
|
+
// session, which the substring scrubbing does not reach (one call site, the
|
|
1664
|
+
// resource-Created event's properties). The kind that owns the contract
|
|
1665
|
+
// marks the field; nothing here knows which kind that is.
|
|
1666
|
+
const hide = (detail: Record<string, unknown>): Record<string, unknown> => {
|
|
1667
|
+
let out = detail;
|
|
1668
|
+
for (const direction of ["inputType", "outputType"] as const) {
|
|
1669
|
+
const key = direction === "inputType" ? "inputs" : "outputs";
|
|
1670
|
+
if (!(key in out)) continue;
|
|
1671
|
+
const paths = sensitivePathsOfInstance(instance, direction);
|
|
1672
|
+
// Unknown, because the contract would not resolve — withhold the whole
|
|
1673
|
+
// value rather than guess. The dispatch is about to raise that same
|
|
1674
|
+
// failure with its own code.
|
|
1675
|
+
if (paths === undefined) {
|
|
1676
|
+
out = { ...out, [key]: REDACTED };
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (paths.length === 0) continue;
|
|
1680
|
+
out = { ...out, [key]: redactSensitive(out[key], paths) };
|
|
1681
|
+
}
|
|
1682
|
+
return out;
|
|
1683
|
+
};
|
|
1609
1684
|
const span = (
|
|
1610
1685
|
phase: "start" | "end",
|
|
1611
1686
|
outcome: SpanOutcome | undefined,
|
|
@@ -1620,7 +1695,7 @@ export class EvaluationContext implements IEvaluationContext {
|
|
|
1620
1695
|
"invoke",
|
|
1621
1696
|
phase,
|
|
1622
1697
|
outcome,
|
|
1623
|
-
phase === "end" && rootScope ? { ...detail, context: rootScope } : detail,
|
|
1698
|
+
phase === "end" && rootScope ? { ...hide(detail), context: rootScope } : hide(detail),
|
|
1624
1699
|
);
|
|
1625
1700
|
// When tracing, a derived context carries the new id down the tree so nested
|
|
1626
1701
|
// invokes read it as their parent; it is never `=== ambient`, so the call
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instance → the contract paths its kind marked `x-telo-sensitive`, recorded at
|
|
3
|
+
* `create()` beside the handle and the declaration.
|
|
4
|
+
*
|
|
5
|
+
* The trace site has only the instance. It cannot re-derive this: the resolved
|
|
6
|
+
* contract is compiled inside `bindContract`'s closure and dropped, the
|
|
7
|
+
* definition carries the DECLARATION rather than the resolved schema, and an
|
|
8
|
+
* instance-manifest override would be missed by re-resolving from the kind. So
|
|
9
|
+
* the answer is recorded where both halves are in hand, exactly as
|
|
10
|
+
* `instance-declaration.ts` records the other direction.
|
|
11
|
+
*
|
|
12
|
+
* TWO entries per instance, kept apart by direction, because a contract may mark
|
|
13
|
+
* a field on the way in as well as on the way out — `forceRefresh` going to a
|
|
14
|
+
* credential is not sensitive, but a signing key handed to one would be, and a
|
|
15
|
+
* single merged list would redact an input path in an output payload where it
|
|
16
|
+
* names something else entirely.
|
|
17
|
+
*
|
|
18
|
+
* Weak and one-way: paths are obtainable FROM an instance, never an instance
|
|
19
|
+
* from paths, so nothing here extends a lifetime or hands out live state.
|
|
20
|
+
* Resolution is LAZY — the paths are read through a thunk rather than eagerly,
|
|
21
|
+
* because compiling a contract at create time would make every contract-bearing
|
|
22
|
+
* kind depend on type-registration order, which is the reason the binding defers
|
|
23
|
+
* it in the first place.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export type ContractDirection = "inputType" | "outputType";
|
|
27
|
+
|
|
28
|
+
interface SensitiveThunks {
|
|
29
|
+
inputType?: () => string[][];
|
|
30
|
+
outputType?: () => string[][];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sensitive = new WeakMap<object, SensitiveThunks>();
|
|
34
|
+
|
|
35
|
+
/** Record how to obtain one direction's sensitive paths for a live instance.
|
|
36
|
+
* First record wins, matching the handle and declaration rules: a `base:` child
|
|
37
|
+
* IS its parent instance, and the parent's binding is the one that produced
|
|
38
|
+
* it. */
|
|
39
|
+
export function recordSensitivePaths(
|
|
40
|
+
instance: object,
|
|
41
|
+
direction: ContractDirection,
|
|
42
|
+
paths: () => string[][],
|
|
43
|
+
): void {
|
|
44
|
+
const entry = sensitive.get(instance) ?? {};
|
|
45
|
+
if (entry[direction] !== undefined) return;
|
|
46
|
+
entry[direction] = paths;
|
|
47
|
+
sensitive.set(instance, entry);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The paths one direction of a live instance's contract marked sensitive.
|
|
52
|
+
*
|
|
53
|
+
* An EMPTY list means the contract marked nothing, or the kind declares no
|
|
54
|
+
* contract at all — carry the payload verbatim. `undefined` means the contract
|
|
55
|
+
* could not be resolved, so WHICH fields are sensitive is unknown and the
|
|
56
|
+
* payload must be withheld whole.
|
|
57
|
+
*
|
|
58
|
+
* Nothing is swallowed by that: resolving is exactly what the dispatch about to
|
|
59
|
+
* follow does, so the same failure surfaces from it a moment later, with its own
|
|
60
|
+
* code and its own message. What the catch avoids is a trace site becoming the
|
|
61
|
+
* place an unrelated contract defect first appears — and, far worse, emitting
|
|
62
|
+
* auth material onto the wire because a schema failed to compile.
|
|
63
|
+
*/
|
|
64
|
+
export function sensitivePathsOfInstance(
|
|
65
|
+
instance: unknown,
|
|
66
|
+
direction: ContractDirection,
|
|
67
|
+
): string[][] | undefined {
|
|
68
|
+
if (!instance || typeof instance !== "object") return [];
|
|
69
|
+
const thunk = sensitive.get(instance as object)?.[direction];
|
|
70
|
+
if (!thunk) return [];
|
|
71
|
+
try {
|
|
72
|
+
return thunk();
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What a hidden value reads as. The key is KEPT and only the value replaced,
|
|
79
|
+
* per the logging spec §14: a payload that silently loses a key reads as a
|
|
80
|
+
* value that was never produced. */
|
|
81
|
+
export const REDACTED = "[redacted]";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `value` with the marked paths replaced by {@link REDACTED}.
|
|
85
|
+
*
|
|
86
|
+
* Copy-on-write ALONG THE PATHS ONLY, so the caller's own object — the very
|
|
87
|
+
* object being handed to a controller, or the one it just returned — is never
|
|
88
|
+
* mutated. A whole-payload clone would be the obvious alternative and is wrong
|
|
89
|
+
* twice: it costs a deep copy per span on the dispatch path, and it would
|
|
90
|
+
* rewrite live values (a stream handle, a resource instance) that only survive
|
|
91
|
+
* by identity.
|
|
92
|
+
*/
|
|
93
|
+
export function redactSensitive(value: unknown, paths: readonly string[][]): unknown {
|
|
94
|
+
if (paths.length === 0) return value;
|
|
95
|
+
let out = value;
|
|
96
|
+
for (const path of paths) out = redactAt(out, path, 0);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function redactAt(node: unknown, path: readonly string[], index: number): unknown {
|
|
101
|
+
if (node === null || node === undefined) return node;
|
|
102
|
+
if (index === path.length) return REDACTED;
|
|
103
|
+
const segment = path[index];
|
|
104
|
+
if (segment === "[]") {
|
|
105
|
+
return Array.isArray(node) ? node.map((element) => redactAt(element, path, index + 1)) : node;
|
|
106
|
+
}
|
|
107
|
+
// The map-value wildcard: every own key, whatever it is named. What
|
|
108
|
+
// `additionalProperties` / `patternProperties` emit, since neither carries
|
|
109
|
+
// property names to walk.
|
|
110
|
+
if (segment === "{}") {
|
|
111
|
+
if (typeof node !== "object" || Array.isArray(node)) return node;
|
|
112
|
+
const source = node as Record<string, unknown>;
|
|
113
|
+
const out: Record<string, unknown> = {};
|
|
114
|
+
for (const [key, value] of Object.entries(source)) out[key] = redactAt(value, path, index + 1);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
if (typeof node !== "object" || Array.isArray(node)) return node;
|
|
118
|
+
const object = node as Record<string, unknown>;
|
|
119
|
+
// A path the value does not carry is not a defect — the contract describes
|
|
120
|
+
// what MAY be there, and an optional field is routinely absent.
|
|
121
|
+
if (!(segment in object)) return node;
|
|
122
|
+
return { ...object, [segment]: redactAt(object[segment], path, index + 1) };
|
|
123
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type DeclaredScalarForm,
|
|
6
6
|
type DeclaredScalarPath,
|
|
7
7
|
defaultBearingPaths,
|
|
8
|
+
sensitivePaths,
|
|
8
9
|
effectiveContractField,
|
|
9
10
|
describeProjectionFailure,
|
|
10
11
|
resolveSchemaProjections,
|
|
@@ -75,6 +76,9 @@ export interface BoundContract {
|
|
|
75
76
|
* value is normalized at, in either direction. Empty when the contract
|
|
76
77
|
* declares none. */
|
|
77
78
|
scalarPaths(): DeclaredScalarPath[];
|
|
79
|
+
/** Paths the contract marked `x-telo-sensitive` — the values a trace payload
|
|
80
|
+
* carries as `[redacted]`. Empty when the contract marks none. */
|
|
81
|
+
sensitivePaths(): string[][];
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
const CONTRACT_ERROR: Record<ContractDirection, string> = {
|
|
@@ -149,6 +153,7 @@ export function resolveBoundContract(
|
|
|
149
153
|
let compiled: { validate(value: unknown): void } | undefined;
|
|
150
154
|
let paths: string[][] | undefined;
|
|
151
155
|
let scalars: DeclaredScalarPath[] | undefined;
|
|
156
|
+
let sensitive: string[][] | undefined;
|
|
152
157
|
|
|
153
158
|
const resolve = (): { validate(value: unknown): void } => {
|
|
154
159
|
if (compiled !== undefined) return compiled;
|
|
@@ -193,6 +198,9 @@ export function resolveBoundContract(
|
|
|
193
198
|
const stripped = withLiveValuesSkipped(projected, factory.resolveRef);
|
|
194
199
|
paths = defaultBearingPaths(stripped, factory.resolveRef);
|
|
195
200
|
scalars = declaredScalarPaths(stripped, factory.resolveRef);
|
|
201
|
+
// Read off the STRIPPED schema like the other two, so a marked node behind a
|
|
202
|
+
// live value is not reported: nothing walks into a stream to redact it.
|
|
203
|
+
sensitive = sensitivePaths(stripped, factory.resolveRef);
|
|
196
204
|
// Compile by NAME whenever the declaration is one, so the type's CEL
|
|
197
205
|
// `rules:` are composed in — including when a stream had to be stripped, in
|
|
198
206
|
// which case the stream-bearing properties are dropped from the schema the
|
|
@@ -220,6 +228,10 @@ export function resolveBoundContract(
|
|
|
220
228
|
resolve();
|
|
221
229
|
return scalars ?? [];
|
|
222
230
|
},
|
|
231
|
+
sensitivePaths: () => {
|
|
232
|
+
resolve();
|
|
233
|
+
return sensitive ?? [];
|
|
234
|
+
},
|
|
223
235
|
};
|
|
224
236
|
}
|
|
225
237
|
|
package/src/kernel.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { ResourceContextImpl } from "./resource-context.js";
|
|
|
53
53
|
import { mintResourceHandle } from "./resource-handle.js";
|
|
54
54
|
import { bindEffectOwner } from "./effect-scope.js";
|
|
55
55
|
import { declarationOfInstance, recordInstanceDeclaration } from "./instance-declaration.js";
|
|
56
|
+
import { recordSensitivePaths } from "./instance-sensitive-paths.js";
|
|
56
57
|
import { nodeHostVersions } from "./host-versions.js";
|
|
57
58
|
import { nodeCelHandlers } from "./cel-handlers.js";
|
|
58
59
|
import { parseRef, seedInvokeSource } from "./invoke-dispatch.js";
|
|
@@ -1479,6 +1480,26 @@ export class Kernel implements IKernel {
|
|
|
1479
1480
|
|
|
1480
1481
|
if (!runtime.length) return { instance, ctx, resource: processedResource };
|
|
1481
1482
|
|
|
1483
|
+
// Runtime eval paths are expanded against a CALL's inputs, so they need a
|
|
1484
|
+
// call. `invoke` is the only entry point that takes any — `run()` and
|
|
1485
|
+
// `provide()` are parameterless — so a kind that declares one of these paths
|
|
1486
|
+
// and has no `invoke()` has annotated something nothing can ever expand: the
|
|
1487
|
+
// value stays a compiled expression for the life of the resource. Reported
|
|
1488
|
+
// here rather than dereferenced: this used to be a non-null assertion, and it
|
|
1489
|
+
// failed as `undefined is not an object (evaluating 'instance.invoke.bind')`
|
|
1490
|
+
// against the kernel's own source, naming neither the kind nor the field.
|
|
1491
|
+
// Method presence rather than declared capability, because the kernel
|
|
1492
|
+
// dispatches on the method — a Provider that implements `invoke` is bound
|
|
1493
|
+
// exactly like an Invocable.
|
|
1494
|
+
if (typeof instance.invoke !== "function") {
|
|
1495
|
+
throw new RuntimeError(
|
|
1496
|
+
"ERR_RUNTIME_EVAL_WITHOUT_INVOKE",
|
|
1497
|
+
`Kind ${resolvedKind} declares 'x-telo-eval: runtime' (at ${runtime.join(", ")}), but its resources have no invoke() — ` +
|
|
1498
|
+
`runtime evaluation expands a call's inputs, and run() / provide() take none. ` +
|
|
1499
|
+
`Use 'x-telo-eval: compile' for a value resolved once when the resource is created, or give the kind an invocable controller.`,
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1482
1503
|
// Override invoke in-place so all lifecycle methods (init/invoke/teardown/snapshot)
|
|
1483
1504
|
// share the same `this`. A wrapper object would split identity: state mutated by
|
|
1484
1505
|
// init() on the wrapper would be invisible to the original invoke(), which still
|
|
@@ -1487,7 +1508,7 @@ export class Kernel implements IKernel {
|
|
|
1487
1508
|
// Every argument is forwarded: `invoke(inputs, ctx)` carries the
|
|
1488
1509
|
// InvokeContext (cancellation, tracing) as its second parameter, and a
|
|
1489
1510
|
// wrapper that declares only `inputs` silently drops it.
|
|
1490
|
-
const originalInvoke = instance.invoke
|
|
1511
|
+
const originalInvoke = instance.invoke.bind(instance);
|
|
1491
1512
|
instance.invoke = async (inputs: any, ...rest: unknown[]) => {
|
|
1492
1513
|
const expanded = evalContext.expandPaths(inputs as Record<string, unknown>, runtime);
|
|
1493
1514
|
return (originalInvoke as (i: any, ...r: unknown[]) => Promise<unknown>)(expanded, ...rest);
|
|
@@ -1564,6 +1585,13 @@ export class Kernel implements IKernel {
|
|
|
1564
1585
|
);
|
|
1565
1586
|
if (!input && !output) return;
|
|
1566
1587
|
|
|
1588
|
+
// Recorded here because this is the only point holding both the instance and
|
|
1589
|
+
// its resolved contract — `bindContract` closes over the contract and drops
|
|
1590
|
+
// it, and the trace site has only the instance. Lazily, so a contract is
|
|
1591
|
+
// still compiled on first dispatch rather than at create time.
|
|
1592
|
+
if (input) recordSensitivePaths(instance, "inputType", () => input.sensitivePaths());
|
|
1593
|
+
if (output) recordSensitivePaths(instance, "outputType", () => output.sensitivePaths());
|
|
1594
|
+
|
|
1567
1595
|
bindContract(instance, {
|
|
1568
1596
|
input,
|
|
1569
1597
|
output,
|