@milaboratories/pl-middle-layer 1.73.3 → 1.73.5
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/middle_layer/build_stamp.cjs +1 -1
- package/dist/middle_layer/build_stamp.js +1 -1
- package/dist/middle_layer/finality_stop_rules.cjs +43 -0
- package/dist/middle_layer/finality_stop_rules.cjs.map +1 -0
- package/dist/middle_layer/finality_stop_rules.js +42 -0
- package/dist/middle_layer/finality_stop_rules.js.map +1 -0
- package/dist/middle_layer/project.cjs +4 -20
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.js +5 -21
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/sharing_list.cjs.map +1 -1
- package/dist/middle_layer/sharing_list.js.map +1 -1
- package/package.json +18 -18
- package/src/middle_layer/finality_stop_rules.test.ts +386 -0
- package/src/middle_layer/finality_stop_rules.ts +58 -0
- package/src/middle_layer/project.ts +5 -143
- package/src/middle_layer/project_pruning.test.ts +59 -16
- package/src/middle_layer/sharing_list.ts +2 -2
|
@@ -18,6 +18,7 @@ import { projectsListFieldFilter, ProjectsListTreePruningFunction } from "./proj
|
|
|
18
18
|
import type { Filter } from "@milaboratories/pl-client";
|
|
19
19
|
import {
|
|
20
20
|
DefaultFinalResourceDataPredicate,
|
|
21
|
+
TreeFinality,
|
|
21
22
|
FilterOperatorType,
|
|
22
23
|
FilterProperty,
|
|
23
24
|
NullSignedResourceId,
|
|
@@ -65,7 +66,7 @@ function makeReadyResource(typeName: string, version = "1"): ExtendedResourceDat
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
|
-
* Build a resource that satisfies
|
|
69
|
+
* Build a resource that satisfies readyAndAllOutputsFilled:
|
|
69
70
|
* resourceReady=true, outputsLocked=true, one field with valueIsFinal=true.
|
|
70
71
|
*/
|
|
71
72
|
function makeAllOutputsFinalResource(typeName: string): ExtendedResourceData {
|
|
@@ -285,6 +286,23 @@ describe("§4.1 projects-list pruning parity", () => {
|
|
|
285
286
|
|
|
286
287
|
// ── §4.2 — traverseStopRules coverage ────────────────────────────────────────
|
|
287
288
|
|
|
289
|
+
describe("the pruning the finality table relies on", () => {
|
|
290
|
+
for (const e of TreeFinality.entries) {
|
|
291
|
+
if (e.requiresPruning === undefined) continue;
|
|
292
|
+
const { type, fields } = e.requiresPruning;
|
|
293
|
+
it(`${type}: projectTreePruning and projectTreeFieldFilter drop ${fields === "all" ? "every field" : fields.join(", ")}`, () => {
|
|
294
|
+
const names = fields === "all" ? ["incarnation", "any"] : [...fields];
|
|
295
|
+
const r = makeResource(type, names);
|
|
296
|
+
const kept = projectTreePruning(noopLogger)(r).map((f) => f.name);
|
|
297
|
+
const keptByFilter = evaluateFieldFilter(projectTreeFieldFilter(), r);
|
|
298
|
+
for (const name of names) {
|
|
299
|
+
expect(kept).not.toContain(name);
|
|
300
|
+
expect(keptByFilter).not.toContain(name);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
288
306
|
describe("§4.2 projectTreeTraverseStopRules", () => {
|
|
289
307
|
const rule = projectTreeTraverseStopRules();
|
|
290
308
|
|
|
@@ -301,8 +319,11 @@ describe("§4.2 projectTreeTraverseStopRules", () => {
|
|
|
301
319
|
"BObjectSpec",
|
|
302
320
|
"BContextEnd",
|
|
303
321
|
"Null",
|
|
304
|
-
"LSProvider",
|
|
305
322
|
"Blob",
|
|
323
|
+
"Frontend/FromLocalTgz",
|
|
324
|
+
"json/bool",
|
|
325
|
+
"json/null",
|
|
326
|
+
"json/errorTrace",
|
|
306
327
|
];
|
|
307
328
|
for (const t of alwaysTerminalTypes) {
|
|
308
329
|
it(`always-terminal: ${t}`, () => {
|
|
@@ -436,15 +457,14 @@ describe("§4.2 projectTreeTraverseStopRules", () => {
|
|
|
436
457
|
// 1. DefaultFinalResourceDataPredicate(resource) === expectedPredicate
|
|
437
458
|
// 2. evaluateStopRule(rule, flags) === expectedStop
|
|
438
459
|
//
|
|
439
|
-
// `flags`
|
|
460
|
+
// `flags` compress several independent backend properties into two:
|
|
440
461
|
// - isFinal = resource is "ready/error/duplicate" (or always-final type)
|
|
441
462
|
// - allOutputsFinal = outputsLocked && every field value is final
|
|
442
463
|
//
|
|
443
464
|
// The parity contract: when the predicate says "final" AND the type is one the
|
|
444
|
-
// stop rule covers, the stop rule must also fire.
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
// documented as intentional gaps below.
|
|
465
|
+
// stop rule covers, the stop rule must also fire. Where the backend's filter
|
|
466
|
+
// differs from the predicate, finality_stop_rules.test.ts checks it against the
|
|
467
|
+
// backend's semantics.
|
|
448
468
|
|
|
449
469
|
describe("§4.3 final-predicate parity: DefaultFinalResourceDataPredicate ⇄ projectTreeTraverseStopRules", () => {
|
|
450
470
|
const rule = projectTreeTraverseStopRules();
|
|
@@ -463,8 +483,11 @@ describe("§4.3 final-predicate parity: DefaultFinalResourceDataPredicate ⇄ pr
|
|
|
463
483
|
{ typeName: "BObjectSpec" },
|
|
464
484
|
{ typeName: "BContextEnd" },
|
|
465
485
|
{ typeName: "Null" },
|
|
466
|
-
{ typeName: "LSProvider" },
|
|
467
486
|
{ typeName: "Blob" },
|
|
487
|
+
{ typeName: "Frontend/FromLocalTgz" },
|
|
488
|
+
{ typeName: "json/bool" },
|
|
489
|
+
{ typeName: "json/null" },
|
|
490
|
+
{ typeName: "json/errorTrace" },
|
|
468
491
|
{ typeName: "Blob/v2" },
|
|
469
492
|
{ typeName: "LS/remote" },
|
|
470
493
|
{ typeName: "WorkingDirectory/1" },
|
|
@@ -551,7 +574,7 @@ describe("§4.3 final-predicate parity: DefaultFinalResourceDataPredicate ⇄ pr
|
|
|
551
574
|
);
|
|
552
575
|
});
|
|
553
576
|
|
|
554
|
-
// ── Group C: isFinal+allOutputsFinal-conditional —
|
|
577
|
+
// ── Group C: isFinal+allOutputsFinal-conditional — readyAndAllOutputsFilled ──
|
|
555
578
|
|
|
556
579
|
it("BlobUpload/v1 — all outputs final: predicate=true, stop fires with isFinal+allOutputsFinal=true", () => {
|
|
557
580
|
const r = makeAllOutputsFinalResource("BlobUpload/v1");
|
|
@@ -615,15 +638,13 @@ describe("§4.3 final-predicate parity: DefaultFinalResourceDataPredicate ⇄ pr
|
|
|
615
638
|
|
|
616
639
|
// ── Group E: never-final types — predicate=false, stop rule does not fire ──
|
|
617
640
|
//
|
|
618
|
-
//
|
|
619
|
-
// (UserProject, Projects, ClientRoot) and the `default` else-branch for unknown
|
|
620
|
-
// types. Both paths agree: not final, traversal continues.
|
|
641
|
+
// Types declared `never`, and types no entry matches: not final, and no stop clause.
|
|
621
642
|
|
|
622
643
|
const neverFinalCases: Array<{ typeName: string; label: string }> = [
|
|
623
|
-
{ typeName: "UserProject", label: "
|
|
624
|
-
{ typeName: "Projects", label: "
|
|
625
|
-
{ typeName: "ClientRoot", label: "
|
|
626
|
-
{ typeName: "SomeUnknownType", label: "
|
|
644
|
+
{ typeName: "UserProject", label: "declared never" },
|
|
645
|
+
{ typeName: "Projects", label: "declared never" },
|
|
646
|
+
{ typeName: "ClientRoot", label: "declared never" },
|
|
647
|
+
{ typeName: "SomeUnknownType", label: "unmatched type" },
|
|
627
648
|
];
|
|
628
649
|
|
|
629
650
|
for (const { typeName, label } of neverFinalCases) {
|
|
@@ -633,4 +654,26 @@ describe("§4.3 final-predicate parity: DefaultFinalResourceDataPredicate ⇄ pr
|
|
|
633
654
|
expect(evaluateStopRule(rule, { resourceType: typeName, isFinal: false })).toBe(false);
|
|
634
655
|
});
|
|
635
656
|
}
|
|
657
|
+
|
|
658
|
+
it("LSProvider: never final, stop does NOT fire even when ready", () => {
|
|
659
|
+
expect(DefaultFinalResourceDataPredicate(makeReadyResource("LSProvider"))).toBe(false);
|
|
660
|
+
expect(
|
|
661
|
+
evaluateStopRule(rule, { resourceType: "LSProvider", isFinal: true, allOutputsFinal: true }),
|
|
662
|
+
).toBe(false);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// For these ready fixtures, locked outputs and settled fields satisfy both checks.
|
|
666
|
+
for (const typeName of ["BResolveSingle", "BResolveChoice", "BlobCopy/aToB"]) {
|
|
667
|
+
it(`${typeName}: final with outputs locked and settled, stop fires only then`, () => {
|
|
668
|
+
expect(DefaultFinalResourceDataPredicate(makeAllOutputsFinalResource(typeName))).toBe(true);
|
|
669
|
+
// a synthetic unlocked snapshot: schema-created resolvers and copies have locked outputs
|
|
670
|
+
expect(DefaultFinalResourceDataPredicate(makeReadyResource(typeName))).toBe(false);
|
|
671
|
+
expect(
|
|
672
|
+
evaluateStopRule(rule, { resourceType: typeName, isFinal: true, allOutputsFinal: true }),
|
|
673
|
+
).toBe(true);
|
|
674
|
+
expect(
|
|
675
|
+
evaluateStopRule(rule, { resourceType: typeName, isFinal: true, allOutputsFinal: false }),
|
|
676
|
+
).toBe(false);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
636
679
|
});
|
|
@@ -325,8 +325,8 @@ const SharingOutboxPruningFunction: PruningFunction = (resource) => {
|
|
|
325
325
|
|
|
326
326
|
// Server-side traversal scope (modern resourceTree path). Pruning is client-side ONLY and does
|
|
327
327
|
// NOT stop the backend walk — without a fieldFilter the backend descends through the envelope's
|
|
328
|
-
// project/{uuid} snapshots into the whole project graph (StreamManager etc.),
|
|
329
|
-
//
|
|
328
|
+
// project/{uuid} snapshots into the whole project graph (StreamManager etc.), which the tree would
|
|
329
|
+
// then hold, with its fields pruned to [], and re-poll for nothing. Following fields only FROM the outbox
|
|
330
330
|
// and the envelope stops the walk at the project snapshots (UserProject), which we never traverse.
|
|
331
331
|
const SharingOutboxFieldFilter: Filter = treeFilter.or(
|
|
332
332
|
treeFilter.resourceTypeEq(SharingOutboxResourceType.name),
|