@rulvar/plan 1.34.0 → 1.36.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/index.d.ts +10 -1
- package/dist/index.js +77 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -503,7 +503,16 @@ declare function rebasePlanRevision(request: PlanReviseRequest, context: RebaseC
|
|
|
503
503
|
//#region src/guards.d.ts
|
|
504
504
|
/** RevisionGuards configuration. */
|
|
505
505
|
interface RevisionGuardsOptions {
|
|
506
|
-
/**
|
|
506
|
+
/**
|
|
507
|
+
* Default 'finish-with-partial'; the chain is non-HITL and
|
|
508
|
+
* terminating. 'reject-revision' and 'finish-with-partial' freeze the
|
|
509
|
+
* plan and steer the orchestrator to finish with the partial result
|
|
510
|
+
* (run outcome 'ok'). 'fail-run' closes the run as a FAILURE: after
|
|
511
|
+
* the journaled guard verdict the PlanRunner terminates the
|
|
512
|
+
* orchestration with FailRunError (code 'fail_run', data.source
|
|
513
|
+
* 'plan_guards', data.verdictRef), no further model turn is consulted,
|
|
514
|
+
* and resume rolls the same failure forward from the verdict entry.
|
|
515
|
+
*/
|
|
507
516
|
fallback?: "reject-revision" | "finish-with-partial" | "fail-run";
|
|
508
517
|
/** Default 3 consecutive fully-dropped revisions. */
|
|
509
518
|
droppedRevisionLimit?: number;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdmissionRejectedError, BudgetExhaustedError, CURRENT_HASH_VERSION, ConfigError, DedupIndex, InMemoryStore, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LineageIndex, PlanInvariantError, ROOT_ACCOUNT, ReplayPlanHashMismatch, TerminationAccount, approachSigCoarse, buildTerminationInitValue, canonicalIsolationTag, canonicalizeLadder, checkpointRefFor, countsAgainstLimit, createEngine, defineWorkflow, deriverV2, evaluateReuse, foldTermination, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, makeOrchestratorWorkflow, nodeLinkKey, normalizeApproachTag, orchestrate, planNodeScope, profileRegistrySnapshotHash, tool, validateTerminationLimits } from "@rulvar/core";
|
|
1
|
+
import { AdmissionRejectedError, BudgetExhaustedError, CURRENT_HASH_VERSION, ConfigError, DedupIndex, FailRunError, InMemoryStore, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LineageIndex, PlanInvariantError, ROOT_ACCOUNT, ReplayPlanHashMismatch, TerminationAccount, approachSigCoarse, buildTerminationInitValue, canonicalIsolationTag, canonicalizeLadder, checkpointRefFor, countsAgainstLimit, createEngine, defineWorkflow, deriverV2, evaluateReuse, foldTermination, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, makeOrchestratorWorkflow, nodeLinkKey, normalizeApproachTag, orchestrate, planNodeScope, profileRegistrySnapshotHash, tool, validateTerminationLimits } from "@rulvar/core";
|
|
2
2
|
//#region src/plan-state.ts
|
|
3
3
|
/**
|
|
4
4
|
* Plan scope substrate (M7-T01): TaskPlan as engine-owned typed data, the
|
|
@@ -923,6 +923,49 @@ function cascadeOf(plan, root) {
|
|
|
923
923
|
}
|
|
924
924
|
//#endregion
|
|
925
925
|
//#region src/guards.ts
|
|
926
|
+
/**
|
|
927
|
+
* RevisionGuards, the oscillation detector, and hysteresis (M7-T06).
|
|
928
|
+
*
|
|
929
|
+
* Guide: https://docs.rulvar.com/guide/adaptive-orchestration
|
|
930
|
+
* (DEF-8; DEF-2/DEF-5 interactions). The guards are non-HITL and
|
|
931
|
+
* terminating: a human in the loop is NEVER required for a run to end.
|
|
932
|
+
* Every guard verdict is a decision entry written strictly BEFORE its
|
|
933
|
+
* effects; verdict state is a pure fold of those entries, so replay
|
|
934
|
+
* re-derives the same freezes with zero live calls.
|
|
935
|
+
*
|
|
936
|
+
* Three detectors:
|
|
937
|
+
*
|
|
938
|
+
* - droppedRevisionStreak: `effectiveDroppedStreak` (the hashed counter
|
|
939
|
+
* plus trailing bad_base entries) reaching `droppedRevisionLimit`
|
|
940
|
+
* (default 3) fires the configured terminating fallback
|
|
941
|
+
* (reject-revision -> finish-with-partial -> fail-run; default
|
|
942
|
+
* finish-with-partial).
|
|
943
|
+
* - Oscillation: keyed on approachSigCoarse ACROSS LogicalTaskId
|
|
944
|
+
* boundaries (a content-identical rebirth under a fresh lineage root
|
|
945
|
+
* is still flagged): a re-add after a severing cancel of the same
|
|
946
|
+
* coarse signature counts one oscillation; at the per-key limit
|
|
947
|
+
* further re-adds FREEZE (the per-SpawnKey osc_guard of DEF-5 lands
|
|
948
|
+
* in M7-T07 and keys the DedupIndex instead).
|
|
949
|
+
* - Stall replans: hard-bounded per run; the streak already excludes
|
|
950
|
+
* transient and environment classes.
|
|
951
|
+
*
|
|
952
|
+
* Hysteresis on almost-done nodes is structural: park and cancel against
|
|
953
|
+
* RUNNING nodes only ever land as boundary flags (requestOnly), so a
|
|
954
|
+
* nearly-finished child is never killed mid-turn, and park/unpark churn
|
|
955
|
+
* feeds the oscillation counter.
|
|
956
|
+
*/
|
|
957
|
+
/**
|
|
958
|
+
* Local mirror of the core numeric-intake idiom (v1.34.0 review P2-3):
|
|
959
|
+
* guard limits are compared with `<` and `!==`, and every comparison
|
|
960
|
+
* with NaN is false, so an unvalidated NaN limit made the dropped and
|
|
961
|
+
* oscillation guards trip immediately and the stall cap never trip.
|
|
962
|
+
*/
|
|
963
|
+
function requireCount(value, site, min) {
|
|
964
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min) throw new ConfigError(`${site} must be ${min === 1 ? "a positive integer" : "a nonnegative integer"}; got ${String(value)}`);
|
|
965
|
+
}
|
|
966
|
+
function requireGuardFraction(value, site) {
|
|
967
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1) throw new ConfigError(`${site} must be a fraction in (0, 1]; got ${String(value)}`);
|
|
968
|
+
}
|
|
926
969
|
/** Appendix A: osc_guard reject threshold per key (shared default). */
|
|
927
970
|
const DEFAULT_MAX_OSCILLATIONS_PER_KEY = 2;
|
|
928
971
|
/** The hard per-run stall replan bound. */
|
|
@@ -947,6 +990,10 @@ var RevisionGuards = class {
|
|
|
947
990
|
frozen = /* @__PURE__ */ new Set();
|
|
948
991
|
stallReplans = 0;
|
|
949
992
|
constructor(options) {
|
|
993
|
+
if (options?.droppedRevisionLimit !== void 0) requireCount(options.droppedRevisionLimit, "RevisionGuards droppedRevisionLimit", 1);
|
|
994
|
+
if (options?.maxOscillationsPerKey !== void 0) requireCount(options.maxOscillationsPerKey, "RevisionGuards maxOscillationsPerKey", 1);
|
|
995
|
+
if (options?.stallReplanCap !== void 0) requireCount(options.stallReplanCap, "RevisionGuards stallReplanCap", 0);
|
|
996
|
+
if (options?.maxAbandonedNetUsdFraction !== void 0) requireGuardFraction(options.maxAbandonedNetUsdFraction, "RevisionGuards maxAbandonedNetUsdFraction");
|
|
950
997
|
this.fallback = options?.fallback ?? "finish-with-partial";
|
|
951
998
|
this.droppedRevisionLimit = options?.droppedRevisionLimit ?? 3;
|
|
952
999
|
this.maxOscillationsPerKey = options?.maxOscillationsPerKey ?? 2;
|
|
@@ -1982,6 +2029,26 @@ function planRunner(options) {
|
|
|
1982
2029
|
guard: verdict.guard,
|
|
1983
2030
|
...verdict.approachSigCoarse === void 0 ? {} : { approachSigCoarse: verdict.approachSigCoarse }
|
|
1984
2031
|
});
|
|
2032
|
+
/**
|
|
2033
|
+
* The engaged 'fail-run' fallback closes the run as a typed failure
|
|
2034
|
+
* (v1.35.0 review P2-1: the verdict used to journal the fallback and
|
|
2035
|
+
* then behave exactly like finish-with-partial). The journaled verdict
|
|
2036
|
+
* is the decision; terminate() aborts the loop and the orchestrate
|
|
2037
|
+
* settle boundary rethrows the failure, so a crash after the verdict
|
|
2038
|
+
* rolls the SAME outcome forward from the journal on resume, with no
|
|
2039
|
+
* second decision and no model call.
|
|
2040
|
+
*/
|
|
2041
|
+
let failRunSignalled = false;
|
|
2042
|
+
const enforceEngagedFailRun = () => {
|
|
2043
|
+
if (failRunSignalled || guards.state.engaged !== "fail-run") return;
|
|
2044
|
+
failRunSignalled = true;
|
|
2045
|
+
const verdictEntry = io.snapshot().find((entry) => entry.scope === planScope && entry.kind === "decision" && entry.value?.decisionType === "guard-verdict" && entry.value.fallback === "fail-run");
|
|
2046
|
+
const guardName = (verdictEntry?.value)?.guard;
|
|
2047
|
+
io.terminate?.(new FailRunError(`revision guards engaged (${guardName ?? "guard"}) with fallback 'fail-run': the plan is closed for adaptation and the run fails instead of finishing with the partial result`, { data: {
|
|
2048
|
+
source: "plan_guards",
|
|
2049
|
+
...verdictEntry === void 0 ? {} : { verdictRef: verdictEntry.seq }
|
|
2050
|
+
} }));
|
|
2051
|
+
};
|
|
1985
2052
|
/** Guard verdict state is a pure fold of journaled verdicts (M7-T06). */
|
|
1986
2053
|
const absorbGuardVerdicts = () => {
|
|
1987
2054
|
for (const entry of io.snapshot()) {
|
|
@@ -1993,6 +2060,7 @@ function planRunner(options) {
|
|
|
1993
2060
|
}
|
|
1994
2061
|
guardCursor = entry.seq;
|
|
1995
2062
|
}
|
|
2063
|
+
enforceEngagedFailRun();
|
|
1996
2064
|
};
|
|
1997
2065
|
/**
|
|
1998
2066
|
* Appends fold-fired verdicts strictly BEFORE their effects; a
|
|
@@ -2018,6 +2086,7 @@ function planRunner(options) {
|
|
|
2018
2086
|
limit: verdict.oscillationCount ?? 0
|
|
2019
2087
|
});
|
|
2020
2088
|
}
|
|
2089
|
+
enforceEngagedFailRun();
|
|
2021
2090
|
};
|
|
2022
2091
|
/** The coarse approach signature of a spec (mirrors admission's inputs). */
|
|
2023
2092
|
const coarseOf = (spec) => approachSigCoarse({
|
|
@@ -3293,7 +3362,13 @@ function planRunner(options) {
|
|
|
3293
3362
|
await io.flush();
|
|
3294
3363
|
absorbPlan();
|
|
3295
3364
|
absorbGuardVerdicts();
|
|
3296
|
-
if (guards.revisionsRejected)
|
|
3365
|
+
if (guards.revisionsRejected) {
|
|
3366
|
+
if (guards.state.engaged === "fail-run") {
|
|
3367
|
+
enforceEngagedFailRun();
|
|
3368
|
+
throw new FailRunError("revision guards engaged with fallback 'fail-run': the plan is closed and the run fails instead of finishing with the partial result", { data: { source: "plan_guards" } });
|
|
3369
|
+
}
|
|
3370
|
+
throw new ConfigError(`revision guards engaged (${guards.state.engaged ?? "unknown"}): the plan is closed for adaptation; call finish with the partial result`);
|
|
3371
|
+
}
|
|
3297
3372
|
if (vocabulary !== void 0) {
|
|
3298
3373
|
for (const op of request.ops) if (op.op === "add_task") {
|
|
3299
3374
|
const tag = op.approach ?? op.spec.approach;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/plan",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"description": "Rulvar adaptive orchestration extension: PlanRunner, RunLedger, escalation extensions, ModelLadder configuration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.36.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.0",
|
|
29
29
|
"tsdown": "^0.22.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/store-sqlite": "1.
|
|
31
|
+
"@rulvar/store-sqlite": "1.36.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|