@nexus-cortex/core 4.124.11 → 4.124.12

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.
Files changed (33) hide show
  1. package/.env.defaults +25 -0
  2. package/dist/config/RuntimeConfigRegistry.d.ts.map +1 -1
  3. package/dist/config/RuntimeConfigRegistry.js +5 -0
  4. package/dist/config/RuntimeConfigRegistry.js.map +1 -1
  5. package/dist/config/SettingsLoader.d.ts.map +1 -1
  6. package/dist/config/SettingsLoader.js +5 -0
  7. package/dist/config/SettingsLoader.js.map +1 -1
  8. package/dist/config/SettingsSchema.d.ts +5 -0
  9. package/dist/config/SettingsSchema.d.ts.map +1 -1
  10. package/dist/config/SettingsSchema.js +13 -0
  11. package/dist/config/SettingsSchema.js.map +1 -1
  12. package/dist/config/effectiveConfig.d.ts.map +1 -1
  13. package/dist/config/effectiveConfig.js +5 -0
  14. package/dist/config/effectiveConfig.js.map +1 -1
  15. package/dist/middleware/HelperModelMiddleware.d.ts +11 -0
  16. package/dist/middleware/HelperModelMiddleware.d.ts.map +1 -1
  17. package/dist/middleware/HelperModelMiddleware.js +18 -0
  18. package/dist/middleware/HelperModelMiddleware.js.map +1 -1
  19. package/dist/orchestrator/CortexOrchestrator.d.ts +12 -0
  20. package/dist/orchestrator/CortexOrchestrator.d.ts.map +1 -1
  21. package/dist/orchestrator/CortexOrchestrator.js +127 -3
  22. package/dist/orchestrator/CortexOrchestrator.js.map +1 -1
  23. package/dist/training/DecisionStore.d.ts +1 -1
  24. package/dist/training/DecisionStore.d.ts.map +1 -1
  25. package/dist/training/endTurnResolver.d.ts +9 -0
  26. package/dist/training/endTurnResolver.d.ts.map +1 -1
  27. package/dist/training/endTurnResolver.js +6 -1
  28. package/dist/training/endTurnResolver.js.map +1 -1
  29. package/dist/training/requirementLedger.d.ts +84 -0
  30. package/dist/training/requirementLedger.d.ts.map +1 -0
  31. package/dist/training/requirementLedger.js +155 -0
  32. package/dist/training/requirementLedger.js.map +1 -0
  33. package/package.json +3 -3
@@ -23,6 +23,7 @@ import { resolveEndTurnResolverConfig, parseResolverVerdict, effectiveMaxRejects
23
23
  import { jevAvailable, jevNoul, buildGapHoldState, GAP_HOLD_QUESTIONS } from '../training/jevGate.js'; // R173b
24
24
  import { resolveFrameConfig, FRAME_CONTRACT, FRAME_TOOL_NAME } from '../frames/frameConfig.js'; // R179
25
25
  import { FrameRunner } from '../frames/frameRunner.js'; // R179
26
+ import { parseRequirementLedger, initLedger, updateLedger, ledgerCounts, ledgerHoldable, formatLedgerForWriter, formatLedgerForJudge, formatLedgerHoldMessage, buildLedgerJevState, buildLedgerJevQuestions, applyLedgerJevAnswers } from '../training/requirementLedger.js'; // R187
26
27
  import { isValueShapedTask, parseDerivationReply, methodsDiffer, parseValueLines, extractNumbers, reconcile, buildDerivationHoldMessage, isDerivationCommandAllowed, checkRunPassed, checkRunBody } from '../training/independentDerivation.js'; // R176
27
28
  import { resolveDeadlineExitConfig, deadlineExitCallBudget, parseDeadlineExitVerdict } from '../training/deadlineExitMentor.js';
28
29
  import { isTaskShaped } from './requirementsVerification.js';
@@ -249,6 +250,10 @@ export class CortexOrchestrator {
249
250
  specChecks = null; // R174: blind spec-derived checks authored for this turn (null = not yet)
250
251
  specChecksMeta = { genLatencyMs: 0, refused: 0, raw: 0 }; // R174
251
252
  specChecksPromise = null; // R174b: authored at lift in the background (CORTEX_JUDGE_SPEC_TESTS_AT=lift)
253
+ reqLedger = null; // R187: the stated-requirement ledger for this turn (null = not yet authored)
254
+ reqLedgerPromise = null; // R187: authored at lift in the background
255
+ reqLedgerHolds = 0; // R187: ledger holds used this turn
256
+ reqLedgerMeta = { genLatencyMs: 0, raw: 0, refused: 0 }; // R187
252
257
  specFailHistory = new Map(); // R174b: consecutive identical spec failures
253
258
  lastHoldMs = 0; // R173c: wall clock of the last hold/veto this turn
254
259
  derivationHolds = 0; // R176: independent-derivation holds this turn (max 1)
@@ -542,6 +547,7 @@ export class CortexOrchestrator {
542
547
  void store.recordEvent({
543
548
  sessionId, kind: 'lift_plan', detail: { fired: true, planChars: 0, empty: true, ...r171, mentor: this.mentorWire('lift-plan', resolveLiftPlanConfig().effort, resolveLiftPlanConfig().outputBudgetTokens) },
544
549
  }).catch(() => { });
550
+ await this.deliverRequirementLedgerToWriter(lastMsg); // R187: the ledger rides even when the plan is empty
545
551
  return; // fail-open: proceed without a plan
546
552
  }
547
553
  const retire = /\bRETIRE\b/i.test(plan);
@@ -564,6 +570,7 @@ export class CortexOrchestrator {
564
570
  if (this.config.debug) {
565
571
  console.log(`[LiftPlan] plan delivered at lift (${plan.length} chars, retire=${retire}, criteria=${criteriaStated})`);
566
572
  }
573
+ await this.deliverRequirementLedgerToWriter(lastMsg); // R187
567
574
  }
568
575
  catch (e) {
569
576
  if (store)
@@ -574,6 +581,26 @@ export class CortexOrchestrator {
574
581
  console.warn(`[LiftPlan] delivery failed (continuing without): ${e?.message ?? e}`);
575
582
  }
576
583
  }
584
+ /** R187: append the requirement ledger to the lift observation the writer is about to see (waits for the lift-time authoring,
585
+ * bounded by the resolver timeout; fail-open to nothing). One-shot per turn. */
586
+ reqLedgerDelivered = false;
587
+ async deliverRequirementLedgerToWriter(lastMsg) {
588
+ const cfg = resolveEndTurnResolverConfig();
589
+ if (!cfg.reqLedger || this.reqLedgerDelivered)
590
+ return;
591
+ this.reqLedgerDelivered = true;
592
+ try {
593
+ const entries = this.reqLedger ?? (this.reqLedgerPromise ? await withTimeout(this.reqLedgerPromise, mentorSurfaceTimeoutMs('endturn-resolver', parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10))) : null);
594
+ const text = formatLedgerForWriter(entries ?? []);
595
+ if (!text)
596
+ return;
597
+ lastMsg?.message?.content?.push?.({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
598
+ const store = this.getDecisionStore();
599
+ if (store)
600
+ void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'requirement_ledger_delivered', toolName: 'EndTurn', detail: { lines: (entries ?? []).length, chars: text.length } }).catch(() => { });
601
+ }
602
+ catch { /* fail-open */ }
603
+ }
577
604
  /** Bounded read-only environment recon (ENV_RECON_COMMAND), gathered ONCE and cached — shared by the
578
605
  * lift planner and the EndTurn resolver. Fail-open: partial stdout on timeout/non-zero, else empty. */
579
606
  gatherEnvReport(opts = {}) {
@@ -762,6 +789,47 @@ export class CortexOrchestrator {
762
789
  console.warn(`[EndTurnResolver] R174 SPEC-TESTS — ${this.specChecks.length} blind check(s) authored at ${cfg.specTestsAt} in ${this.specChecksMeta.genLatencyMs} ms (${this.specChecksMeta.refused} refused)`);
763
790
  return this.specChecks;
764
791
  }
792
+ /** R187: author the requirement ledger (task text + env report ONLY). Sets reqLedger/reqLedgerMeta; never throws. */
793
+ async authorRequirementLedger(task) {
794
+ const cfg = resolveEndTurnResolverConfig();
795
+ const store = this.getDecisionStore();
796
+ const sessionId = this.currentSessionId ?? 'unknown';
797
+ const g0 = Date.now();
798
+ if (!this.helperMiddleware?.deriveRequirementLedger) {
799
+ this.reqLedger = [];
800
+ return [];
801
+ }
802
+ const tmo = mentorSurfaceTimeoutMs('endturn-resolver', parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10));
803
+ try {
804
+ const raw = await withTimeout(this.helperMiddleware.deriveRequirementLedger({ task, envReport: this.gatherEnvReport({ fresh: false }), max: cfg.reqLedgerMax, helperModelId: this.config.reactiveMentorship?.helperModelId }), tmo);
805
+ const lines = parseRequirementLedger(raw ?? '', cfg.reqLedgerMax);
806
+ // a check that is not read-only is dropped (the line stays, unverifiable) — same denylist as every other harness-run check
807
+ let refused = 0;
808
+ const safe = lines.map((l) => (l.check && !isInvestigateCommandAllowed(l.check) ? (refused += 1, { ...l, check: null }) : l));
809
+ this.reqLedger = initLedger(safe);
810
+ this.reqLedgerMeta = { genLatencyMs: Date.now() - g0, raw: (raw ?? '').length, refused };
811
+ }
812
+ catch {
813
+ this.reqLedger = [];
814
+ this.reqLedgerMeta = { genLatencyMs: Date.now() - g0, raw: 0, refused: 0 };
815
+ }
816
+ if (store)
817
+ void store.recordEvent({ sessionId, kind: 'requirement_ledger', toolName: 'EndTurn', detail: { lines: this.reqLedger.map((e) => ({ id: e.id, kind: e.kind, text: e.text, check: e.check })), ...this.reqLedgerMeta, mentor: this.mentorWire('endturn-resolver', cfg.effort, 2000) } }).catch(() => { });
818
+ console.warn(`[EndTurnResolver] R187 REQUIREMENT LEDGER — ${this.reqLedger.length} line(s) (${this.reqLedger.filter((e) => e.check).length} with checks, ${this.reqLedgerMeta.refused} refused) in ${this.reqLedgerMeta.genLatencyMs} ms`);
819
+ return this.reqLedger;
820
+ }
821
+ /** R187: at lift, start authoring the ledger in the background (delivered to the writer by deliverLiftPlanAtLift when ready). */
822
+ maybeAuthorRequirementLedgerAtLift() {
823
+ const cfg = resolveEndTurnResolverConfig();
824
+ if (!cfg.reqLedger || this.reqLedger !== null || this.reqLedgerPromise)
825
+ return;
826
+ if (!resolveEndTurnResolver(process.env))
827
+ return;
828
+ const task = this.lastRealUserText();
829
+ if (!task.trim())
830
+ return;
831
+ this.reqLedgerPromise = this.authorRequirementLedger(task).catch(() => []);
832
+ }
765
833
  /** R174b: at lift, start authoring the spec checks in the background so a late first finish still has them. */
766
834
  maybeAuthorSpecChecksAtLift() {
767
835
  const cfg = resolveEndTurnResolverConfig();
@@ -872,8 +940,34 @@ export class CortexOrchestrator {
872
940
  specResults += (specResults ? '\n\n' : '') + x.r + (ev === 'suspect' ? '\n(this check has failed identically at consecutive finishes while everything else passed — treat it as SUSPECT, not as proof of a gap)' : '');
873
941
  }
874
942
  }
943
+ // R187 HB-REQUIREMENT-LEDGER: run each stated requirement's check; a FAILED line is R166 evidence; an OPEN line (never exercised) can hold once.
944
+ let reqResults = '';
945
+ let reqCounts = { lines: 0, open: 0, exercised: 0, failed: 0, unverifiable: 0 };
946
+ let reqJev = null;
947
+ if (cfg.reqLedger) {
948
+ if (this.reqLedger === null)
949
+ this.reqLedger = await (this.reqLedgerPromise ?? this.authorRequirementLedger(task));
950
+ const runs = this.reqLedger.filter((e) => e.check).map((e) => { const r = runCheck(judgeCwd, e.check, jcfg); return { id: e.id, result: r, cls: classifyCheckRun(r) }; });
951
+ this.reqLedger = updateLedger(this.reqLedger, runs);
952
+ // R187b: Jev decides the lines the shell could not — typed per-line questions on the writer's attestation + harness evidence.
953
+ if (cfg.reqLedgerJev && jevAvailable() && this.reqLedger.some((e) => e.status === 'open' || e.status === 'unverifiable')) {
954
+ const j0 = Date.now();
955
+ try {
956
+ const jr = await jevNoul(buildLedgerJevState({ task, entries: this.reqLedger, attestation, workProduct }), buildLedgerJevQuestions(this.reqLedger));
957
+ const applied = applyLedgerJevAnswers(this.reqLedger, jr ? jr.probabilities : null, cfg.reqLedgerJevMin);
958
+ this.reqLedger = applied.entries;
959
+ reqJev = { asked: applied.asked, closed: applied.closed, latencyMs: Date.now() - j0 };
960
+ }
961
+ catch {
962
+ reqJev = { asked: 0, closed: [], latencyMs: Date.now() - j0, error: true };
963
+ }
964
+ }
965
+ reqCounts = ledgerCounts(this.reqLedger);
966
+ reqResults = formatLedgerForJudge(this.reqLedger);
967
+ }
875
968
  const combinedCheck = [checkResult, namedResults ? `JUDGE-NAMED CHECKS (from your previous fix plan; executed by the harness just now):\n${namedResults}` : '',
876
- specResults ? `SPEC CHECKS (derived from the TASK text before any work was seen; executed by the harness just now — a FAILED one is a requirement not yet met):\n${specResults}` : ''] // R174
969
+ specResults ? `SPEC CHECKS (derived from the TASK text before any work was seen; executed by the harness just now — a FAILED one is a requirement not yet met):\n${specResults}` : '', // R174
970
+ reqResults] // R187
877
971
  .filter(Boolean).join('\n\n');
878
972
  const callsSince = this.turnToolCallTotal - this.toolCallsAtLastVeto;
879
973
  const progressed = this.endTurnResolverRejects === 0 || callsSince >= cfg.progressMinCalls || namedPassed > this.judgePriorNamedPassed; // R166a: newly-passing checks, not any passing check
@@ -999,7 +1093,7 @@ export class CortexOrchestrator {
999
1093
  }
1000
1094
  }
1001
1095
  const holdable = gapHoldable({ gapHold: cfg.gapHold, remainingFrac: resolverRemainingFrac, minRemaining: resolveBudgetContinueMinRemaining(), planChars: verdict.plan.length, jevMode: cfg.gapHoldJev, jevFixable, jevMin: cfg.gapHoldJevMin });
1002
- const checksFailed = namedFailed > 0 || specFailed > 0; // R166 + R174
1096
+ const checksFailed = namedFailed > 0 || specFailed > 0 || reqCounts.failed > 0; // R166 + R174 + R187
1003
1097
  // R173c: a re-hold needs real work since the last hold (elapsed time or a changed open-items list), not just tool calls.
1004
1098
  const msSinceLastHold = this.lastHoldMs > 0 ? Date.now() - this.lastHoldMs : null;
1005
1099
  const holdProg = holdProgressed({ rejects: this.endTurnResolverRejects, msSinceLastHold, priorPlan: this.judgePriorPlan, plan: verdict.plan, minIntervalMs: cfg.gapHoldMinIntervalMs, maxSimilarity: cfg.gapHoldPlanMaxSimilarity });
@@ -1026,11 +1120,18 @@ export class CortexOrchestrator {
1026
1120
  // R173c: the budget floor — below CORTEX_JUDGE_VETO_MIN_REMAINING nothing holds the finish (the gap is recorded).
1027
1121
  const belowFloor = cfg.vetoMinRemaining > 0 && resolverRemainingFrac !== null && resolverRemainingFrac < cfg.vetoMinRemaining && action === 'veto';
1028
1122
  action = applyVetoFloor(action, resolverRemainingFrac, cfg.vetoMinRemaining);
1123
+ // R187: a finish that would otherwise stand while a STATED requirement is still OPEN (its check never passed nor failed — never
1124
+ // exercised) is held once with the open lines named. Never below the budget floor; a FAILED line already went the R166 evidence way.
1125
+ let reqHold = null;
1126
+ if (cfg.reqLedger && this.reqLedger && action !== 'veto' && action !== 'escalate' && !belowFloor &&
1127
+ ledgerHoldable({ open: reqCounts.open, holdsUsed: this.reqLedgerHolds, maxHolds: cfg.reqLedgerHoldMax, remainingFrac: resolverRemainingFrac, minRemaining: cfg.vetoMinRemaining })) {
1128
+ reqHold = { message: formatLedgerHoldMessage({ entries: this.reqLedger, holdIndex: this.reqLedgerHolds + 1, maxHolds: cfg.reqLedgerHoldMax, remainingFrac: resolverRemainingFrac }) };
1129
+ }
1029
1130
  // R176 HB-INDEPENDENT-DERIVATION: on a value-shaped task, before a finish that would otherwise STAND, recompute the result by a
1030
1131
  // different method and hold ONCE on disagreement. Never below the budget floor; never more than once per turn.
1031
1132
  let derivationHold = null;
1032
1133
  let derivationInfo = null;
1033
- if (cfg.derivation === 'on' && action !== 'veto' && action !== 'escalate' && this.derivationHolds < 1 && !belowFloor &&
1134
+ if (cfg.derivation === 'on' && !reqHold && action !== 'veto' && action !== 'escalate' && this.derivationHolds < 1 && !belowFloor &&
1034
1135
  !(cfg.vetoMinRemaining > 0 && resolverRemainingFrac !== null && resolverRemainingFrac < cfg.vetoMinRemaining) && this.helperMiddleware.deriveIndependentCheck) {
1035
1136
  const shape = isValueShapedTask(task);
1036
1137
  derivationInfo = { valueShaped: shape.valueShaped, reason: shape.reason, artifacts: shape.artifacts };
@@ -1098,6 +1199,7 @@ export class CortexOrchestrator {
1098
1199
  specTests: cfg.specTests, specChecks: this.specChecks?.length ?? 0, specRan, specPassed, specFailed, specInconclusive, specSuspect, specGenLatencyMs: this.specChecksMeta.genLatencyMs, specTestsAt: cfg.specTestsAt, // R174 / R174b
1099
1200
  vetoMinRemaining: cfg.vetoMinRemaining, belowFloor, holdProgressed: holdProg, holdGapMs: msSinceLastHold, planSim: Number(planSimilarity(this.judgePriorPlan, verdict.plan).toFixed(3)), msSinceLastHold, // R173c
1100
1201
  derivation: cfg.derivation, derivationAgreement: derivationInfo?.agreement ?? null, derivationHeld: !!derivationHold, // R176
1202
+ reqLedger: cfg.reqLedger, reqLines: reqCounts.lines, reqOpen: reqCounts.open, reqExercised: reqCounts.exercised, reqFailed: reqCounts.failed, reqUnverifiable: reqCounts.unverifiable, reqHeld: !!reqHold, reqHolds: this.reqLedgerHolds, reqGenLatencyMs: this.reqLedgerMeta.genLatencyMs, reqJev: cfg.reqLedgerJev, reqJevAsked: reqJev?.asked ?? 0, reqJevClosed: reqJev?.closed ?? [], reqJevLatencyMs: reqJev?.latencyMs ?? 0, // R187/R187b
1101
1203
  latencyMs, rawLen: (text ?? '').length,
1102
1204
  deltaChars: workspaceDelta.length, checkRan: !!checkResult, checkPassed: checkResult ? /→ PASSED/.test(checkResult) : null,
1103
1205
  liftPlanChars: this.liftPlanText.length,
@@ -1137,10 +1239,20 @@ export class CortexOrchestrator {
1137
1239
  `${verdict.plan}\n\nDo this, verify against the task's own criteria (not your own tests), then call EndTurn again.` +
1138
1240
  (namedResultsNow ? `\n\nHARNESS-RUN CHECKS (named by the reviewer, executed just now — the failing ones are the gap):\n${namedResultsNow.slice(0, 3000)}` : '') + // R166
1139
1241
  (specFailed > 0 ? `\n\nSPEC CHECKS (derived from the TASK text before your work was seen, executed just now — a FAILED one is a stated requirement not yet met):\n${specResults.slice(0, 3000)}` : '') + // R174
1242
+ (reqCounts.failed > 0 ? `\n\nREQUIREMENT LEDGER (stated requirements, checked just now — a FAILED line is a stated requirement not yet met):\n${reqResults.slice(0, 3000)}` : '') + // R187
1140
1243
  (holdable && !checksFailed ? `\n\nNo harness-run check failed, but the reviewer named open items and budget remains — this hold returns them to you (hold ${this.endTurnResolverRejects}/${resolverCap}).` : '') + // R173
1141
1244
  budgetedVetoEscalation(this.endTurnResolverRejects, resolverCap, resolverRemainingMs ?? 0, this.turnDeadlineMsActive); // R160
1142
1245
  console.warn(`[EndTurnResolver] GAP — vetoed finish (${verdict.plan.length}-char plan, reject ${this.endTurnResolverRejects}/${resolverCap}${resolverRemainingFrac === null ? '' : `, budget remaining ${Math.round(resolverRemainingFrac * 100)}%`})`);
1143
1246
  }
1247
+ else if (reqHold) {
1248
+ // R187: the finish would stand, but a stated requirement was never exercised — hold once with the open lines named.
1249
+ this.reqLedgerHolds += 1;
1250
+ this.lastHoldMs = Date.now();
1251
+ ev.endTurnCalled = false;
1252
+ et.is_error = true;
1253
+ et.content = reqHold.message;
1254
+ console.warn(`[EndTurnResolver] R187 LEDGER-HOLD — finish held (${reqCounts.open} open of ${reqCounts.lines} stated requirements; hold ${this.reqLedgerHolds}/${cfg.reqLedgerHoldMax})`);
1255
+ }
1144
1256
  else if (derivationHold) {
1145
1257
  // R176: the finish would stand, but an independent recomputation disagrees — hold once with both values shown.
1146
1258
  this.derivationHolds += 1;
@@ -2136,6 +2248,11 @@ export class CortexOrchestrator {
2136
2248
  this.specChecksMeta = { genLatencyMs: 0, refused: 0, raw: 0 };
2137
2249
  this.specChecksPromise = null;
2138
2250
  this.specFailHistory.clear();
2251
+ this.reqLedger = null;
2252
+ this.reqLedgerPromise = null;
2253
+ this.reqLedgerHolds = 0;
2254
+ this.reqLedgerMeta = { genLatencyMs: 0, raw: 0, refused: 0 };
2255
+ this.reqLedgerDelivered = false;
2139
2256
  this.lastHoldMs = 0;
2140
2257
  this.derivationHolds = 0; // R165 per-turn / R174 / R173c / R176
2141
2258
  this.recordDsmlRecovery(currentAssistantCanonicalMessage);
@@ -3353,6 +3470,7 @@ export class CortexOrchestrator {
3353
3470
  await this.deliverDeferredCorpusAtLift(effectiveModel);
3354
3471
  this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
3355
3472
  await this.deliverLiftPlanAtLift(effectiveModel); // LIFT_MENTOR_PLANNER: bounded mentor-planner at lift (dark unless CORTEX_LIFT_PLAN)
3473
+ this.maybeAuthorRequirementLedgerAtLift(); // R187: the stated-requirement ledger, authored in the background at lift
3356
3474
  this.maybeAuthorSpecChecksAtLift(); // R174b: blind spec checks authored in the background at lift (CORTEX_JUDGE_SPEC_TESTS_AT=lift)
3357
3475
  if (this.config.debug)
3358
3476
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');
@@ -4350,6 +4468,11 @@ export class CortexOrchestrator {
4350
4468
  this.specChecksMeta = { genLatencyMs: 0, refused: 0, raw: 0 };
4351
4469
  this.specChecksPromise = null;
4352
4470
  this.specFailHistory.clear();
4471
+ this.reqLedger = null;
4472
+ this.reqLedgerPromise = null;
4473
+ this.reqLedgerHolds = 0;
4474
+ this.reqLedgerMeta = { genLatencyMs: 0, raw: 0, refused: 0 };
4475
+ this.reqLedgerDelivered = false;
4353
4476
  this.lastHoldMs = 0;
4354
4477
  this.derivationHolds = 0; // R165 per-turn / R174 / R173c / R176
4355
4478
  this.recordDsmlRecovery(currentAssistantCanonicalMessage);
@@ -5150,6 +5273,7 @@ export class CortexOrchestrator {
5150
5273
  await this.deliverDeferredCorpusAtLift(effectiveModel);
5151
5274
  this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
5152
5275
  await this.deliverLiftPlanAtLift(effectiveModel); // LIFT_MENTOR_PLANNER: bounded mentor-planner at lift (dark unless CORTEX_LIFT_PLAN)
5276
+ this.maybeAuthorRequirementLedgerAtLift(); // R187: the stated-requirement ledger, authored in the background at lift
5153
5277
  this.maybeAuthorSpecChecksAtLift(); // R174b: blind spec checks authored in the background at lift (CORTEX_JUDGE_SPEC_TESTS_AT=lift)
5154
5278
  if (this.config.debug)
5155
5279
  console.log('[Anchor] lifted at first tool_result boundary — session profile applies');