@habitat-ai/resource-rule-evaluation 0.2.4 → 0.3.1
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/contract.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { Effect } from "effect";
|
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
3
|
/** Maximum detail retained by one rule-evaluation failure. */
|
|
4
4
|
export declare const MAX_RULE_EVALUATION_FAILURE_DETAIL = 4096;
|
|
5
|
+
/** Structural schema for one caller-identified, already-resolved program. */
|
|
6
|
+
export declare const RuleEvaluationProgramSchema: Type.TObject<{
|
|
7
|
+
id: Type.TReadonly<Type.TString>;
|
|
8
|
+
program: Type.TReadonly<Type.TString>;
|
|
9
|
+
}>;
|
|
5
10
|
/** Structural schema for one source position reported by an evaluator. */
|
|
6
11
|
export declare const RuleEvaluationPositionSchema: Type.TObject<{
|
|
7
12
|
line: Type.TReadonly<Type.TInteger>;
|
|
@@ -23,13 +28,17 @@ export declare const RuleEvaluationFindingSchema: Type.TObject<{
|
|
|
23
28
|
}>>;
|
|
24
29
|
message: Type.TReadonly<Type.TUnion<[Type.TString, Type.TNull]>>;
|
|
25
30
|
}>;
|
|
26
|
-
/** Structural schema for one
|
|
31
|
+
/** Structural schema for one exact-subject evaluation batch. */
|
|
27
32
|
export declare const RuleEvaluationRequestSchema: Type.TObject<{
|
|
28
|
-
|
|
33
|
+
programs: Type.TReadonly<Type.TImmutable<Type.TArray<Type.TObject<{
|
|
34
|
+
id: Type.TReadonly<Type.TString>;
|
|
35
|
+
program: Type.TReadonly<Type.TString>;
|
|
36
|
+
}>>>>;
|
|
29
37
|
subjectPaths: Type.TReadonly<Type.TImmutable<Type.TArray<Type.TString>>>;
|
|
30
38
|
}>;
|
|
31
|
-
/** Structural schema for one
|
|
32
|
-
export declare const
|
|
39
|
+
/** Structural schema for one program's attributed evaluation result. */
|
|
40
|
+
export declare const RuleEvaluationProgramResultSchema: Type.TObject<{
|
|
41
|
+
programId: Type.TReadonly<Type.TString>;
|
|
33
42
|
findings: Type.TReadonly<Type.TImmutable<Type.TArray<Type.TObject<{
|
|
34
43
|
path: Type.TReadonly<Type.TString>;
|
|
35
44
|
start: Type.TReadonly<Type.TObject<{
|
|
@@ -45,6 +54,26 @@ export declare const RuleEvaluationResultSchema: Type.TObject<{
|
|
|
45
54
|
message: Type.TReadonly<Type.TUnion<[Type.TString, Type.TNull]>>;
|
|
46
55
|
}>>>>;
|
|
47
56
|
}>;
|
|
57
|
+
/** Structural schema for one completed exact-subject evaluation batch. */
|
|
58
|
+
export declare const RuleEvaluationResultSchema: Type.TObject<{
|
|
59
|
+
results: Type.TReadonly<Type.TImmutable<Type.TArray<Type.TObject<{
|
|
60
|
+
programId: Type.TReadonly<Type.TString>;
|
|
61
|
+
findings: Type.TReadonly<Type.TImmutable<Type.TArray<Type.TObject<{
|
|
62
|
+
path: Type.TReadonly<Type.TString>;
|
|
63
|
+
start: Type.TReadonly<Type.TObject<{
|
|
64
|
+
line: Type.TReadonly<Type.TInteger>;
|
|
65
|
+
column: Type.TReadonly<Type.TInteger>;
|
|
66
|
+
offset: Type.TReadonly<Type.TInteger>;
|
|
67
|
+
}>>;
|
|
68
|
+
end: Type.TReadonly<Type.TObject<{
|
|
69
|
+
line: Type.TReadonly<Type.TInteger>;
|
|
70
|
+
column: Type.TReadonly<Type.TInteger>;
|
|
71
|
+
offset: Type.TReadonly<Type.TInteger>;
|
|
72
|
+
}>>;
|
|
73
|
+
message: Type.TReadonly<Type.TUnion<[Type.TString, Type.TNull]>>;
|
|
74
|
+
}>>>>;
|
|
75
|
+
}>>>>;
|
|
76
|
+
}>;
|
|
48
77
|
/** Provider-neutral mechanical failure reasons for one evaluation attempt. */
|
|
49
78
|
export declare const RuleEvaluationFailureReasonSchema: Type.TUnion<[Type.TLiteral<"InvalidInput">, Type.TLiteral<"SetupFailed">, Type.TLiteral<"ExecutionFailed">, Type.TLiteral<"TimedOut">, Type.TLiteral<"InvalidOutput">]>;
|
|
50
79
|
/** Structural schema for one bounded typed evaluation failure. */
|
|
@@ -57,9 +86,13 @@ export declare const RuleEvaluationFailureSchema: Type.TObject<{
|
|
|
57
86
|
export type RuleEvaluationPosition = Static<typeof RuleEvaluationPositionSchema>;
|
|
58
87
|
/** One provider-neutral finding. */
|
|
59
88
|
export type RuleEvaluationFinding = Static<typeof RuleEvaluationFindingSchema>;
|
|
60
|
-
/** One already-resolved
|
|
89
|
+
/** One caller-identified, already-resolved evaluator program. */
|
|
90
|
+
export type RuleEvaluationProgram = Static<typeof RuleEvaluationProgramSchema>;
|
|
91
|
+
/** One exact-subject evaluation batch. */
|
|
61
92
|
export type RuleEvaluationRequest = Static<typeof RuleEvaluationRequestSchema>;
|
|
62
|
-
/** One completed evaluation
|
|
93
|
+
/** One completed program result within an evaluation batch. */
|
|
94
|
+
export type RuleEvaluationProgramResult = Static<typeof RuleEvaluationProgramResultSchema>;
|
|
95
|
+
/** One completed exact-subject evaluation batch. */
|
|
63
96
|
export type RuleEvaluationResult = Static<typeof RuleEvaluationResultSchema>;
|
|
64
97
|
/** One provider-neutral mechanical failure reason. */
|
|
65
98
|
export type RuleEvaluationFailureReason = Static<typeof RuleEvaluationFailureReasonSchema>;
|
|
@@ -67,7 +100,7 @@ export type RuleEvaluationFailureReason = Static<typeof RuleEvaluationFailureRea
|
|
|
67
100
|
export type RuleEvaluationFailure = Static<typeof RuleEvaluationFailureSchema>;
|
|
68
101
|
/** Checks an unknown value against the complete rule-evaluation failure contract. */
|
|
69
102
|
export declare function isRuleEvaluationFailure(input: unknown): input is RuleEvaluationFailure;
|
|
70
|
-
/** Provider-neutral capability for evaluating one
|
|
103
|
+
/** Provider-neutral capability for evaluating resolved programs over one exact subject set. */
|
|
71
104
|
export interface RuleEvaluationResource<R = never> {
|
|
72
105
|
readonly evaluate: (input: RuleEvaluationRequest) => Effect.Effect<RuleEvaluationResult, RuleEvaluationFailure, R>;
|
|
73
106
|
}
|
package/dist/contract.js
CHANGED
|
@@ -5,6 +5,18 @@ export const MAX_RULE_EVALUATION_FAILURE_DETAIL = 4_096;
|
|
|
5
5
|
const NonEmptyStringSchema = Type.String({
|
|
6
6
|
minLength: 1,
|
|
7
7
|
});
|
|
8
|
+
/** Structural schema for one caller-identified, already-resolved program. */
|
|
9
|
+
export const RuleEvaluationProgramSchema = ReadonlyObject(Type.Object({
|
|
10
|
+
id: Type.String({
|
|
11
|
+
minLength: 1,
|
|
12
|
+
maxLength: 1_024,
|
|
13
|
+
description: "Invocation-local program identity used to attribute findings",
|
|
14
|
+
}),
|
|
15
|
+
program: Type.String({
|
|
16
|
+
minLength: 1,
|
|
17
|
+
description: "Already-resolved evaluator program",
|
|
18
|
+
}),
|
|
19
|
+
}), { additionalProperties: false });
|
|
8
20
|
/** Structural schema for one source position reported by an evaluator. */
|
|
9
21
|
export const RuleEvaluationPositionSchema = ReadonlyObject(Type.Object({
|
|
10
22
|
line: Type.Integer({
|
|
@@ -32,23 +44,35 @@ export const RuleEvaluationFindingSchema = ReadonlyObject(Type.Object({
|
|
|
32
44
|
description: "Evaluator-authored diagnostic message when one was produced",
|
|
33
45
|
}),
|
|
34
46
|
}), { additionalProperties: false });
|
|
35
|
-
/** Structural schema for one
|
|
47
|
+
/** Structural schema for one exact-subject evaluation batch. */
|
|
36
48
|
export const RuleEvaluationRequestSchema = ReadonlyObject(Type.Object({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
description: "
|
|
49
|
+
programs: ReadonlyObject(Type.Array(RuleEvaluationProgramSchema), {
|
|
50
|
+
minItems: 1,
|
|
51
|
+
description: "Non-empty ordered programs evaluated against the same subjects",
|
|
40
52
|
}),
|
|
41
53
|
subjectPaths: ReadonlyObject(Type.Array(NonEmptyStringSchema), {
|
|
42
54
|
minItems: 1,
|
|
43
55
|
description: "Non-empty caller-resolved absolute subject paths",
|
|
44
56
|
}),
|
|
45
57
|
}), { additionalProperties: false });
|
|
46
|
-
/** Structural schema for one
|
|
47
|
-
export const
|
|
58
|
+
/** Structural schema for one program's attributed evaluation result. */
|
|
59
|
+
export const RuleEvaluationProgramResultSchema = ReadonlyObject(Type.Object({
|
|
60
|
+
programId: Type.String({
|
|
61
|
+
minLength: 1,
|
|
62
|
+
maxLength: 1_024,
|
|
63
|
+
description: "Invocation-local identity of the evaluated program",
|
|
64
|
+
}),
|
|
48
65
|
findings: ReadonlyObject(Type.Array(RuleEvaluationFindingSchema), {
|
|
49
66
|
description: "Findings reported by the evaluator in provider-defined stable order",
|
|
50
67
|
}),
|
|
51
68
|
}), { additionalProperties: false });
|
|
69
|
+
/** Structural schema for one completed exact-subject evaluation batch. */
|
|
70
|
+
export const RuleEvaluationResultSchema = ReadonlyObject(Type.Object({
|
|
71
|
+
results: ReadonlyObject(Type.Array(RuleEvaluationProgramResultSchema), {
|
|
72
|
+
minItems: 1,
|
|
73
|
+
description: "Per-program results in request order",
|
|
74
|
+
}),
|
|
75
|
+
}), { additionalProperties: false });
|
|
52
76
|
/** Provider-neutral mechanical failure reasons for one evaluation attempt. */
|
|
53
77
|
export const RuleEvaluationFailureReasonSchema = Type.Union([
|
|
54
78
|
Type.Literal("InvalidInput"),
|
|
@@ -6,9 +6,10 @@ import { ReadonlyObject, Type } from "typebox";
|
|
|
6
6
|
import Schema from "typebox/schema";
|
|
7
7
|
import { Value } from "typebox/value";
|
|
8
8
|
const TEMP_CATALOG_PREFIX = "habitat-rule-evaluation-";
|
|
9
|
-
const
|
|
10
|
-
/**
|
|
11
|
-
const
|
|
9
|
+
const GRIT_PATTERN_PREFIX = "habitat_rule_evaluation_";
|
|
10
|
+
/** Output retained per program, capped so an untrusted batch cannot allocate without bound. */
|
|
11
|
+
const MAX_GRIT_OUTPUT_BYTES_PER_PROGRAM = 256 * 1_024;
|
|
12
|
+
const MAX_GRIT_BATCH_OUTPUT_BYTES = 16 * 1_024 * 1_024;
|
|
12
13
|
/** Structural schema for Grit provider construction. */
|
|
13
14
|
export const GritRuleEvaluationProviderConfigSchema = ReadonlyObject(Type.Object({
|
|
14
15
|
executable: Type.String({
|
|
@@ -27,11 +28,12 @@ const GritPositionSchema = Type.Object({
|
|
|
27
28
|
}, { additionalProperties: false, description: "Grit-reported source position" });
|
|
28
29
|
const GritResultSchema = Type.Object({
|
|
29
30
|
check_id: Type.String({
|
|
30
|
-
|
|
31
|
-
description: "
|
|
31
|
+
minLength: 1,
|
|
32
|
+
description: "Grit check identity with evaluator suffix",
|
|
32
33
|
}),
|
|
33
|
-
local_name: Type.
|
|
34
|
-
|
|
34
|
+
local_name: Type.String({
|
|
35
|
+
minLength: 1,
|
|
36
|
+
description: "Local Grit catalog pattern name",
|
|
35
37
|
}),
|
|
36
38
|
path: Type.String({ minLength: 1, description: "Grit-reported subject path" }),
|
|
37
39
|
start: GritPositionSchema,
|
|
@@ -65,13 +67,27 @@ export function makeGritRuleEvaluationResource(config) {
|
|
|
65
67
|
return yield* fail("InvalidInput", "Grit provider configuration does not match its structural contract");
|
|
66
68
|
}
|
|
67
69
|
if (!requestValidator.Check(input)) {
|
|
68
|
-
return yield* fail("InvalidInput", "Rule-evaluation request requires
|
|
70
|
+
return yield* fail("InvalidInput", "Rule-evaluation request requires resolved programs and non-empty subject paths");
|
|
69
71
|
}
|
|
70
72
|
const path = yield* Path.Path;
|
|
71
73
|
if (input.subjectPaths.some((subjectPath) => !path.isAbsolute(subjectPath))) {
|
|
72
74
|
return yield* fail("InvalidInput", "Rule-evaluation subject paths must be caller-resolved absolute paths");
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
const [firstProgram, ...remainingPrograms] = input.programs;
|
|
77
|
+
if (firstProgram === undefined) {
|
|
78
|
+
return yield* fail("InvalidInput", "Rule-evaluation request requires at least one program");
|
|
79
|
+
}
|
|
80
|
+
if (new Set(input.programs.map(({ id }) => id)).size !== input.programs.length) {
|
|
81
|
+
return yield* fail("InvalidInput", "Rule-evaluation program identities must be unique");
|
|
82
|
+
}
|
|
83
|
+
const programs = [
|
|
84
|
+
materializeProgram(firstProgram, 0),
|
|
85
|
+
...remainingPrograms.map((program) => materializeProgram(program, 0)),
|
|
86
|
+
];
|
|
87
|
+
const evaluations = yield* Effect.forEach(programs, (program) => Effect.scoped(withScopedGritCatalog([program], (catalog) => runGritCheck(config, catalog, [program], input.subjectPaths))), { concurrency: 1 });
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
results: Object.freeze(evaluations.flatMap(({ results }) => results)),
|
|
90
|
+
});
|
|
75
91
|
});
|
|
76
92
|
return Object.freeze({ evaluate });
|
|
77
93
|
}
|
|
@@ -82,7 +98,7 @@ export function makeNodeGritRuleEvaluationResource(config) {
|
|
|
82
98
|
evaluate: (input) => resource.evaluate(input).pipe(Effect.provide(NodeServices.layer)),
|
|
83
99
|
});
|
|
84
100
|
}
|
|
85
|
-
function withScopedGritCatalog(
|
|
101
|
+
function withScopedGritCatalog(programs, use) {
|
|
86
102
|
return Effect.gen(function* () {
|
|
87
103
|
const fs = yield* FileSystem.FileSystem;
|
|
88
104
|
const path = yield* Path.Path;
|
|
@@ -105,12 +121,13 @@ function withScopedGritCatalog(program, use) {
|
|
|
105
121
|
.makeDirectory(catalog.cacheDirectory)
|
|
106
122
|
.pipe(mapPlatform("SetupFailed", "Failed to create Grit cache directory"));
|
|
107
123
|
yield* fs
|
|
108
|
-
.writeFileString(path.join(catalog.gritDirectory, "grit.yaml"), renderGritCatalog(
|
|
124
|
+
.writeFileString(path.join(catalog.gritDirectory, "grit.yaml"), renderGritCatalog(programs))
|
|
109
125
|
.pipe(mapPlatform("SetupFailed", "Failed to write temporary Grit catalog"));
|
|
110
126
|
return yield* use(catalog);
|
|
111
127
|
});
|
|
112
128
|
}
|
|
113
|
-
function runGritCheck(config, catalog, subjectPaths) {
|
|
129
|
+
function runGritCheck(config, catalog, programs, subjectPaths) {
|
|
130
|
+
const outputLimit = gritOutputLimit(programs.length);
|
|
114
131
|
const observe = Effect.gen(function* () {
|
|
115
132
|
const command = ChildProcess.make(config.executable, ["--json", "check", "--no-cache", "--grit-dir", catalog.gritDirectory, ...subjectPaths], {
|
|
116
133
|
cwd: catalog.root,
|
|
@@ -120,6 +137,7 @@ function runGritCheck(config, catalog, subjectPaths) {
|
|
|
120
137
|
GRIT_USER_CONFIG: catalog.userConfigDirectory,
|
|
121
138
|
GRIT_TELEMETRY_DISABLED: "true",
|
|
122
139
|
NO_COLOR: undefined,
|
|
140
|
+
RAYON_NUM_THREADS: "2",
|
|
123
141
|
},
|
|
124
142
|
extendEnv: true,
|
|
125
143
|
stdin: "ignore",
|
|
@@ -128,8 +146,8 @@ function runGritCheck(config, catalog, subjectPaths) {
|
|
|
128
146
|
});
|
|
129
147
|
const process = yield* command.pipe(mapPlatform("ExecutionFailed", "Failed to start Grit"));
|
|
130
148
|
const output = yield* Effect.all({
|
|
131
|
-
stdout: collectBoundedOutput(process.stdout, "stdout"),
|
|
132
|
-
stderr: collectBoundedOutput(process.stderr, "stderr"),
|
|
149
|
+
stdout: collectBoundedOutput(process.stdout, "stdout", outputLimit),
|
|
150
|
+
stderr: collectBoundedOutput(process.stderr, "stderr", outputLimit),
|
|
133
151
|
exitCode: process.exitCode.pipe(Effect.map(Number), mapPlatform("ExecutionFailed", "Failed to observe Grit exit")),
|
|
134
152
|
}, { concurrency: "unbounded" });
|
|
135
153
|
return Object.freeze(output);
|
|
@@ -147,7 +165,7 @@ function runGritCheck(config, catalog, subjectPaths) {
|
|
|
147
165
|
if (observation.stdout.trim().length > 0) {
|
|
148
166
|
return fail("InvalidOutput", "Grit check emitted unexpected stdout alongside its stderr JSON report");
|
|
149
167
|
}
|
|
150
|
-
return decodeGritReport(observation.stderr).pipe(Effect.
|
|
168
|
+
return decodeGritReport(observation.stderr).pipe(Effect.flatMap((report) => resultFromGritReport(report, programs)));
|
|
151
169
|
}));
|
|
152
170
|
}
|
|
153
171
|
function decodeGritReport(stderr) {
|
|
@@ -159,47 +177,61 @@ function decodeGritReport(stderr) {
|
|
|
159
177
|
catch: (error) => failure("InvalidOutput", `Grit check emitted an invalid report: ${errorMessage(error)}`),
|
|
160
178
|
})));
|
|
161
179
|
}
|
|
162
|
-
function resultFromGritReport(report) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
function resultFromGritReport(report, programs) {
|
|
181
|
+
const programsByPattern = new Map(programs.map((program) => [program.patternName, program]));
|
|
182
|
+
const findingsByProgram = new Map(programs.map(({ id }) => [id, []]));
|
|
183
|
+
for (const result of report.results) {
|
|
184
|
+
const program = programsByPattern.get(result.local_name);
|
|
185
|
+
if (program === undefined || !result.check_id.startsWith(`#${result.local_name}/`)) {
|
|
186
|
+
return fail("InvalidOutput", `Grit check reported unexpected pattern identity '${result.local_name}'`);
|
|
187
|
+
}
|
|
188
|
+
findingsByProgram.get(program.id)?.push(Object.freeze({
|
|
189
|
+
path: result.path,
|
|
190
|
+
start: Object.freeze({
|
|
191
|
+
line: result.start.line,
|
|
192
|
+
column: result.start.col,
|
|
193
|
+
offset: result.start.offset,
|
|
194
|
+
}),
|
|
195
|
+
end: Object.freeze({
|
|
196
|
+
line: result.end.line,
|
|
197
|
+
column: result.end.col,
|
|
198
|
+
offset: result.end.offset,
|
|
199
|
+
}),
|
|
200
|
+
message: result.extra.message,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
return Effect.succeed(Object.freeze({
|
|
204
|
+
results: Object.freeze(programs.map(({ id }) => Object.freeze({
|
|
205
|
+
programId: id,
|
|
206
|
+
findings: Object.freeze((findingsByProgram.get(id) ?? []).sort(compareFindings)),
|
|
207
|
+
}))),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
function materializeProgram(program, index) {
|
|
179
211
|
return Object.freeze({
|
|
180
|
-
|
|
212
|
+
id: program.id,
|
|
213
|
+
patternName: `${GRIT_PATTERN_PREFIX}${index}`,
|
|
214
|
+
program: program.program,
|
|
181
215
|
});
|
|
182
216
|
}
|
|
183
|
-
function renderGritCatalog(
|
|
217
|
+
function renderGritCatalog(programs) {
|
|
184
218
|
return `${JSON.stringify({
|
|
185
219
|
version: "0.0.2",
|
|
186
|
-
patterns:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
},
|
|
193
|
-
],
|
|
220
|
+
patterns: programs.map(({ id, patternName, program }) => Object.freeze({
|
|
221
|
+
name: patternName,
|
|
222
|
+
title: id,
|
|
223
|
+
level: "error",
|
|
224
|
+
body: program,
|
|
225
|
+
})),
|
|
194
226
|
}, undefined, 2)}\n`;
|
|
195
227
|
}
|
|
196
|
-
function collectBoundedOutput(stream, channel) {
|
|
197
|
-
return Stream.runFoldEffect(stream.pipe(Stream.mapError((error) => failure("ExecutionFailed", `Failed to drain Grit ${channel}: ${error.message}`))), () => Object.freeze({ buffer: new Uint8Array(
|
|
228
|
+
function collectBoundedOutput(stream, channel, outputLimit) {
|
|
229
|
+
return Stream.runFoldEffect(stream.pipe(Stream.mapError((error) => failure("ExecutionFailed", `Failed to drain Grit ${channel}: ${error.message}`))), () => Object.freeze({ buffer: new Uint8Array(outputLimit), bytes: 0 }), (state, chunk) => {
|
|
198
230
|
if (chunk.byteLength === 0)
|
|
199
231
|
return Effect.succeed(state);
|
|
200
232
|
const bytes = state.bytes + chunk.byteLength;
|
|
201
|
-
return bytes >
|
|
202
|
-
? fail("InvalidOutput", `Grit ${channel} exceeded its ${
|
|
233
|
+
return bytes > outputLimit
|
|
234
|
+
? fail("InvalidOutput", `Grit ${channel} exceeded its ${outputLimit}-byte output limit`)
|
|
203
235
|
: Effect.sync(() => {
|
|
204
236
|
state.buffer.set(chunk, state.bytes);
|
|
205
237
|
return Object.freeze({ buffer: state.buffer, bytes });
|
|
@@ -209,6 +241,9 @@ function collectBoundedOutput(stream, channel) {
|
|
|
209
241
|
catch: (error) => failure("InvalidOutput", `Grit ${channel} was not valid UTF-8: ${errorMessage(error)}`),
|
|
210
242
|
})));
|
|
211
243
|
}
|
|
244
|
+
function gritOutputLimit(programCount) {
|
|
245
|
+
return Math.min(MAX_GRIT_BATCH_OUTPUT_BYTES, MAX_GRIT_OUTPUT_BYTES_PER_PROGRAM * programCount);
|
|
246
|
+
}
|
|
212
247
|
function mapPlatform(reason, context) {
|
|
213
248
|
return (effect) => effect.pipe(Effect.mapError((error) => failure(reason, `${context}: ${error.message}`)));
|
|
214
249
|
}
|