@nexus-cortex/core 4.124.11 → 4.124.13

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 +29 -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 +129 -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,28 @@ 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
+ try {
592
+ if (this.reqLedger === null && !this.reqLedgerPromise)
593
+ this.maybeAuthorRequirementLedgerAtLift(); // the plan path ran before the lift hook
594
+ const entries = this.reqLedger ?? (this.reqLedgerPromise ? await withTimeout(this.reqLedgerPromise, mentorSurfaceTimeoutMs('endturn-resolver', parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10))) : null);
595
+ const text = formatLedgerForWriter(entries ?? []);
596
+ if (!text)
597
+ return; // nothing authored (yet) — leave the one-shot open
598
+ this.reqLedgerDelivered = true;
599
+ lastMsg?.message?.content?.push?.({ type: 'text', text: `<system-reminder>\n${text}\n</system-reminder>` });
600
+ const store = this.getDecisionStore();
601
+ if (store)
602
+ void store.recordEvent({ sessionId: this.currentSessionId ?? 'unknown', kind: 'requirement_ledger_delivered', toolName: 'EndTurn', detail: { lines: (entries ?? []).length, chars: text.length } }).catch(() => { });
603
+ }
604
+ catch { /* fail-open */ }
605
+ }
577
606
  /** Bounded read-only environment recon (ENV_RECON_COMMAND), gathered ONCE and cached — shared by the
578
607
  * lift planner and the EndTurn resolver. Fail-open: partial stdout on timeout/non-zero, else empty. */
579
608
  gatherEnvReport(opts = {}) {
@@ -762,6 +791,47 @@ export class CortexOrchestrator {
762
791
  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
792
  return this.specChecks;
764
793
  }
794
+ /** R187: author the requirement ledger (task text + env report ONLY). Sets reqLedger/reqLedgerMeta; never throws. */
795
+ async authorRequirementLedger(task) {
796
+ const cfg = resolveEndTurnResolverConfig();
797
+ const store = this.getDecisionStore();
798
+ const sessionId = this.currentSessionId ?? 'unknown';
799
+ const g0 = Date.now();
800
+ if (!this.helperMiddleware?.deriveRequirementLedger) {
801
+ this.reqLedger = [];
802
+ return [];
803
+ }
804
+ const tmo = mentorSurfaceTimeoutMs('endturn-resolver', parseInt(process.env.CORTEX_ENDTURN_RESOLVER_TIMEOUT_MS ?? '90000', 10));
805
+ try {
806
+ const raw = await withTimeout(this.helperMiddleware.deriveRequirementLedger({ task, envReport: this.gatherEnvReport({ fresh: false }), max: cfg.reqLedgerMax, helperModelId: this.config.reactiveMentorship?.helperModelId }), tmo);
807
+ const lines = parseRequirementLedger(raw ?? '', cfg.reqLedgerMax);
808
+ // a check that is not read-only is dropped (the line stays, unverifiable) — same denylist as every other harness-run check
809
+ let refused = 0;
810
+ const safe = lines.map((l) => (l.check && !isInvestigateCommandAllowed(l.check) ? (refused += 1, { ...l, check: null }) : l));
811
+ this.reqLedger = initLedger(safe);
812
+ this.reqLedgerMeta = { genLatencyMs: Date.now() - g0, raw: (raw ?? '').length, refused };
813
+ }
814
+ catch {
815
+ this.reqLedger = [];
816
+ this.reqLedgerMeta = { genLatencyMs: Date.now() - g0, raw: 0, refused: 0 };
817
+ }
818
+ if (store)
819
+ 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(() => { });
820
+ 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`);
821
+ return this.reqLedger;
822
+ }
823
+ /** R187: at lift, start authoring the ledger in the background (delivered to the writer by deliverLiftPlanAtLift when ready). */
824
+ maybeAuthorRequirementLedgerAtLift() {
825
+ const cfg = resolveEndTurnResolverConfig();
826
+ if (!cfg.reqLedger || this.reqLedger !== null || this.reqLedgerPromise)
827
+ return;
828
+ if (!resolveEndTurnResolver(process.env))
829
+ return;
830
+ const task = this.lastRealUserText();
831
+ if (!task.trim())
832
+ return;
833
+ this.reqLedgerPromise = this.authorRequirementLedger(task).catch(() => []);
834
+ }
765
835
  /** R174b: at lift, start authoring the spec checks in the background so a late first finish still has them. */
766
836
  maybeAuthorSpecChecksAtLift() {
767
837
  const cfg = resolveEndTurnResolverConfig();
@@ -872,8 +942,34 @@ export class CortexOrchestrator {
872
942
  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
943
  }
874
944
  }
945
+ // R187 HB-REQUIREMENT-LEDGER: run each stated requirement's check; a FAILED line is R166 evidence; an OPEN line (never exercised) can hold once.
946
+ let reqResults = '';
947
+ let reqCounts = { lines: 0, open: 0, exercised: 0, failed: 0, unverifiable: 0 };
948
+ let reqJev = null;
949
+ if (cfg.reqLedger) {
950
+ if (this.reqLedger === null)
951
+ this.reqLedger = await (this.reqLedgerPromise ?? this.authorRequirementLedger(task));
952
+ 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) }; });
953
+ this.reqLedger = updateLedger(this.reqLedger, runs);
954
+ // R187b: Jev decides the lines the shell could not — typed per-line questions on the writer's attestation + harness evidence.
955
+ if (cfg.reqLedgerJev && jevAvailable() && this.reqLedger.some((e) => e.status === 'open' || e.status === 'unverifiable')) {
956
+ const j0 = Date.now();
957
+ try {
958
+ const jr = await jevNoul(buildLedgerJevState({ task, entries: this.reqLedger, attestation, workProduct }), buildLedgerJevQuestions(this.reqLedger));
959
+ const applied = applyLedgerJevAnswers(this.reqLedger, jr ? jr.probabilities : null, cfg.reqLedgerJevMin);
960
+ this.reqLedger = applied.entries;
961
+ reqJev = { asked: applied.asked, closed: applied.closed, latencyMs: Date.now() - j0 };
962
+ }
963
+ catch {
964
+ reqJev = { asked: 0, closed: [], latencyMs: Date.now() - j0, error: true };
965
+ }
966
+ }
967
+ reqCounts = ledgerCounts(this.reqLedger);
968
+ reqResults = formatLedgerForJudge(this.reqLedger);
969
+ }
875
970
  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
971
+ 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
972
+ reqResults] // R187
877
973
  .filter(Boolean).join('\n\n');
878
974
  const callsSince = this.turnToolCallTotal - this.toolCallsAtLastVeto;
879
975
  const progressed = this.endTurnResolverRejects === 0 || callsSince >= cfg.progressMinCalls || namedPassed > this.judgePriorNamedPassed; // R166a: newly-passing checks, not any passing check
@@ -999,7 +1095,7 @@ export class CortexOrchestrator {
999
1095
  }
1000
1096
  }
1001
1097
  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
1098
+ const checksFailed = namedFailed > 0 || specFailed > 0 || reqCounts.failed > 0; // R166 + R174 + R187
1003
1099
  // R173c: a re-hold needs real work since the last hold (elapsed time or a changed open-items list), not just tool calls.
1004
1100
  const msSinceLastHold = this.lastHoldMs > 0 ? Date.now() - this.lastHoldMs : null;
1005
1101
  const holdProg = holdProgressed({ rejects: this.endTurnResolverRejects, msSinceLastHold, priorPlan: this.judgePriorPlan, plan: verdict.plan, minIntervalMs: cfg.gapHoldMinIntervalMs, maxSimilarity: cfg.gapHoldPlanMaxSimilarity });
@@ -1026,11 +1122,18 @@ export class CortexOrchestrator {
1026
1122
  // R173c: the budget floor — below CORTEX_JUDGE_VETO_MIN_REMAINING nothing holds the finish (the gap is recorded).
1027
1123
  const belowFloor = cfg.vetoMinRemaining > 0 && resolverRemainingFrac !== null && resolverRemainingFrac < cfg.vetoMinRemaining && action === 'veto';
1028
1124
  action = applyVetoFloor(action, resolverRemainingFrac, cfg.vetoMinRemaining);
1125
+ // R187: a finish that would otherwise stand while a STATED requirement is still OPEN (its check never passed nor failed — never
1126
+ // exercised) is held once with the open lines named. Never below the budget floor; a FAILED line already went the R166 evidence way.
1127
+ let reqHold = null;
1128
+ if (cfg.reqLedger && this.reqLedger && action !== 'veto' && action !== 'escalate' && !belowFloor &&
1129
+ ledgerHoldable({ open: reqCounts.open, holdsUsed: this.reqLedgerHolds, maxHolds: cfg.reqLedgerHoldMax, remainingFrac: resolverRemainingFrac, minRemaining: cfg.vetoMinRemaining })) {
1130
+ reqHold = { message: formatLedgerHoldMessage({ entries: this.reqLedger, holdIndex: this.reqLedgerHolds + 1, maxHolds: cfg.reqLedgerHoldMax, remainingFrac: resolverRemainingFrac }) };
1131
+ }
1029
1132
  // R176 HB-INDEPENDENT-DERIVATION: on a value-shaped task, before a finish that would otherwise STAND, recompute the result by a
1030
1133
  // different method and hold ONCE on disagreement. Never below the budget floor; never more than once per turn.
1031
1134
  let derivationHold = null;
1032
1135
  let derivationInfo = null;
1033
- if (cfg.derivation === 'on' && action !== 'veto' && action !== 'escalate' && this.derivationHolds < 1 && !belowFloor &&
1136
+ if (cfg.derivation === 'on' && !reqHold && action !== 'veto' && action !== 'escalate' && this.derivationHolds < 1 && !belowFloor &&
1034
1137
  !(cfg.vetoMinRemaining > 0 && resolverRemainingFrac !== null && resolverRemainingFrac < cfg.vetoMinRemaining) && this.helperMiddleware.deriveIndependentCheck) {
1035
1138
  const shape = isValueShapedTask(task);
1036
1139
  derivationInfo = { valueShaped: shape.valueShaped, reason: shape.reason, artifacts: shape.artifacts };
@@ -1098,6 +1201,7 @@ export class CortexOrchestrator {
1098
1201
  specTests: cfg.specTests, specChecks: this.specChecks?.length ?? 0, specRan, specPassed, specFailed, specInconclusive, specSuspect, specGenLatencyMs: this.specChecksMeta.genLatencyMs, specTestsAt: cfg.specTestsAt, // R174 / R174b
1099
1202
  vetoMinRemaining: cfg.vetoMinRemaining, belowFloor, holdProgressed: holdProg, holdGapMs: msSinceLastHold, planSim: Number(planSimilarity(this.judgePriorPlan, verdict.plan).toFixed(3)), msSinceLastHold, // R173c
1100
1203
  derivation: cfg.derivation, derivationAgreement: derivationInfo?.agreement ?? null, derivationHeld: !!derivationHold, // R176
1204
+ 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
1205
  latencyMs, rawLen: (text ?? '').length,
1102
1206
  deltaChars: workspaceDelta.length, checkRan: !!checkResult, checkPassed: checkResult ? /→ PASSED/.test(checkResult) : null,
1103
1207
  liftPlanChars: this.liftPlanText.length,
@@ -1137,10 +1241,20 @@ export class CortexOrchestrator {
1137
1241
  `${verdict.plan}\n\nDo this, verify against the task's own criteria (not your own tests), then call EndTurn again.` +
1138
1242
  (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
1243
  (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
1244
+ (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
1245
  (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
1246
  budgetedVetoEscalation(this.endTurnResolverRejects, resolverCap, resolverRemainingMs ?? 0, this.turnDeadlineMsActive); // R160
1142
1247
  console.warn(`[EndTurnResolver] GAP — vetoed finish (${verdict.plan.length}-char plan, reject ${this.endTurnResolverRejects}/${resolverCap}${resolverRemainingFrac === null ? '' : `, budget remaining ${Math.round(resolverRemainingFrac * 100)}%`})`);
1143
1248
  }
1249
+ else if (reqHold) {
1250
+ // R187: the finish would stand, but a stated requirement was never exercised — hold once with the open lines named.
1251
+ this.reqLedgerHolds += 1;
1252
+ this.lastHoldMs = Date.now();
1253
+ ev.endTurnCalled = false;
1254
+ et.is_error = true;
1255
+ et.content = reqHold.message;
1256
+ console.warn(`[EndTurnResolver] R187 LEDGER-HOLD — finish held (${reqCounts.open} open of ${reqCounts.lines} stated requirements; hold ${this.reqLedgerHolds}/${cfg.reqLedgerHoldMax})`);
1257
+ }
1144
1258
  else if (derivationHold) {
1145
1259
  // R176: the finish would stand, but an independent recomputation disagrees — hold once with both values shown.
1146
1260
  this.derivationHolds += 1;
@@ -2136,6 +2250,11 @@ export class CortexOrchestrator {
2136
2250
  this.specChecksMeta = { genLatencyMs: 0, refused: 0, raw: 0 };
2137
2251
  this.specChecksPromise = null;
2138
2252
  this.specFailHistory.clear();
2253
+ this.reqLedger = null;
2254
+ this.reqLedgerPromise = null;
2255
+ this.reqLedgerHolds = 0;
2256
+ this.reqLedgerMeta = { genLatencyMs: 0, raw: 0, refused: 0 };
2257
+ this.reqLedgerDelivered = false;
2139
2258
  this.lastHoldMs = 0;
2140
2259
  this.derivationHolds = 0; // R165 per-turn / R174 / R173c / R176
2141
2260
  this.recordDsmlRecovery(currentAssistantCanonicalMessage);
@@ -3352,6 +3471,7 @@ export class CortexOrchestrator {
3352
3471
  // full tool catalog (no-op unless CORTEX_PROMPT_MASS=defer).
3353
3472
  await this.deliverDeferredCorpusAtLift(effectiveModel);
3354
3473
  this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
3474
+ this.maybeAuthorRequirementLedgerAtLift(); // R187: the stated-requirement ledger, authored in the background at lift — BEFORE the plan call so it is ready to ride with the plan
3355
3475
  await this.deliverLiftPlanAtLift(effectiveModel); // LIFT_MENTOR_PLANNER: bounded mentor-planner at lift (dark unless CORTEX_LIFT_PLAN)
3356
3476
  this.maybeAuthorSpecChecksAtLift(); // R174b: blind spec checks authored in the background at lift (CORTEX_JUDGE_SPEC_TESTS_AT=lift)
3357
3477
  if (this.config.debug)
@@ -4350,6 +4470,11 @@ export class CortexOrchestrator {
4350
4470
  this.specChecksMeta = { genLatencyMs: 0, refused: 0, raw: 0 };
4351
4471
  this.specChecksPromise = null;
4352
4472
  this.specFailHistory.clear();
4473
+ this.reqLedger = null;
4474
+ this.reqLedgerPromise = null;
4475
+ this.reqLedgerHolds = 0;
4476
+ this.reqLedgerMeta = { genLatencyMs: 0, raw: 0, refused: 0 };
4477
+ this.reqLedgerDelivered = false;
4353
4478
  this.lastHoldMs = 0;
4354
4479
  this.derivationHolds = 0; // R165 per-turn / R174 / R173c / R176
4355
4480
  this.recordDsmlRecovery(currentAssistantCanonicalMessage);
@@ -5149,6 +5274,7 @@ export class CortexOrchestrator {
5149
5274
  // P6 deferral: same one-shot corpus delivery as the sendMessage path.
5150
5275
  await this.deliverDeferredCorpusAtLift(effectiveModel);
5151
5276
  this.deliverLiftNudge(allTools, effectiveModel); // A′ proposal-1: SearchTools/AskForAdvice signpost at lift
5277
+ this.maybeAuthorRequirementLedgerAtLift(); // R187: the stated-requirement ledger, authored in the background at lift — BEFORE the plan call so it is ready to ride with the plan
5152
5278
  await this.deliverLiftPlanAtLift(effectiveModel); // LIFT_MENTOR_PLANNER: bounded mentor-planner at lift (dark unless CORTEX_LIFT_PLAN)
5153
5279
  this.maybeAuthorSpecChecksAtLift(); // R174b: blind spec checks authored in the background at lift (CORTEX_JUDGE_SPEC_TESTS_AT=lift)
5154
5280
  if (this.config.debug)