@sema-agent/core 5.58.0 → 5.59.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.
@@ -1,11 +1,11 @@
1
1
  import type { EngineNotice } from "../types.js";
2
- import { type ConsolidationGateRow, type ConsolidationPlanFile, type ConsolidationProposal, type ConsolidationRunStopReason, type MemoryConsolidationOptions } from "./consolidation.js";
2
+ import { type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationPlanFile, type ConsolidationProposal, type ConsolidationRunStopReason, type MemoryConsolidationOptions } from "./consolidation.js";
3
3
  import { foreignDanglingSessionAccounts, type EffectiveSettlement, type HoldRow } from "./delegation-settlement.js";
4
4
  import { type OriginClearanceRow } from "./origin-clearance.js";
5
5
  import { type CommittedBinding, type EraseMemoryEntriesInput, type MemoryErasureAttestation, type TransferEvidence } from "./file-backend.js";
6
6
  import { type MemoryExportBundle, type MemoryImportReport } from "./export-bundle.js";
7
7
  import { type ChallengeAssignment, type ChallengeEvent, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionMarkOutcome, type SessionPollutionRecord } from "./layout.js";
8
- import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemoryEntry, MemoryEntryOrigin, MemorySessionHandle, ScanFinding } from "./types.js";
8
+ import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemoryEntry, MemoryEntryOrigin, MemoryScopeEnumeration, MemorySessionHandle, ScanFinding } from "./types.js";
9
9
  /**
10
10
  * The CC `# Memory` instruction section — VERBATIM from the live capture
11
11
  * (bench/cc-parity-toolsuite/reminders/live-capture-main-session-2026-07-08.md §12), with the
@@ -514,6 +514,36 @@ export interface ConsolidationPlanSummary {
514
514
  directed?: number;
515
515
  intents?: number;
516
516
  }
517
+ /**
518
+ * design/376 slice ③ (#436) — one plan's MEMBER-LEVEL fold evidence
519
+ * ({@link MemoryEngine.readConsolidationPlanFoldEvidence}'s answer): which entry ids this plan's
520
+ * APPLIED products attest as superseded. Discriminated so an unreadable plan can never be told
521
+ * apart from an empty one by accident.
522
+ */
523
+ export type ConsolidationPlanFoldEvidence = {
524
+ state: "ok";
525
+ scope: string;
526
+ /** The commit call's requestId, frozen on the plan — the durable run-attribution anchor
527
+ * (the driver's per-cycle requestIds share the run's prefix, so a cycle whose receipt was
528
+ * lost to a crash is still attributable to its run through the plan file). */
529
+ requestId: string;
530
+ planState: ConsolidationPlanFile["state"];
531
+ /** Every supersession-edge target of an APPLIED product, deduplicated and sorted. Products
532
+ * still pending/conflicted attest nothing — the store never saw their edges land. */
533
+ supersededTargets: string[];
534
+ } | {
535
+ state: "absent";
536
+ } | {
537
+ state: "corrupt";
538
+ detail: string;
539
+ /** Present when the plan FILE parsed and only its product frontmatter is damaged (the deep
540
+ * per-product walk's corrupt arm): the frozen attribution anchors survive the damage, so a
541
+ * caller can still attribute the damaged plan to a run — the conservation wall uses this
542
+ * to fail closed ONLY on a run-owned damaged plan while a foreign/historical one is
543
+ * skipped with disclosure. Absent when the file itself is unreadable/unparseable. */
544
+ scope?: string;
545
+ requestId?: string;
546
+ };
517
547
  /** §2.2 — the recommendation notice, minted off the harvest report's advisory member (the runner's
518
548
  * hold-notice consumption point is its natural home). Undefined ⇔ this harvest crossed no edge. */
519
549
  export declare function memoryConsolidationRecommendedNotice(report: HarvestReport, sessionId?: string): EngineNotice | undefined;
@@ -956,10 +986,18 @@ export declare class MemoryEngine {
956
986
  }>;
957
987
  /**
958
988
  * §1.2-2/§1.2-3 — verb ②: mechanical acceptance + plan FREEZE + apply-to-terminal. Structural
959
- * violations (scan findings, out-of-snapshot attribution, caps, the instruction hard gate, the
960
- * supersession fuse) refuse the WHOLE plan — zero partial freeze; per-product eligibility drift
961
- * (an input's rev moved / an input got challenged between snapshot and freeze) refuses THAT
962
- * product with a disclosed reason while the rest freeze (§1.2-2 冻结时资格复验).
989
+ * violations (out-of-snapshot attribution, caps, the instruction hard gate, the supersession
990
+ * fuse, an empty plan) refuse the WHOLE plan — zero partial freeze. Per-product refusals, each
991
+ * disclosed on the receipt (`products.refused`): eligibility drift (an input's rev moved / an
992
+ * input got challenged between snapshot and freeze, §1.2-2 冻结时资格复验) AND scan findings
993
+ * (design/376 C-2① — the explicit revision of 339 §1.2-2's whole-plan clause: one product's
994
+ * refused bytes must not permanently deny service to every other fold in the plan). The scan
995
+ * seat keeps two whole-plan backstops: the scan-refusal fuse (C-2②, {@link scanRefusalCeiling})
996
+ * refuses a plan saturated with scan-refused products, and a plan the freeze emptied entirely
997
+ * refuses rather than settling as a vacuous completed run (C-2③ — completion writes `lastRunAt`
998
+ * and stamps the full eligible set into the fingerprint, blinding every later incremental
999
+ * snapshot). Intent (directed) scan findings stay whole-plan: intents are host-authored, not
1000
+ * driver products.
963
1001
  */
964
1002
  commitConsolidationPlan(cycleToken: string, proposal: ConsolidationProposal, opts: {
965
1003
  requestId: string;
@@ -1009,7 +1047,12 @@ export declare class MemoryEngine {
1009
1047
  * ABANDONED explicitly (audited; never a silent drop — unfulfilled and given-up stay two
1010
1048
  * distinguishable account facts, G24) and the seat releases; `retry` = the plan's unfulfilled
1011
1049
  * intents re-judge NOW into a NEW plan (fresh freeze, fresh baseRevs — the ONE authorized
1012
- * re-judgment point) which applies immediately. Both audited by requestId.
1050
+ * re-judgment point) which applies immediately. A retry with NOTHING to re-judge (a
1051
+ * products-only conflict plan, or every unfulfilled intent already credentialed) refuses
1052
+ * instead of settling a vacuous completed run — C-2③'s empty-plan law at this corridor's door
1053
+ * (the completion would burn the time gate and stamp the original cycle's eligible map into
1054
+ * the fingerprint); `discard` is the exit that releases the seat without wearing the
1055
+ * completed-run label. Both audited by requestId.
1013
1056
  */
1014
1057
  resolveConsolidationPlan(planId: string, action: "discard" | "retry", opts: {
1015
1058
  requestId: string;
@@ -1025,9 +1068,44 @@ export declare class MemoryEngine {
1025
1068
  private reconstructIntentsForRetry;
1026
1069
  /** §6.2 — host read face: every plan's summary (audit enumeration, zero model surface). */
1027
1070
  listConsolidationPlans(): ConsolidationPlanSummary[];
1071
+ /**
1072
+ * design/376 slice ③ (#436) — host read face: one plan's MEMBER-LEVEL fold evidence (which ids
1073
+ * its APPLIED products supersede). This is the conservation wall's terminal witness: the
1074
+ * driver's other two witnesses (per-cycle row delta, since-open store delta) are both
1075
+ * store-COUNT readings, and an unrelated fold interleaved into the run's scope can numerically
1076
+ * offset a planned member that was deleted mid-run — masking the shortfall behind a balanced
1077
+ * count. Member identity cannot be offset: a planned member either appears among some
1078
+ * run-attributed plan's applied supersession targets, sits in named residue, or the run must
1079
+ * not wear the converged label. Committed plan-file read, side-effect-free; the answer carries
1080
+ * the plan's frozen `requestId` so a caller can attribute plans to a run even across the
1081
+ * commit→receipt crash window (the plan file is durable before any receipt is).
1082
+ *
1083
+ * SCOPE of the attestation, disclosed (an outside review probed it; recorded as the designed
1084
+ * limit rather than patched): this is the committed HISTORICAL fact "this plan's edge landed" —
1085
+ * it does not re-derive whether the edge still stands NOW. A target edit or a carrier challenge
1086
+ * later SUSPENDS the edge (`deriveSupersededSet`'s revival law), and a competing writer may
1087
+ * re-fold the revived member before the run's next snapshot; the attestation still exempts it
1088
+ * and the run keeps its converged label. That label stays materially true in every reachable
1089
+ * arm: the member's content is preserved in THIS run's committed carrier either way, the store
1090
+ * is at a fixpoint (re-covered by a standing edge, or explicitly tombstoned), and the next full
1091
+ * mint re-judges the live world. A LIVE-standing re-derivation here would instead false-red the
1092
+ * honestly converged re-covered case, and the ruled evidence form is the per-plan applied edge.
1093
+ */
1094
+ readConsolidationPlanFoldEvidence(planId: string): ConsolidationPlanFoldEvidence;
1028
1095
  /** §6.2 — host read face: one scope's gate row (undefined when the store has no row — reading
1029
- * never creates the file, so the OFF deployment stays byte-identical, G1). */
1096
+ * never creates the file, so the OFF deployment stays byte-identical, G1). A corrupt store
1097
+ * ALSO answers undefined here — this face cannot distinguish the two; a consumer that needs
1098
+ * the difference reads {@link readConsolidationGateState} (#437②). */
1030
1099
  readConsolidationGate(scope: string): ConsolidationGateRow | undefined;
1100
+ /**
1101
+ * design/376 slice ③ (#437②) — host read face: one scope's gate row as a DISCRIMINATED answer.
1102
+ * The undiscriminated face above folds a corrupt store into `undefined`, so a healthy never-run
1103
+ * scope and a store every verb will refuse fail-closed were indistinguishable to an operator or
1104
+ * a scheduling read. `present` carries the row; `absent` = readable store, no row (reading never
1105
+ * creates the file — the OFF deployment stays byte-identical, G1); `corrupt` carries the
1106
+ * refusal detail. Additive; the undiscriminated face is unchanged.
1107
+ */
1108
+ readConsolidationGateState(scope: string): ConsolidationGateRead;
1031
1109
  /**
1032
1110
  * design/376 片② — host read face: one scope's committed FOLD STATE, in the freeze's own
1033
1111
  * accounting (`active` counts entries neither superseded nor excluded — byte-for-byte the
@@ -1050,6 +1128,17 @@ export declare class MemoryEngine {
1050
1128
  * (design/376 §1.6-4); path knowledge is not authority — nothing here bypasses a verb.
1051
1129
  */
1052
1130
  get controlPlaneRoot(): string;
1131
+ /**
1132
+ * design/376 slice ③ (#437③) — host read face: the store's scope enumeration, capability-probed
1133
+ * on the backend (`listScopes`, the audit-face probe family). A backend WITHOUT the face
1134
+ * answers `supported: false` — never an empty array: "cannot enumerate" and "hosts no scopes"
1135
+ * must stay two different answers, or every audit sweep over a non-enumerating backend would
1136
+ * silently claim a clean store. The File backend derives the answer from the store root's
1137
+ * durable scope registry (store-level fact — never a session roster); the SQL twins gain the
1138
+ * face through the store-contracts extension (separate ticket). A supporting backend's corrupt
1139
+ * registry THROWS fail-closed — the same reason an unsupported one never reads as empty.
1140
+ */
1141
+ listMemoryScopes(): Promise<MemoryScopeEnumeration>;
1053
1142
  /** The committed, side-effect-free audit read: the adoption-restricted committed view when the
1054
1143
  * backend offers one (zero-copy File — ledger+shadow, no disk adoption), else the non-adopting
1055
1144
  * retrieval view (copy-out File), else the backend itself (Pg/TiDB shapes — naturally committed
@@ -9,7 +9,7 @@ import { formatMemoryAge } from "../memory-recall.js";
9
9
  import { committedDistilledOf, committedOriginOf, computeEntryRev, distilledEquals, hasDistilledFormExtra, hasOriginFormExtra, isValidEntryId, originEquals, parseEntryFile, serializeEntryFile, stripModelWrittenDistilled, stripModelWrittenOrigin } from "./frontmatter.js";
10
10
  import { MEMORY_PROVENANCE_RECALL_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow } from "./provenance-wording.js";
11
11
  import { isInstructionEntry } from "./header-hints.js";
12
- import { ConsolidationRefusedError, CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, CONSOLIDATION_SEAT_FREEZE_GRACE_MS, consolidationTypeEligible, deriveProductSlug, deriveSupersededSet, isValidConsolidationPlanId, listConsolidationPlanIds, productAddPatch, quarantineCorruptPlan, readConsolidationGateFile, readConsolidationPlan, readIntentCredentials, recordActiveSetBaseline, recordConsolidationSession, recordIntentCredentials, screenConsolidationOptions, supersessionFuseCeiling, updateConsolidationGate, writeConsolidationPlan, } from "./consolidation.js";
12
+ import { ConsolidationRefusedError, CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, CONSOLIDATION_SEAT_FREEZE_GRACE_MS, consolidationTypeEligible, deriveProductSlug, deriveSupersededSet, isValidConsolidationPlanId, listConsolidationPlanIds, productAddPatch, quarantineCorruptPlan, readConsolidationGateFile, readConsolidationPlan, readIntentCredentials, recordActiveSetBaseline, recordConsolidationSession, recordIntentCredentials, scanRefusalCeiling, screenConsolidationOptions, supersessionFuseCeiling, updateConsolidationGate, writeConsolidationPlan, } from "./consolidation.js";
13
13
  import { classifySessionSettlements, clearHoldResolution, closeSessionAccount, disposeHold, effectiveSettlements, expireOverdueSettlements, foreignDanglingSessionAccounts, markHoldReleased, openInstructionHold, openSessionAccount, readHoldCustody, readHolds, reconcileHolds, replayExternalSettlementEffects, resolveHoldRecord, resolveSessionAccountRecord, resolveSettlementRecord, sessionSettlements, sessionUnattributedSet, } from "./delegation-settlement.js";
14
14
  import { noReplaceRestore } from "./delegation-settlement.js";
15
15
  import { markOriginClearanceTombstoned, openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
@@ -2656,6 +2656,9 @@ export class MemoryEngine {
2656
2656
  else if (p.inputs.length > screened.maxInputsPerProduct)
2657
2657
  reasons.push(`product #${i}: ${p.inputs.length} inputs > maxInputsPerProduct ${screened.maxInputsPerProduct}`);
2658
2658
  }
2659
+ if (proposal.products.length === 0 && intents.length === 0) {
2660
+ reasons.push("empty plan: zero products and zero directed intents — a plan with nothing to apply must not settle as a completed run (the vacuous completion would stamp the eligible set into the fingerprint and blind the incremental face)");
2661
+ }
2659
2662
  if (reasons.length > 0)
2660
2663
  throw new ConsolidationRefusedError("memory.consolidation_refused", "memory consolidation plan refused (structural)", reasons);
2661
2664
  const planId = uuidv7();
@@ -2737,21 +2740,7 @@ export class MemoryEngine {
2737
2740
  }
2738
2741
  }
2739
2742
  const activeSetSize = headers.filter((h) => !superseded.has(h.id) && !exclusions.has(h.id)).length;
2740
- const edgeTargets = new Set();
2741
- for (const p of proposal.products)
2742
- for (const i2 of p.inputs)
2743
- if (i2.supersede === true)
2744
- edgeTargets.add(i2.id);
2745
2743
  const ceiling = supersessionFuseCeiling(activeSetSize, screened);
2746
- if (edgeTargets.size > ceiling) {
2747
- wholeReasons.push(`supersession fuse: ${edgeTargets.size} distinct targets > ceiling ${ceiling} (active set ${activeSetSize}, ratio ${screened.supersedeRatioCap}, floor ${screened.supersedeAbsoluteFloor}, N-1 unconditional)`);
2748
- try {
2749
- enqueueMemoryAnnouncement(this.controlDir, { kind: "gate", at, items: [`memory consolidation plan for scope ${inlineUntrusted(scope, 80)} REFUSED by the supersession fuse (${edgeTargets.size} targets over the ${ceiling} ceiling)`] });
2750
- }
2751
- catch (err) {
2752
- this.discloseAnnounceFailure("consolidation fuse enqueue", err);
2753
- }
2754
- }
2755
2744
  let visibleMarked = snapshot.marked;
2756
2745
  if (!visibleMarked) {
2757
2746
  const inputIds = [...new Set(proposal.products.flatMap((p) => p.inputs.map((i2) => i2.id)))];
@@ -2761,12 +2750,18 @@ export class MemoryEngine {
2761
2750
  const foldedOrigin = visibleMarked ? { taint: "external", cause: "derived", at } : undefined;
2762
2751
  for (let i = 0; i < proposal.products.length; i++) {
2763
2752
  const p = proposal.products[i];
2764
- if (visibleMarked && isInstructionEntry({ ...(p.type !== undefined ? { type: p.type } : {}) })) {
2765
- wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — the run's visible set contains marked content (the laundering hard gate, G7/G26)`);
2753
+ if (!isInstructionEntry({ ...(p.type !== undefined ? { type: p.type } : {}) }))
2754
+ continue;
2755
+ if (visibleMarked) {
2756
+ wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — the run's visible set contains marked content (the laundering hard gate, G7/G26; no option opens this arm)`);
2757
+ }
2758
+ else if (!screened.allowInstructionProducts) {
2759
+ wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — instruction-form consolidation products are refused unconditionally by default (a consolidation must not mint privileged entries out of ordinary notes); the host escape hatch is consolidation.allowInstructionProducts: true`);
2766
2760
  }
2767
2761
  }
2768
2762
  const assembled = [];
2769
2763
  const refusedProducts = [];
2764
+ const scanRefused = [];
2770
2765
  for (let i = 0; i < proposal.products.length; i++) {
2771
2766
  const p = proposal.products[i];
2772
2767
  const drift = [];
@@ -2813,11 +2808,31 @@ export class MemoryEngine {
2813
2808
  const nameFinding = scanMemoryFileName(`${slug}.md`);
2814
2809
  const findings = [...(nameFinding !== undefined ? [nameFinding] : []), ...scanMemoryWrite(serializeEntryFile(entry), { maxBytes: this.perFileBytes })];
2815
2810
  if (findings.length > 0) {
2816
- wholeReasons.push(`product #${i}: scan gate (${findings.map((f) => `${f.code}: ${f.reason}`).join("; ")})`);
2811
+ const reason = `scan gate (${findings.map((f) => `${f.code}: ${f.reason}`).join("; ")})`;
2812
+ refusedProducts.push({ index: i, reason });
2813
+ scanRefused.push({ index: i, reason });
2817
2814
  continue;
2818
2815
  }
2819
2816
  assembled.push(entry);
2820
2817
  }
2818
+ const edgeTargets = new Set();
2819
+ for (const e of assembled)
2820
+ for (const i2 of e.frontmatter.distilled?.inputs ?? [])
2821
+ if (i2.superseded === true)
2822
+ edgeTargets.add(i2.id);
2823
+ if (edgeTargets.size > ceiling) {
2824
+ wholeReasons.push(`supersession fuse: ${edgeTargets.size} distinct targets > ceiling ${ceiling} (active set ${activeSetSize}, ratio ${screened.supersedeRatioCap}, floor ${screened.supersedeAbsoluteFloor}, N-1 unconditional)`);
2825
+ try {
2826
+ enqueueMemoryAnnouncement(this.controlDir, { kind: "gate", at, items: [`memory consolidation plan for scope ${inlineUntrusted(scope, 80)} REFUSED by the supersession fuse (${edgeTargets.size} targets over the ${ceiling} ceiling)`] });
2827
+ }
2828
+ catch (err) {
2829
+ this.discloseAnnounceFailure("consolidation fuse enqueue", err);
2830
+ }
2831
+ }
2832
+ const scanCeiling = scanRefusalCeiling(proposal.products.length, screened);
2833
+ if (scanRefused.length > scanCeiling) {
2834
+ wholeReasons.push(`scan-refusal fuse: ${scanRefused.length} of ${proposal.products.length} product(s) failed the write scan > ceiling ${scanCeiling} (ratio ${screened.scanRefusalRatioCap}, per-plan max ${screened.scanRefusalMaxPerPlan}) — ${scanRefused.map((r) => `#${r.index}: ${r.reason}`).join(" | ")}`);
2835
+ }
2821
2836
  const directed = [];
2822
2837
  const intents = proposal.intents ?? [];
2823
2838
  const targetIds = intents.map((it) => it.entryId);
@@ -2875,6 +2890,9 @@ export class MemoryEngine {
2875
2890
  directed.push({ intentRequestId: intent.requestId, op: "update", id: intent.entryId, baseRev: committed.rev, plannedPostRev: terminal.rev, entry: terminal, state: "pending" });
2876
2891
  }
2877
2892
  }
2893
+ if (wholeReasons.length === 0 && assembled.length === 0 && directed.length === 0) {
2894
+ wholeReasons.push(`empty plan after freeze: every product was refused (${refusedProducts.map((r) => `#${r.index}: ${r.reason}`).join(" | ")}) — a plan with nothing left to apply must not settle as a completed run (the vacuous completion would stamp the eligible set into the fingerprint and blind the incremental face)`);
2895
+ }
2878
2896
  if (wholeReasons.length > 0)
2879
2897
  throw new ConsolidationRefusedError("memory.consolidation_refused", "memory consolidation plan refused", wholeReasons);
2880
2898
  try {
@@ -2888,8 +2906,9 @@ export class MemoryEngine {
2888
2906
  throw err;
2889
2907
  throw new ConsolidationRefusedError("memory.consolidation_gate_corrupt", `memory consolidation refused: the gate store could not be re-read at the freeze boundary (fail-closed): ${err instanceof Error ? err.message : String(err)}`);
2890
2908
  }
2909
+ const freezeRefusedInputIds = [...new Set(refusedProducts.flatMap((r) => (proposal.products[r.index]?.inputs ?? []).map((i2) => i2.id)))].sort();
2891
2910
  const plan = {
2892
- v: 1,
2911
+ v: freezeRefusedInputIds.length > 0 ? 2 : 1,
2893
2912
  planId,
2894
2913
  scope,
2895
2914
  requestId: input.requestId,
@@ -2900,6 +2919,7 @@ export class MemoryEngine {
2900
2919
  ...(foldedOrigin !== undefined ? { foldedOrigin } : {}),
2901
2920
  products: assembled,
2902
2921
  productStates: Object.fromEntries(assembled.map((e) => [e.id, "pending"])),
2922
+ ...(freezeRefusedInputIds.length > 0 ? { freezeRefusedInputIds } : {}),
2903
2923
  directed,
2904
2924
  intents: intents.map((it) => ({ requestId: it.requestId, state: "pending" })),
2905
2925
  state: "open",
@@ -3099,6 +3119,7 @@ export class MemoryEngine {
3099
3119
  }
3100
3120
  writeConsolidationPlan(this.controlDir, plan);
3101
3121
  const currentlyEligible = await this.settleFingerprintFilter(plan.scope);
3122
+ const freezeRefused = new Set(plan.freezeRefusedInputIds ?? []);
3102
3123
  updateConsolidationGate(this.controlDir, (file) => {
3103
3124
  const row = file.scopes[plan.scope];
3104
3125
  if (row?.openPlanId !== plan.planId)
@@ -3111,6 +3132,8 @@ export class MemoryEngine {
3111
3132
  if (currentlyEligible !== undefined) {
3112
3133
  const fp = {};
3113
3134
  for (const [id, rev] of Object.entries(row.snapshot.eligible)) {
3135
+ if (freezeRefused.has(id))
3136
+ continue;
3114
3137
  if (!currentlyEligible(id, rev))
3115
3138
  continue;
3116
3139
  fp[id] = { rev };
@@ -3234,6 +3257,7 @@ export class MemoryEngine {
3234
3257
  }
3235
3258
  async settleGateForTerminalPlan(plan) {
3236
3259
  const currentlyEligible = await this.settleFingerprintFilter(plan.scope);
3260
+ const freezeRefused = new Set(plan.freezeRefusedInputIds ?? []);
3237
3261
  updateConsolidationGate(this.controlDir, (file) => {
3238
3262
  const row = file.scopes[plan.scope];
3239
3263
  if (row?.openPlanId !== plan.planId)
@@ -3247,6 +3271,8 @@ export class MemoryEngine {
3247
3271
  if (currentlyEligible !== undefined) {
3248
3272
  const fp = {};
3249
3273
  for (const [id, rev] of Object.entries(row.snapshot.eligible)) {
3274
+ if (freezeRefused.has(id))
3275
+ continue;
3250
3276
  if (!currentlyEligible(id, rev))
3251
3277
  continue;
3252
3278
  fp[id] = { rev };
@@ -3348,6 +3374,24 @@ export class MemoryEngine {
3348
3374
  throw new ConsolidationRefusedError("memory.consolidation_gate_corrupt", `memory consolidation retry refused: the intent-credential account cannot be trusted (fail-closed): ${err instanceof Error ? err.message : String(err)}`);
3349
3375
  }
3350
3376
  const pendingIntents = plan.intents.filter((i) => i.state === "pending" && standingCredentials[i.requestId] === undefined).map((i) => i.requestId);
3377
+ if (pendingIntents.length === 0) {
3378
+ throw new ConsolidationRefusedError("memory.consolidation_refused", `memory consolidation retry refused: plan ${planId} has nothing to re-judge — ` +
3379
+ (plan.intents.length === 0
3380
+ ? "it carries no intents (a conflicted product never rides a retry)"
3381
+ : "every unfulfilled intent already carries a credential (consumed by an earlier retry or a racing plan)") +
3382
+ ` — a plan with nothing to apply must not settle as a completed run (the vacuous completion would stamp the eligible set into the fingerprint and blind the incremental face); resolve with "discard" to release the seat.`);
3383
+ }
3384
+ const conflictProductInputIds = [];
3385
+ for (const p of plan.products) {
3386
+ if (plan.productStates[p.id] !== "conflict")
3387
+ continue;
3388
+ const inputs = p.frontmatter?.distilled?.inputs;
3389
+ if (!Array.isArray(inputs) || inputs.some((i) => typeof i?.["id"] !== "string")) {
3390
+ throw new ConsolidationRefusedError("memory.consolidation_refused", `memory consolidation retry refused: conflicted product ${p.id} carries no readable distilled attribution rows (every frozen product is minted with them — this is control-plane damage); the plan stays parked, resolve with "discard" (the bytes remain on the plan file for host review).`);
3391
+ }
3392
+ for (const i of inputs)
3393
+ conflictProductInputIds.push(i.id);
3394
+ }
3351
3395
  const face = this.committedAuditFace();
3352
3396
  const originalIntents = await this.reconstructIntentsForRetry(plan, pendingIntents, face);
3353
3397
  const retryPlanId = uuidv7();
@@ -3361,8 +3405,9 @@ export class MemoryEngine {
3361
3405
  file.scopes[plan.scope] = row;
3362
3406
  return { next: file, result: undefined };
3363
3407
  });
3408
+ const inheritedRefusals = [...new Set([...(plan.freezeRefusedInputIds ?? []), ...conflictProductInputIds])].sort();
3364
3409
  const retryPlan = {
3365
- v: 1,
3410
+ v: inheritedRefusals.length > 0 ? 2 : 1,
3366
3411
  planId: retryPlanId,
3367
3412
  scope: plan.scope,
3368
3413
  requestId: opts.requestId,
@@ -3371,6 +3416,7 @@ export class MemoryEngine {
3371
3416
  visibleMarked: false,
3372
3417
  products: [],
3373
3418
  productStates: {},
3419
+ ...(inheritedRefusals.length > 0 ? { freezeRefusedInputIds: inheritedRefusals } : {}),
3374
3420
  directed: originalIntents.directed,
3375
3421
  intents: pendingIntents.map((r) => ({ requestId: r, state: "pending" })),
3376
3422
  state: "open",
@@ -3450,6 +3496,35 @@ export class MemoryEngine {
3450
3496
  }
3451
3497
  return out;
3452
3498
  }
3499
+ readConsolidationPlanFoldEvidence(planId) {
3500
+ const read = readConsolidationPlan(this.controlDir, planId);
3501
+ if (read.state === "absent")
3502
+ return { state: "absent" };
3503
+ if (read.state === "corrupt")
3504
+ return { state: "corrupt", detail: read.detail };
3505
+ const p = read.plan;
3506
+ const targets = new Set();
3507
+ for (const prod of p.products) {
3508
+ if (p.productStates[prod.id] !== "applied")
3509
+ continue;
3510
+ const inputs = prod.frontmatter?.distilled?.inputs;
3511
+ const anchors = { scope: p.scope, requestId: p.requestId };
3512
+ if (inputs === undefined)
3513
+ return { state: "corrupt", detail: `applied product ${prod.id} carries no distilled.inputs (every frozen product is minted with its attribution rows)`, ...anchors };
3514
+ if (!Array.isArray(inputs))
3515
+ return { state: "corrupt", detail: `applied product ${prod.id} carries a non-array distilled.inputs`, ...anchors };
3516
+ for (const i of inputs) {
3517
+ if (typeof i !== "object" || i === null)
3518
+ return { state: "corrupt", detail: `applied product ${prod.id} carries a malformed distilled input row`, ...anchors };
3519
+ if (i.superseded !== true)
3520
+ continue;
3521
+ if (typeof i.id !== "string")
3522
+ return { state: "corrupt", detail: `applied product ${prod.id} carries a supersession edge without a string target id`, ...anchors };
3523
+ targets.add(i.id);
3524
+ }
3525
+ }
3526
+ return { state: "ok", scope: p.scope, requestId: p.requestId, planState: p.state, supersededTargets: [...targets].sort() };
3527
+ }
3453
3528
  readConsolidationGate(scope) {
3454
3529
  try {
3455
3530
  return readConsolidationGateFile(this.controlDir).scopes[scope];
@@ -3458,6 +3533,16 @@ export class MemoryEngine {
3458
3533
  return undefined;
3459
3534
  }
3460
3535
  }
3536
+ readConsolidationGateState(scope) {
3537
+ let row;
3538
+ try {
3539
+ row = readConsolidationGateFile(this.controlDir).scopes[scope];
3540
+ }
3541
+ catch (err) {
3542
+ return { state: "corrupt", detail: err instanceof Error ? err.message : String(err) };
3543
+ }
3544
+ return row !== undefined ? { state: "present", row } : { state: "absent" };
3545
+ }
3461
3546
  async readConsolidationFoldState(scope) {
3462
3547
  const face = this.committedAuditFace();
3463
3548
  const headers = await face.listHeaders([scope]);
@@ -3478,6 +3563,13 @@ export class MemoryEngine {
3478
3563
  get controlPlaneRoot() {
3479
3564
  return this.controlDir;
3480
3565
  }
3566
+ async listMemoryScopes() {
3567
+ const b = this.backend;
3568
+ if (typeof b.listScopes !== "function") {
3569
+ return { supported: false, reason: "the backend does not implement scope enumeration (listScopes) — an absent capability must never read as an empty scope list" };
3570
+ }
3571
+ return await b.listScopes();
3572
+ }
3481
3573
  committedAuditFace() {
3482
3574
  const b = this.backend;
3483
3575
  return b.restrictedAdoptionView?.({ audit: false }) ?? b.retrievalView?.() ?? this.backend;
@@ -1,6 +1,6 @@
1
1
  import { scopeDirName, type ChallengeEvent, type MemoryPartitionIncidentSink } from "./layout.js";
2
2
  import type { MemoryBundleImportPlan, MemoryImportReport } from "./export-bundle.js";
3
- import type { HarvestRejection, MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "./types.js";
3
+ import type { HarvestRejection, MemoryBackend, MemoryEntry, MemoryEntryHeader, MemoryScopeEnumeration, NotePatch, PatchReport, ScoredMemoryEntry } from "./types.js";
4
4
  /** Files/dirs the entry scan never treats as entries: the derived index, dotfiles (`.hydrate`,
5
5
  * transaction staging files), and anything not `.md`. */
6
6
  export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
@@ -756,6 +756,18 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
756
756
  * while a live holder exists ({@link txnInFlight}), which now covers adoption holds too.
757
757
  */
758
758
  private readScopeAdopting;
759
+ /**
760
+ * design/376 slice ③ (#437③) — the File leg's scope-enumeration capability face: every scope
761
+ * this store has hosted, derived from the store root's durable scope registry (the control
762
+ * plane's `scopes.json` — the one source that can invert a scope's sanitized directory label
763
+ * back to its name; a listing of raw subdirectories could not). Side-effect-free committed
764
+ * read; a corrupt/unreadable registry THROWS its fail-closed refusal — enumeration over an
765
+ * untrustworthy registry must never answer an empty (= clean-looking) list. Honest residual,
766
+ * disclosed: the registry records scopes as they are first touched THROUGH this control plane,
767
+ * so a hand-assembled tree's never-touched scope dirs appear here only once a caller first
768
+ * names them — store-level fact, session-roster-free either way.
769
+ */
770
+ listScopes(): MemoryScopeEnumeration;
759
771
  listHeaders(scopes: readonly string[]): Promise<MemoryEntryHeader[]>;
760
772
  private listHeadersFrom;
761
773
  getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
@@ -1780,6 +1780,9 @@ export class FileMemoryEngineBackend {
1780
1780
  lock.release();
1781
1781
  }
1782
1782
  }
1783
+ listScopes() {
1784
+ return { supported: true, scopes: Object.keys(registeredScopes(this.controlPlaneRoot)).sort() };
1785
+ }
1783
1786
  async listHeaders(scopes) {
1784
1787
  return this.listHeadersFrom(scopes, (s) => this.readScopeAdopting(s));
1785
1788
  }
@@ -1,4 +1,4 @@
1
- export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, } from "./engine.js";
1
+ export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, } from "./engine.js";
2
2
  export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, type MemoryGetDetails, } from "./tools.js";
3
3
  export { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_RECALL_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow, } from "./provenance-wording.js";
4
4
  export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
@@ -8,11 +8,12 @@ export { ControlPlaneCorruptError, deriveControlPlaneDir, deriveRepoControlPlane
8
8
  export { readV2HeaderHints, isInstructionEntry, type V2HeaderHints } from "./header-hints.js";
9
9
  export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation, type ParsedEntryFile } from "./frontmatter.js";
10
10
  export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
11
- export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
11
+ export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
12
12
  export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
13
13
  export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type RunMemoryConsolidationOptions, } from "./consolidation-driver.js";
14
+ export type { OriginClearanceRow, OriginClearanceEvent } from "./origin-clearance.js";
14
15
  export { MEMORY_ORIGIN_CAUSES } from "./types.js";
15
- export type { MemoryBackend, MemoryEntry, MemoryEntryFrontmatter, MemoryEntryOrigin, MemoryOriginCause, MemoryEntryDistilled, MemoryEntryDistilledInput, MemoryEntryHeader, ScoredMemoryEntry, NotePatch, PatchReport, MaterializedFile, MemorySessionHandle, HarvestReport, HarvestRejection, HarvestRejectionCode, MemoryAnnouncement, ScanFinding, } from "./types.js";
16
+ export type { MemoryBackend, MemoryEntry, MemoryEntryFrontmatter, MemoryEntryOrigin, MemoryOriginCause, MemoryScopeEnumeration, MemoryEntryDistilled, MemoryEntryDistilledInput, MemoryEntryHeader, ScoredMemoryEntry, NotePatch, PatchReport, MaterializedFile, MemorySessionHandle, HarvestReport, HarvestRejection, HarvestRejectionCode, MemoryAnnouncement, ScanFinding, } from "./types.js";
16
17
  export { memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, } from "./memory-backend-contract.js";
17
18
  export { SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, } from "./scope-contract.js";
18
19
  export { migrateScope, type MigrateScopeReport } from "./migrate.js";
@@ -303,21 +303,35 @@ export function dirCaseFolds(dir) {
303
303
  }
304
304
  function readScopesRecord(controlDir) {
305
305
  const path = join(controlDir, SCOPES_FILE);
306
+ const journalPath = `${path}.journal`;
306
307
  let raw;
308
+ let sourcePath = journalPath;
307
309
  try {
308
- raw = readFileSync(path, "utf8");
310
+ raw = readFileSync(journalPath, "utf8");
309
311
  }
310
312
  catch (err) {
311
- if (err.code === "ENOENT")
312
- return {};
313
- throw new ControlPlaneCorruptError(`scope registry unreadable (${err.code ?? "io error"}): ${path}`, { cause: err });
313
+ if (err.code !== "ENOENT") {
314
+ throw new ControlPlaneCorruptError(`scope registry journal unreadable (${err.code ?? "io error"}) — a committed next state may exist that cannot be proven: ${journalPath}`, { cause: err });
315
+ }
316
+ raw = undefined;
317
+ }
318
+ if (raw === undefined) {
319
+ sourcePath = path;
320
+ try {
321
+ raw = readFileSync(path, "utf8");
322
+ }
323
+ catch (err) {
324
+ if (err.code === "ENOENT")
325
+ return {};
326
+ throw new ControlPlaneCorruptError(`scope registry unreadable (${err.code ?? "io error"}): ${path}`, { cause: err });
327
+ }
314
328
  }
315
329
  let parsed;
316
330
  try {
317
331
  parsed = JSON.parse(raw);
318
332
  }
319
333
  catch {
320
- throw new ControlPlaneCorruptError(`scope registry is unparseable: ${path}`);
334
+ throw new ControlPlaneCorruptError(`scope registry is unparseable: ${sourcePath}`);
321
335
  }
322
336
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
323
337
  throw new ControlPlaneCorruptError(`scope registry has the wrong shape: ${path}`);
@@ -351,7 +365,7 @@ function lockedScopesUpdate(controlDir, fn) {
351
365
  const lock = `${file}.lock`;
352
366
  const token = acquireSidecarLock(lock, { onDeadline: "throw" });
353
367
  try {
354
- rollForwardSidecar(file, journal);
368
+ rollForwardStrictSidecar(file, journal);
355
369
  const rec = readScopesRecord(controlDir);
356
370
  const { next, result } = fn(rec);
357
371
  if (next !== undefined) {
@@ -246,6 +246,23 @@ export interface NotePatch {
246
246
  * Ignored on `update`/`delete`. */
247
247
  guard?: "absent";
248
248
  }
249
+ /**
250
+ * design/376 slice ③ (#437③) — a backend's scope-enumeration answer, DISCRIMINATED on purpose:
251
+ * "this backend cannot enumerate" and "this store hosts no scopes" must never share a spelling
252
+ * (an unsupported backend read as `[]` would make every audit sweep over it silently claim a
253
+ * clean store). The capability itself is an OPTIONAL backend face (`listScopes`, probed like the
254
+ * File class's audit faces): a backend without it is answered `supported: false` by
255
+ * `MemoryEngine.listMemoryScopes`, never an empty array. The File leg derives from the store
256
+ * root's durable scope registry; the SQL twins' leg is a store-contracts extension (separate
257
+ * ticket) — until it lands they simply do not implement the face.
258
+ */
259
+ export type MemoryScopeEnumeration = {
260
+ supported: true;
261
+ scopes: string[];
262
+ } | {
263
+ supported: false;
264
+ reason: string;
265
+ };
249
266
  /** Result of {@link MemoryBackend.applyPatches} — applied ops + per-id CAS conflicts (reported, not thrown). */
250
267
  export interface PatchReport {
251
268
  applied: Array<{
@@ -282,6 +282,40 @@ export declare const SUGGESTION_LEXICON: readonly string[];
282
282
  * through, so an ordinary rule text reads normally. The result is length-bounded.
283
283
  */
284
284
  export declare function escapeForDisclosure(value: unknown): string;
285
+ /**
286
+ * Render raw, untrusted COMMAND text for a display surface — the minimal safe baseline for the seats
287
+ * that carry post-rewrite command bytes verbatim ({@link SegmentRuleSuggestion.segment} is the
288
+ * motivating one; a card's own `command`/`args` echo has the same hazard).
289
+ *
290
+ * WHAT IT DOES, and why the two character families are not handled the same way:
291
+ * · `\p{Cf}` (the bidirectional controls U+061C/U+200E/U+200F/U+202A–U+202E/U+2066–U+2069 and the
292
+ * zero-width formatters ZWSP/ZWNJ/ZWJ/WORD JOINER/SOFT HYPHEN/BOM) is REMOVED. These occupy no
293
+ * width, so deleting them is what makes the rendered line agree with what a reader believes they
294
+ * are reading — a `rm -rf x` that DISPLAYS as something benign is exactly the deception the seat
295
+ * would otherwise hand on. This is the same family
296
+ * {@link import("./tool-policy.js").AskRequest.hasBidiControls} raises its warning bit for; the
297
+ * bit says "the raw bytes carry them", this function is what a surface renders instead.
298
+ * · Controls and line/paragraph separators (C0 incl. TAB and NEWLINE, DEL, C1, U+2028/U+2029) are
299
+ * FOLDED TO A SPACE with runs collapsed, not removed: they occupy width (or a whole line), and
300
+ * deleting them would glue two tokens into one word that reads as a different command. TAB is not
301
+ * excepted here — a one-line display value has no use for one, and a rule text's foldable-tab
302
+ * exception belongs to the VALIDATOR class, not to this one.
303
+ * The result is then capped by code point (never splitting a surrogate pair) with a `…` marker, and
304
+ * carries the shared authority-envelope neutralization every untrusted inline value gets — this text
305
+ * can land on a model-visible line as easily as on a human one.
306
+ *
307
+ * WHAT IT IS NOT. A DISPLAY baseline, never an identity channel and never adjudication input: the
308
+ * transform is LOSSY and NOT injective (two distinct commands can render identically — that is the
309
+ * price of a readable line). Correlate, match, store and adjudicate on the RAW bytes, which the
310
+ * carrying seats keep unchanged: this function neither changes what those seats contain nor what any
311
+ * of them means. A caller that must instead tell two hostile texts APART wants
312
+ * {@link escapeForDisclosure} (which escapes rather than folds, so nothing collides) or
313
+ * {@link hasUnrenderableCharacters} (refuse instead of render).
314
+ *
315
+ * @param text the raw untrusted text (a `String(x)` is applied to anything that is not one).
316
+ * @param maxLen code-point cap for the rendered result; defaults to the disclosure-line budget.
317
+ */
318
+ export declare function renderUntrustedCommandText(text: unknown, maxLen?: number): string;
285
319
  /**
286
320
  * Does this text carry a character of the class {@link escapeForDisclosure} exists for — the class
287
321
  * {@link CONTROL_CHARS_RE} names? Asked by a caller that must REFUSE such a value rather than render
@@ -488,7 +522,14 @@ export interface SegmentRuleSuggestion {
488
522
  readonly command: string;
489
523
  /** The (folded) segment this suggestion came from. Raw post-rewrite command bytes — a substring
490
524
  * of the same call's `args`/`command` values riding beside it on every surface that carries
491
- * this seat — so render it with contextual escaping, exactly like those. */
525
+ * this seat — so render it with contextual escaping, exactly like those.
526
+ *
527
+ * RAW is the contract here and does not change: this seat carries the bytes, because the bytes are
528
+ * what a rule would be minted from and what a correlation keys on. The rendering side of that
529
+ * contract is {@link renderUntrustedCommandText} — the shipped display baseline (bidi and
530
+ * zero-width controls removed, controls/line separators folded, length capped, envelope-neutralized)
531
+ * a surface can adopt instead of writing its own, since raw command bytes displayed unprocessed can
532
+ * show a reader something other than what they authorize. */
492
533
  readonly segment: string;
493
534
  }
494
535
  /**