@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.
- package/CHANGELOG.md +48 -0
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +8 -1
- package/dist/core/governance-codes.d.ts +37 -10
- package/dist/core/governance-codes.js +52 -1
- package/dist/core/memory-engine/consolidation-driver.d.ts +6 -2
- package/dist/core/memory-engine/consolidation-driver.js +54 -5
- package/dist/core/memory-engine/consolidation.d.ts +73 -1
- package/dist/core/memory-engine/consolidation.js +21 -1
- package/dist/core/memory-engine/engine.d.ts +97 -8
- package/dist/core/memory-engine/engine.js +112 -20
- package/dist/core/memory-engine/file-backend.d.ts +13 -1
- package/dist/core/memory-engine/file-backend.js +3 -0
- package/dist/core/memory-engine/index.d.ts +4 -3
- package/dist/core/memory-engine/layout.js +20 -6
- package/dist/core/memory-engine/types.d.ts +17 -0
- package/dist/core/permission-rule-model.d.ts +42 -1
- package/dist/core/permission-rule-model.js +12 -0
- package/dist/core/runner/prepare-task.js +3 -3
- package/dist/core/runner/runtask.js +4 -4
- package/dist/core/tool-policy.d.ts +58 -0
- package/dist/core/tool-policy.js +80 -1
- package/dist/core/types.d.ts +45 -20
- package/dist/core/types.js +4 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +1771 -1
|
@@ -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 (
|
|
960
|
-
*
|
|
961
|
-
* (an input's rev moved / an
|
|
962
|
-
*
|
|
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.
|
|
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 (
|
|
2765
|
-
|
|
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
|
-
|
|
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(
|
|
310
|
+
raw = readFileSync(journalPath, "utf8");
|
|
309
311
|
}
|
|
310
312
|
catch (err) {
|
|
311
|
-
if (err.code
|
|
312
|
-
|
|
313
|
-
|
|
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: ${
|
|
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
|
-
|
|
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
|
/**
|