@milaboratories/pl-middle-layer 1.73.4 → 1.73.6

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.
@@ -0,0 +1,386 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { FieldData, Filter, FinalityEntry, ResourceData } from "@milaboratories/pl-client";
3
+ import {
4
+ CacheFinality,
5
+ createSignedResourceId,
6
+ FilterOperatorType,
7
+ FilterProperty,
8
+ FinalityTable,
9
+ NullSignedResourceId,
10
+ ResourceTypeName,
11
+ ResourceTypePrefix,
12
+ StrictFinality,
13
+ TreeFinality,
14
+ treeFilter,
15
+ } from "@milaboratories/pl-client";
16
+ import { finalityStopRuleClause, finalityStopRules } from "./finality_stop_rules";
17
+
18
+ import { projectTreeTraverseStopRules } from "./project";
19
+
20
+ /** Expected clauses of the project tree's stop rules, listed independently of the table. */
21
+ function expectedStopRules(): Filter {
22
+ return treeFilter.or(
23
+ treeFilter.and(
24
+ treeFilter.resourceTypeEq(ResourceTypeName.StreamManager),
25
+ treeFilter.readyOrDuplicateOrError(),
26
+ ),
27
+ treeFilter.and(
28
+ treeFilter.resourceTypeEq(ResourceTypeName.StdMap),
29
+ treeFilter.readyOrDuplicateOrError(),
30
+ ),
31
+ treeFilter.and(
32
+ treeFilter.resourceTypeEq(ResourceTypeName.StdMapSlash),
33
+ treeFilter.readyOrDuplicateOrError(),
34
+ ),
35
+ treeFilter.and(
36
+ treeFilter.resourceTypeEq(ResourceTypeName.EphStdMap),
37
+ treeFilter.readyOrDuplicateOrError(),
38
+ ),
39
+ treeFilter.and(
40
+ treeFilter.resourceTypeEq(ResourceTypeName.PFrame),
41
+ treeFilter.readyOrDuplicateOrError(),
42
+ ),
43
+ treeFilter.and(
44
+ treeFilter.resourceTypeEq(ResourceTypeName.ParquetChunk),
45
+ treeFilter.readyOrDuplicateOrError(),
46
+ ),
47
+ treeFilter.and(
48
+ treeFilter.resourceTypeEq(ResourceTypeName.BContext),
49
+ treeFilter.readyOrDuplicateOrError(),
50
+ ),
51
+ treeFilter.and(
52
+ treeFilter.resourceTypeEq(ResourceTypeName.BlockPackCustom),
53
+ treeFilter.readyOrDuplicateOrError(),
54
+ ),
55
+ treeFilter.and(
56
+ treeFilter.resourceTypeEq(ResourceTypeName.BinaryMap),
57
+ treeFilter.readyOrDuplicateOrError(),
58
+ ),
59
+ treeFilter.and(
60
+ treeFilter.resourceTypeEq(ResourceTypeName.BinaryValue),
61
+ treeFilter.readyOrDuplicateOrError(),
62
+ ),
63
+ treeFilter.and(
64
+ treeFilter.resourceTypeEq(ResourceTypeName.BlobMap),
65
+ treeFilter.readyOrDuplicateOrError(),
66
+ ),
67
+ treeFilter.and(
68
+ treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingleNoResult),
69
+ treeFilter.readyOrDuplicateOrError(),
70
+ ),
71
+ treeFilter.and(
72
+ treeFilter.resourceTypeEq(ResourceTypeName.BQueryResult),
73
+ treeFilter.readyOrDuplicateOrError(),
74
+ ),
75
+ treeFilter.and(
76
+ treeFilter.resourceTypeEq(ResourceTypeName.TengoTemplate),
77
+ treeFilter.readyOrDuplicateOrError(),
78
+ ),
79
+ treeFilter.and(
80
+ treeFilter.resourceTypeEq(ResourceTypeName.TengoLib),
81
+ treeFilter.readyOrDuplicateOrError(),
82
+ ),
83
+ treeFilter.and(
84
+ treeFilter.resourceTypeEq(ResourceTypeName.SoftwareInfo),
85
+ treeFilter.readyOrDuplicateOrError(),
86
+ ),
87
+ treeFilter.and(
88
+ treeFilter.resourceTypeEq(ResourceTypeName.Dummy),
89
+ treeFilter.readyOrDuplicateOrError(),
90
+ ),
91
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonResourceError),
92
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonObject),
93
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonGzObject),
94
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonString),
95
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonArray),
96
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonNumber),
97
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonBool),
98
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonNull),
99
+ treeFilter.resourceTypeEq(ResourceTypeName.JsonErrorTrace),
100
+ treeFilter.resourceTypeEq(ResourceTypeName.BContextEnd),
101
+ treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromUrl),
102
+ treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromFolder),
103
+ treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromLocalTgz),
104
+ treeFilter.resourceTypeEq(ResourceTypeName.BObjectSpec),
105
+ treeFilter.resourceTypeEq(ResourceTypeName.Blob),
106
+ treeFilter.resourceTypeEq(ResourceTypeName.Null),
107
+ treeFilter.resourceTypeEq(ResourceTypeName.Binary),
108
+ treeFilter.resourceTypeEq(ResourceTypeName.WorkingDirectory),
109
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.Blob),
110
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.LS),
111
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.WorkingDirectory),
112
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StorageSpaceAllocation),
113
+ treeFilter.and(
114
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobUpload),
115
+ treeFilter.readyOrDuplicateOrError(),
116
+ treeFilter.outputsLocked(true),
117
+ treeFilter.allOutputsFinal(true),
118
+ ),
119
+ treeFilter.and(
120
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobIndex),
121
+ treeFilter.readyOrDuplicateOrError(),
122
+ treeFilter.outputsLocked(true),
123
+ treeFilter.allOutputsFinal(true),
124
+ ),
125
+ treeFilter.and(
126
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.PColumnData),
127
+ treeFilter.readyOrDuplicateOrError(),
128
+ ),
129
+ treeFilter.and(
130
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StreamWorkdir),
131
+ treeFilter.readyOrDuplicateOrError(),
132
+ ),
133
+ );
134
+ }
135
+
136
+ function clauses(f: Filter): string[] {
137
+ if (f.value.oneofKind !== "filtersValue") throw new Error("expected a group filter");
138
+ return f.value.filtersValue.filters.map((c) => JSON.stringify(c)).sort();
139
+ }
140
+
141
+ describe("the project tree's stop rules come from TreeFinality", () => {
142
+ test("project stop rules contain the declared clause of every built-in type", () => {
143
+ const resolverAndCopyClauses = [
144
+ treeFilter.and(
145
+ treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingle),
146
+ treeFilter.readyOrDuplicateOrError(),
147
+ treeFilter.outputsLocked(true),
148
+ treeFilter.allOutputsFinal(true),
149
+ ),
150
+ treeFilter.and(
151
+ treeFilter.resourceTypeEq(ResourceTypeName.BResolveChoice),
152
+ treeFilter.readyOrDuplicateOrError(),
153
+ treeFilter.outputsLocked(true),
154
+ treeFilter.allOutputsFinal(true),
155
+ ),
156
+ treeFilter.and(
157
+ treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobCopy),
158
+ treeFilter.readyOrDuplicateOrError(),
159
+ treeFilter.outputsLocked(true),
160
+ treeFilter.allOutputsFinal(true),
161
+ ),
162
+ ];
163
+ const expected = [
164
+ ...clauses(expectedStopRules()),
165
+ ...resolverAndCopyClauses.map((c) => JSON.stringify(c)),
166
+ ];
167
+ expect(clauses(projectTreeTraverseStopRules())).toEqual(expected.sort());
168
+ });
169
+
170
+ test("an entry that is never final sends no stop rule", () => {
171
+ for (const e of TreeFinality.entries)
172
+ if (e.rule === "never") expect(finalityStopRuleClause(e)).toBeUndefined();
173
+ });
174
+
175
+ test("an approximated entry stops on its declared condition", () => {
176
+ const exact = TreeFinality.entries.find((e) => e.rule === "readyAndAllOutputsFilled");
177
+ expect(exact).toBeDefined();
178
+ const match = exact!.match;
179
+ expect(finalityStopRuleClause(exact!)).toEqual(
180
+ treeFilter.and(
181
+ "name" in match
182
+ ? treeFilter.resourceTypeEq(match.name)
183
+ : treeFilter.resourceTypeMatch("^" + match.prefix),
184
+ treeFilter.readyOrDuplicateOrError(),
185
+ treeFilter.outputsLocked(true),
186
+ treeFilter.allOutputsFinal(true),
187
+ ),
188
+ );
189
+ });
190
+
191
+ test("a prefix is matched literally", () => {
192
+ const f = finalityStopRules(
193
+ TreeFinality.extend("probe", [{ match: { prefix: "A.B/" }, rule: "always", why: "test" }]),
194
+ );
195
+ expect(JSON.stringify(f)).toContain(JSON.stringify("^A\\.B/"));
196
+ });
197
+ });
198
+
199
+ const target = createSignedResourceId(201n);
200
+ const failure = createSignedResourceId(202n);
201
+ function field(patch: Partial<FieldData> = {}): FieldData {
202
+ return {
203
+ name: "result",
204
+ type: "Output",
205
+ status: "Resolved",
206
+ value: target,
207
+ error: NullSignedResourceId,
208
+ valueIsFinal: true,
209
+ ...patch,
210
+ };
211
+ }
212
+ function resource(name: string, patch: Partial<ResourceData> = {}): ResourceData {
213
+ return {
214
+ id: createSignedResourceId(203n),
215
+ type: { name, version: "1" },
216
+ kind: "Structural",
217
+ data: undefined,
218
+ originalResourceId: NullSignedResourceId,
219
+ error: NullSignedResourceId,
220
+ inputsLocked: true,
221
+ outputsLocked: true,
222
+ resourceReady: true,
223
+ final: false,
224
+ fields: [field()],
225
+ ...patch,
226
+ };
227
+ }
228
+ function nameOf(e: FinalityEntry): string {
229
+ return "name" in e.match ? e.match.name : e.match.prefix + "probe";
230
+ }
231
+
232
+ /** A stop rule evaluated the way the backend does (core/pl tx_tree_iterator.go,
233
+ * evalTraverseStopRules): ALL_OUTPUTS_FINAL holds when every retained field has an error or a final
234
+ * value; HAS_ERRORS holds when the resource or any of its fields has an error. */
235
+ function evaluate(f: Filter, r: ResourceData): boolean {
236
+ if (f.value.oneofKind === "filtersValue") {
237
+ const children = f.value.filtersValue.filters;
238
+ if (f.operator === FilterOperatorType.AND) return children.every((c) => evaluate(c, r));
239
+ if (f.operator === FilterOperatorType.OR) return children.some((c) => evaluate(c, r));
240
+ if (f.operator === FilterOperatorType.NOT) return !evaluate(children[0]!, r);
241
+ }
242
+ if (f.value.oneofKind === "stringValue" && f.key === FilterProperty.RESOURCE_TYPE) {
243
+ if (f.operator === FilterOperatorType.EQUAL) return r.type.name === f.value.stringValue;
244
+ if (f.operator === FilterOperatorType.MATCH)
245
+ return new RegExp(f.value.stringValue).test(r.type.name);
246
+ }
247
+ if (f.value.oneofKind === "boolValue" && f.operator === FilterOperatorType.EQUAL) {
248
+ let actual: boolean;
249
+ switch (f.key) {
250
+ case FilterProperty.RESOURCE_READY_FOR_CALCULATION:
251
+ actual = r.resourceReady;
252
+ break;
253
+ case FilterProperty.IS_DUPLICATE:
254
+ actual = r.originalResourceId !== NullSignedResourceId;
255
+ break;
256
+ case FilterProperty.HAS_ERRORS:
257
+ actual =
258
+ r.error !== NullSignedResourceId ||
259
+ r.fields.some((v) => v.error !== NullSignedResourceId);
260
+ break;
261
+ case FilterProperty.OUTPUTS_LOCKED:
262
+ actual = r.outputsLocked;
263
+ break;
264
+ case FilterProperty.ALL_OUTPUTS_FINAL:
265
+ actual = r.fields.every((v) => v.error !== NullSignedResourceId || v.valueIsFinal);
266
+ break;
267
+ default:
268
+ throw new Error(`unhandled boolean property ${f.key}`);
269
+ }
270
+ return actual === f.value.boolValue;
271
+ }
272
+ throw new Error(`unhandled filter ${JSON.stringify(f)}`);
273
+ }
274
+
275
+ test("an entry declared exact stops exactly where its predicate is final", () => {
276
+ const rules = finalityStopRules(TreeFinality);
277
+ const approximateNames = new Set(
278
+ TreeFinality.entries
279
+ .filter((e) => e.stopRule !== undefined && e.stopRule !== "exact")
280
+ .map(nameOf),
281
+ );
282
+ const names = [
283
+ ...new Set(
284
+ TreeFinality.entries.flatMap((e) =>
285
+ "name" in e.match
286
+ ? [e.match.name]
287
+ : [e.match.prefix, e.match.prefix + "probe", e.match.prefix + "BlobCopy/nested"],
288
+ ),
289
+ ),
290
+ ];
291
+ for (const name of names) {
292
+ if (approximateNames.has(name)) continue;
293
+ for (const version of ["", "1", "2"])
294
+ for (let bits = 0; bits < 16; bits++) {
295
+ for (const fields of [
296
+ [],
297
+ [field()],
298
+ [field({ valueIsFinal: false })],
299
+ [field({ value: NullSignedResourceId, valueIsFinal: false })],
300
+ ]) {
301
+ const r = resource(name, {
302
+ type: { name, version },
303
+ resourceReady: !!(bits & 1),
304
+ outputsLocked: !!(bits & 2),
305
+ originalResourceId: bits & 4 ? target : NullSignedResourceId,
306
+ error: bits & 8 ? failure : NullSignedResourceId,
307
+ fields,
308
+ });
309
+ expect(evaluate(rules, r), `${name}:${version}, state ${bits}`).toBe(
310
+ TreeFinality.isFinal(r),
311
+ );
312
+ }
313
+ }
314
+ }
315
+ });
316
+
317
+ test("an unresolved Service field keeps a resolver or copy from both the filter's stop and finality", () => {
318
+ const rules = finalityStopRules(TreeFinality);
319
+ for (const name of ["BResolveSingle", "BResolveChoice", "BlobCopy/aToB"]) {
320
+ const r = resource(name, {
321
+ fields: [
322
+ field(),
323
+ field({
324
+ name: "service",
325
+ type: "Service",
326
+ status: "Empty",
327
+ value: NullSignedResourceId,
328
+ valueIsFinal: false,
329
+ }),
330
+ ],
331
+ });
332
+ expect(TreeFinality.isFinal(r), name).toBe(false);
333
+ expect(evaluate(rules, r), name).toBe(false);
334
+ }
335
+ });
336
+
337
+ test("an error on a field alone: every ready-or-error clause stops early, as declared", () => {
338
+ for (const e of TreeFinality.entries) {
339
+ if (e.rule !== "readyOrDuplicateOrError" && e.rule !== "readyAndAllOutputsFilled") continue;
340
+ const name = nameOf(e);
341
+ expect(e.stopRule, name).toMatchObject({ approx: e.rule });
342
+ const r = resource(name, {
343
+ resourceReady: false,
344
+ fields: [
345
+ field(),
346
+ field({
347
+ name: "input",
348
+ type: "Input",
349
+ value: NullSignedResourceId,
350
+ valueIsFinal: false,
351
+ error: failure,
352
+ }),
353
+ ],
354
+ });
355
+ expect(TreeFinality.isFinal(r), name).toBe(false);
356
+ expect(evaluate(finalityStopRuleClause(e)!, r), name).toBe(true);
357
+ }
358
+ });
359
+
360
+ test("stop rules follow the layers: an inherited never neither hides nor revokes a child's entry", () => {
361
+ const table = FinalityTable.of("base", [
362
+ { match: { name: "A.B/child" }, rule: "never", why: "probe" },
363
+ ])
364
+ .extend("allow", [{ match: { prefix: "A.B/" }, rule: "always", why: "probe" }])
365
+ .extend("cannot revoke", [{ match: { prefix: "A.B/" }, rule: "never", why: "probe" }]);
366
+ const rules = finalityStopRules(table);
367
+ for (const name of ["A.B/child", "A.B/", "AXB/child", "A.B", "xA.B/child"]) {
368
+ const expected = name.startsWith("A.B/");
369
+ expect(table.isFinal(resource(name))).toBe(expected);
370
+ expect(evaluate(rules, resource(name))).toBe(expected);
371
+ }
372
+ for (const layer of [StrictFinality, CacheFinality, TreeFinality]) {
373
+ for (const name of [
374
+ "Blob",
375
+ "Blob/fs",
376
+ "BlobIndex/fs",
377
+ "BlobUpload/fs",
378
+ "BlobCopy/fs",
379
+ "WorkingDirectory",
380
+ ]) {
381
+ expect(evaluate(finalityStopRules(layer), resource(name)), `${layer.name}:${name}`).toBe(
382
+ layer.isFinal(resource(name)),
383
+ );
384
+ }
385
+ }
386
+ });
@@ -0,0 +1,58 @@
1
+ import type {
2
+ Filter,
3
+ FinalityEntry,
4
+ FinalityTable,
5
+ TranslatableFinalityRule,
6
+ } from "@milaboratories/pl-client";
7
+ import { treeFilter } from "@milaboratories/pl-client";
8
+
9
+ function escapeRegExp(literal: string): string {
10
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+
13
+ function typeFilter(match: FinalityEntry["match"]): Filter {
14
+ return "name" in match
15
+ ? treeFilter.resourceTypeEq(match.name)
16
+ : treeFilter.resourceTypeMatch("^" + escapeRegExp(match.prefix));
17
+ }
18
+
19
+ function conditionFilter(type: Filter, rule: TranslatableFinalityRule): Filter {
20
+ switch (rule) {
21
+ case "always":
22
+ return type;
23
+ case "readyOrDuplicateOrError":
24
+ return treeFilter.and(type, treeFilter.readyOrDuplicateOrError());
25
+ case "readyAndAllOutputsFilled":
26
+ return treeFilter.and(
27
+ type,
28
+ treeFilter.readyOrDuplicateOrError(),
29
+ treeFilter.outputsLocked(true),
30
+ treeFilter.allOutputsFinal(true),
31
+ );
32
+ }
33
+ }
34
+
35
+ /** The stop-rule clause of one entry, or undefined when the entry sends none: a `never` rule,
36
+ * or a `none` declaration. */
37
+ export function finalityStopRuleClause(entry: FinalityEntry): Filter | undefined {
38
+ if (entry.rule === "never") return undefined;
39
+ const declared = entry.stopRule ?? "exact";
40
+ if (typeof declared === "object" && "none" in declared) return undefined;
41
+ if (declared !== "exact") return conditionFilter(typeFilter(entry.match), declared.approx);
42
+ if (typeof entry.rule === "object") return undefined;
43
+ return conditionFilter(typeFilter(entry.match), entry.rule);
44
+ }
45
+
46
+ /**
47
+ * Traversal stop rules for a finality table: the OR of every entry's declared clause. An
48
+ * approximated clause may stop before the table calls a resource final; an entry with no clause
49
+ * contributes nothing. The backend applies stop rules on walks without a change token only.
50
+ */
51
+ export function finalityStopRules(table: FinalityTable): Filter {
52
+ const clauses: Filter[] = [];
53
+ for (const e of table.entries) {
54
+ const clause = finalityStopRuleClause(e);
55
+ if (clause !== undefined) clauses.push(clause);
56
+ }
57
+ return treeFilter.or(...clauses);
58
+ }
@@ -17,10 +17,10 @@ import {
17
17
  parseSignedResourceId,
18
18
  Pl,
19
19
  resourceIdToString,
20
- ResourceTypeName,
21
- ResourceTypePrefix,
22
20
  treeFilter,
21
+ TreeFinality,
23
22
  } from "@milaboratories/pl-client";
23
+ import { finalityStopRules } from "./finality_stop_rules";
24
24
  import type { ComputableStableDefined, ComputableValueOrErrors } from "@milaboratories/computable";
25
25
  import { Computable } from "@milaboratories/computable";
26
26
  import { projectOverview } from "./project_overview";
@@ -1018,148 +1018,10 @@ export function projectTreeFieldFilter(): Filter {
1018
1018
  );
1019
1019
  }
1020
1020
 
1021
- /**
1022
- * Stop-rules for the ResourceTree backend path.
1023
- *
1024
- * Mirrors every case of DefaultFinalResourceDataPredicate in the same order.
1025
- * The mapping from BFS predicate logic to backend filter conditions:
1026
- *
1027
- * BFS predicate always true
1028
- * → no readyOrDuplicateOrError guard; backend stops traversal unconditionally.
1029
- *
1030
- * BFS predicate: readyOrDuplicateOrError(r)
1031
- * → readyOrDuplicateOrError(): stops when resource_ready_for_calculation,
1032
- * is_duplicate, or has_errors is true — exactly mirroring the BFS predicate.
1033
- *
1034
- * BFS predicate: readyAndHasAllOutputsFilled(r)
1035
- * → isFinal(true) + allOutputsFinal(true).
1036
- *
1037
- * BFS predicate always false (UserProject, Projects, ClientRoot)
1038
- * → no entry here; traversal always continues into them.
1039
- */
1021
+ /** Stop rules for the project tree's ResourceTree walks: {@link TreeFinality}'s declared clauses,
1022
+ * which may stop before the predicate calls a resource final where an entry declares so. */
1040
1023
  export function projectTreeTraverseStopRules(): Filter {
1041
- return treeFilter.or(
1042
- // BFS: readyOrDuplicateOrError(r) AND (fields===undefined OR error OR stream.value===downloadable.value).
1043
- // datactl sets stream field to point at the same resource as downloadable once processing
1044
- // is complete, so stream.value===downloadable.value is the "done" signal.
1045
- treeFilter.and(
1046
- treeFilter.resourceTypeEq(ResourceTypeName.StreamManager),
1047
- treeFilter.readyOrDuplicateOrError(),
1048
- ),
1049
- // BFS: readyOrDuplicateOrError(r)
1050
- treeFilter.and(
1051
- treeFilter.resourceTypeEq(ResourceTypeName.StdMap),
1052
- treeFilter.readyOrDuplicateOrError(),
1053
- ),
1054
- treeFilter.and(
1055
- treeFilter.resourceTypeEq(ResourceTypeName.StdMapSlash),
1056
- treeFilter.readyOrDuplicateOrError(),
1057
- ),
1058
- treeFilter.and(
1059
- treeFilter.resourceTypeEq(ResourceTypeName.EphStdMap),
1060
- treeFilter.readyOrDuplicateOrError(),
1061
- ),
1062
- treeFilter.and(
1063
- treeFilter.resourceTypeEq(ResourceTypeName.PFrame),
1064
- treeFilter.readyOrDuplicateOrError(),
1065
- ),
1066
- treeFilter.and(
1067
- treeFilter.resourceTypeEq(ResourceTypeName.ParquetChunk),
1068
- treeFilter.readyOrDuplicateOrError(),
1069
- ),
1070
- treeFilter.and(
1071
- treeFilter.resourceTypeEq(ResourceTypeName.BContext),
1072
- treeFilter.readyOrDuplicateOrError(),
1073
- ),
1074
- treeFilter.and(
1075
- treeFilter.resourceTypeEq(ResourceTypeName.BlockPackCustom),
1076
- treeFilter.readyOrDuplicateOrError(),
1077
- ),
1078
- treeFilter.and(
1079
- treeFilter.resourceTypeEq(ResourceTypeName.BinaryMap),
1080
- treeFilter.readyOrDuplicateOrError(),
1081
- ),
1082
- treeFilter.and(
1083
- treeFilter.resourceTypeEq(ResourceTypeName.BinaryValue),
1084
- treeFilter.readyOrDuplicateOrError(),
1085
- ),
1086
- treeFilter.and(
1087
- treeFilter.resourceTypeEq(ResourceTypeName.BlobMap),
1088
- treeFilter.readyOrDuplicateOrError(),
1089
- ),
1090
- treeFilter.and(
1091
- treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingle),
1092
- treeFilter.readyOrDuplicateOrError(),
1093
- ),
1094
- treeFilter.and(
1095
- treeFilter.resourceTypeEq(ResourceTypeName.BResolveSingleNoResult),
1096
- treeFilter.readyOrDuplicateOrError(),
1097
- ),
1098
- treeFilter.and(
1099
- treeFilter.resourceTypeEq(ResourceTypeName.BQueryResult),
1100
- treeFilter.readyOrDuplicateOrError(),
1101
- ),
1102
- treeFilter.and(
1103
- treeFilter.resourceTypeEq(ResourceTypeName.TengoTemplate),
1104
- treeFilter.readyOrDuplicateOrError(),
1105
- ),
1106
- treeFilter.and(
1107
- treeFilter.resourceTypeEq(ResourceTypeName.TengoLib),
1108
- treeFilter.readyOrDuplicateOrError(),
1109
- ),
1110
- treeFilter.and(
1111
- treeFilter.resourceTypeEq(ResourceTypeName.SoftwareInfo),
1112
- treeFilter.readyOrDuplicateOrError(),
1113
- ),
1114
- treeFilter.and(
1115
- treeFilter.resourceTypeEq(ResourceTypeName.Dummy),
1116
- treeFilter.readyOrDuplicateOrError(),
1117
- ),
1118
- // BFS: r.type.version === "1" → always true → no state guard needed
1119
- treeFilter.resourceTypeEq(ResourceTypeName.JsonResourceError),
1120
- // BFS: return true (unconditionally) → no readyOrDuplicateOrError guard needed
1121
- treeFilter.resourceTypeEq(ResourceTypeName.JsonObject),
1122
- treeFilter.resourceTypeEq(ResourceTypeName.JsonGzObject),
1123
- treeFilter.resourceTypeEq(ResourceTypeName.JsonString),
1124
- treeFilter.resourceTypeEq(ResourceTypeName.JsonArray),
1125
- treeFilter.resourceTypeEq(ResourceTypeName.JsonNumber),
1126
- treeFilter.resourceTypeEq(ResourceTypeName.BContextEnd),
1127
- treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromUrl),
1128
- treeFilter.resourceTypeEq(ResourceTypeName.FrontendFromFolder),
1129
- treeFilter.resourceTypeEq(ResourceTypeName.BObjectSpec),
1130
- treeFilter.resourceTypeEq(ResourceTypeName.Blob),
1131
- treeFilter.resourceTypeEq(ResourceTypeName.Null),
1132
- treeFilter.resourceTypeEq(ResourceTypeName.Binary),
1133
- treeFilter.resourceTypeEq(ResourceTypeName.LSProvider),
1134
- treeFilter.resourceTypeEq(ResourceTypeName.WorkingDirectory),
1135
- // BFS default branch: startsWith prefix → return true → no guard needed
1136
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.Blob),
1137
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.LS),
1138
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.WorkingDirectory),
1139
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StorageSpaceAllocation),
1140
- // BFS default branch: readyAndHasAllOutputsFilled → readyOrDuplicateOrError() + outputsLocked(true) + allOutputsFinal(true)
1141
- treeFilter.and(
1142
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobUpload),
1143
- treeFilter.readyOrDuplicateOrError(),
1144
- treeFilter.outputsLocked(true),
1145
- treeFilter.allOutputsFinal(true),
1146
- ),
1147
- treeFilter.and(
1148
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.BlobIndex),
1149
- treeFilter.readyOrDuplicateOrError(),
1150
- treeFilter.outputsLocked(true),
1151
- treeFilter.allOutputsFinal(true),
1152
- ),
1153
- // BFS default branch: readyOrDuplicateOrError
1154
- treeFilter.and(
1155
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.PColumnData),
1156
- treeFilter.readyOrDuplicateOrError(),
1157
- ),
1158
- treeFilter.and(
1159
- treeFilter.resourceTypeMatch("^" + ResourceTypePrefix.StreamWorkdir),
1160
- treeFilter.readyOrDuplicateOrError(),
1161
- ),
1162
- );
1024
+ return finalityStopRules(TreeFinality);
1163
1025
  }
1164
1026
 
1165
1027
  /** Returns true if sdk version of the block is old and we need to convert