@joshuaswarren/openclaw-engram 9.0.29 → 9.0.30

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/README.md CHANGED
@@ -34,7 +34,7 @@ AI agents forget everything between conversations. Engram fixes that.
34
34
  - **Objective-state recall** — Engram can now store normalized file, process, and tool outcomes and, when `objectiveStateRecallEnabled` is enabled, inject the most relevant objective-state snapshots back into recall context as a separate `Objective State` section.
35
35
  - **Causal trajectory graph foundation** — Engram can now persist typed `goal -> action -> observation -> outcome -> follow-up` chains when `causalTrajectoryMemoryEnabled` is enabled and, with `actionGraphRecallEnabled`, emit deterministic action-conditioned edges into the causal graph for later trajectory-aware retrieval.
36
36
  - **Causal trajectory recall** — Engram can now, when `causalTrajectoryRecallEnabled` is enabled, inject prompt-relevant causal chains back into recall context as a separate `Causal Trajectories` section with lightweight match explainability.
37
- - **Trust-zone store foundation** — Engram can now, when `trustZonesEnabled` is enabled, persist typed quarantine, working, and trusted records with provenance metadata into a dedicated trust-zone store for later promotion and defense slices.
37
+ - **Trust-zone promotion path** — Engram can now, when `trustZonesEnabled` and `quarantinePromotionEnabled` are enabled, persist typed quarantine, working, and trusted records, plan explicit promotions, block direct `quarantine -> trusted` jumps, and require anchored provenance before promoting risky working records into `trusted`.
38
38
  - **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
39
39
 
40
40
  ## Quick Start
@@ -160,6 +160,8 @@ openclaw engram benchmark-import <path> # Import a validated benchmark pack
160
160
  openclaw engram benchmark-ci-gate # Compare base vs candidate eval stores and fail on regressions
161
161
  openclaw engram objective-state-status # Objective-state snapshot counts and latest stored snapshot
162
162
  openclaw engram causal-trajectory-status # Causal-trajectory record counts and latest stored chain
163
+ openclaw engram trust-zone-status # Trust-zone record counts and latest stored record
164
+ openclaw engram trust-zone-promote # Dry-run or apply a trust-zone promotion with provenance enforcement
163
165
  openclaw engram conversation-index-health # Conversation index status
164
166
  openclaw engram graph-health # Entity graph status
165
167
  openclaw engram tier-status # Hot/cold tier metrics
@@ -190,8 +192,8 @@ Key settings:
190
192
  | `causalTrajectoryStoreDir` | `{memoryDir}/state/causal-trajectories` | Root directory for causal-trajectory records |
191
193
  | `causalTrajectoryRecallEnabled` | `false` | Inject prompt-relevant causal trajectories into recall context |
192
194
  | `actionGraphRecallEnabled` | `false` | Write action-conditioned causal-stage edges from typed trajectory records into the causal graph |
193
- | `trustZonesEnabled` | `false` | Enable the trust-zone memory foundation for quarantine, working, and trusted records |
194
- | `quarantinePromotionEnabled` | `false` | Reserve future promotion flows from quarantine into higher-trust zones |
195
+ | `trustZonesEnabled` | `false` | Enable the trust-zone memory foundation and operator-facing promotion path for quarantine, working, and trusted records |
196
+ | `quarantinePromotionEnabled` | `false` | Allow explicit trust-zone promotions such as `quarantine -> working` and guarded `working -> trusted` |
195
197
  | `trustZoneStoreDir` | `{memoryDir}/state/trust-zones` | Root directory for trust-zone records |
196
198
 
197
199
  Full reference: [Config Reference](docs/config-reference.md)
package/dist/index.js CHANGED
@@ -26294,6 +26294,129 @@ function validateTrustZoneRecord(raw) {
26294
26294
  metadata: validateMetadata3(raw.metadata)
26295
26295
  };
26296
26296
  }
26297
+ async function recordTrustZoneRecord(options) {
26298
+ const rootDir = resolveTrustZoneStoreDir(options.memoryDir, options.trustZoneStoreDir);
26299
+ const validated = validateTrustZoneRecord(options.record);
26300
+ const day = recordStoreDay(validated.recordedAt);
26301
+ const zoneDir = path53.join(rootDir, "zones", validated.zone, day);
26302
+ const filePath = path53.join(zoneDir, `${validated.recordId}.json`);
26303
+ await mkdir35(zoneDir, { recursive: true });
26304
+ await writeFile32(filePath, JSON.stringify(validated, null, 2), "utf8");
26305
+ return filePath;
26306
+ }
26307
+ function hasAnchoredProvenance(record) {
26308
+ return Boolean(record.provenance.sourceId && record.provenance.evidenceHash);
26309
+ }
26310
+ function buildPromotionRecordId(sourceRecordId, targetZone, recordedAt) {
26311
+ const suffix = recordedAt.replace(/[^0-9]/g, "").slice(0, 14);
26312
+ return `${sourceRecordId}-${targetZone}-${suffix}`;
26313
+ }
26314
+ function dedupeStrings(values) {
26315
+ const out = values.filter((value) => typeof value === "string" && value.length > 0);
26316
+ if (out.length === 0) return void 0;
26317
+ return [...new Set(out)];
26318
+ }
26319
+ function planTrustZonePromotion(options) {
26320
+ const { record, targetZone } = options;
26321
+ const reasons = [];
26322
+ const provenanceAnchored = hasAnchoredProvenance(record);
26323
+ if (record.zone === targetZone) {
26324
+ reasons.push(`record is already in the ${targetZone} zone`);
26325
+ }
26326
+ if (record.zone === "trusted") {
26327
+ reasons.push("trusted records are terminal and cannot be promoted again");
26328
+ }
26329
+ if (record.zone === "quarantine" && targetZone === "trusted") {
26330
+ reasons.push("quarantine records must pass through working before trusted promotion");
26331
+ }
26332
+ if (record.zone === "working" && targetZone === "quarantine") {
26333
+ reasons.push("working records cannot be demoted back into quarantine in this promotion path");
26334
+ }
26335
+ if (record.zone === "quarantine" && targetZone !== "working") {
26336
+ reasons.push("quarantine promotions only support the working zone");
26337
+ }
26338
+ if (record.zone === "working" && targetZone !== "trusted") {
26339
+ reasons.push("working promotions only support the trusted zone");
26340
+ }
26341
+ if (targetZone === "trusted" && ["tool_output", "web_content", "subagent_trace"].includes(record.provenance.sourceClass) && provenanceAnchored !== true) {
26342
+ reasons.push("trusted promotion for external/tool-derived provenance requires both provenance.sourceId and provenance.evidenceHash");
26343
+ }
26344
+ return {
26345
+ allowed: reasons.length === 0,
26346
+ reasons,
26347
+ sourceRecordId: record.recordId,
26348
+ sourceZone: record.zone,
26349
+ targetZone,
26350
+ provenanceAnchored
26351
+ };
26352
+ }
26353
+ async function findTrustZoneRecordById(options) {
26354
+ const { records } = await readTrustZoneRecords(options);
26355
+ records.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
26356
+ return records.find((record) => record.recordId === options.recordId) ?? null;
26357
+ }
26358
+ async function promoteTrustZoneRecord(options) {
26359
+ if (options.enabled !== true) {
26360
+ throw new Error("trust zone promotion requires trustZonesEnabled=true");
26361
+ }
26362
+ if (options.promotionEnabled !== true) {
26363
+ throw new Error("trust zone promotion requires quarantinePromotionEnabled=true");
26364
+ }
26365
+ const sourceRecord = await findTrustZoneRecordById({
26366
+ memoryDir: options.memoryDir,
26367
+ trustZoneStoreDir: options.trustZoneStoreDir,
26368
+ recordId: assertSafePathSegment(assertString2(options.sourceRecordId, "sourceRecordId"), "sourceRecordId")
26369
+ });
26370
+ if (!sourceRecord) {
26371
+ throw new Error(`source trust-zone record not found: ${options.sourceRecordId}`);
26372
+ }
26373
+ const plan = planTrustZonePromotion({
26374
+ record: sourceRecord,
26375
+ targetZone: options.targetZone
26376
+ });
26377
+ if (!plan.allowed) {
26378
+ throw new Error(`trust-zone promotion denied: ${plan.reasons.join("; ")}`);
26379
+ }
26380
+ const recordedAt = assertIsoRecordedAt(assertString2(options.recordedAt, "recordedAt"));
26381
+ const promotionReason = assertString2(options.promotionReason, "promotionReason");
26382
+ const nextRecord = {
26383
+ schemaVersion: 1,
26384
+ recordId: buildPromotionRecordId(sourceRecord.recordId, options.targetZone, recordedAt),
26385
+ zone: options.targetZone,
26386
+ recordedAt,
26387
+ kind: sourceRecord.kind,
26388
+ summary: optionalString(options.summary) ?? sourceRecord.summary,
26389
+ provenance: sourceRecord.provenance,
26390
+ promotedFromZone: sourceRecord.zone,
26391
+ entityRefs: sourceRecord.entityRefs,
26392
+ tags: dedupeStrings([...sourceRecord.tags ?? [], "promotion"]),
26393
+ metadata: {
26394
+ ...sourceRecord.metadata ?? {},
26395
+ sourceRecordId: sourceRecord.recordId,
26396
+ promotionReason
26397
+ }
26398
+ };
26399
+ if (options.dryRun === true) {
26400
+ return {
26401
+ plan,
26402
+ wroteRecord: false,
26403
+ record: nextRecord,
26404
+ sourceRecord
26405
+ };
26406
+ }
26407
+ const filePath = await recordTrustZoneRecord({
26408
+ memoryDir: options.memoryDir,
26409
+ trustZoneStoreDir: options.trustZoneStoreDir,
26410
+ record: nextRecord
26411
+ });
26412
+ return {
26413
+ plan,
26414
+ wroteRecord: true,
26415
+ record: nextRecord,
26416
+ filePath,
26417
+ sourceRecord
26418
+ };
26419
+ }
26297
26420
  async function readTrustZoneRecords(options) {
26298
26421
  const rootDir = resolveTrustZoneStoreDir(options.memoryDir, options.trustZoneStoreDir);
26299
26422
  const files = await listJsonFiles(path53.join(rootDir, "zones"));
@@ -26546,6 +26669,24 @@ async function runTrustZoneStatusCliCommand(options) {
26546
26669
  promotionEnabled: options.quarantinePromotionEnabled
26547
26670
  });
26548
26671
  }
26672
+ async function runTrustZonePromoteCliCommand(options) {
26673
+ const result = await promoteTrustZoneRecord({
26674
+ memoryDir: options.memoryDir,
26675
+ trustZoneStoreDir: options.trustZoneStoreDir,
26676
+ enabled: options.trustZonesEnabled,
26677
+ promotionEnabled: options.quarantinePromotionEnabled,
26678
+ sourceRecordId: options.sourceRecordId,
26679
+ targetZone: options.targetZone,
26680
+ recordedAt: options.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
26681
+ promotionReason: options.promotionReason,
26682
+ summary: options.summary,
26683
+ dryRun: options.dryRun === true
26684
+ });
26685
+ return {
26686
+ ...result,
26687
+ dryRun: options.dryRun === true
26688
+ };
26689
+ }
26549
26690
  async function runSessionCheckCliCommand(options) {
26550
26691
  return analyzeSessionIntegrity({ memoryDir: options.memoryDir });
26551
26692
  }
@@ -27714,6 +27855,23 @@ function registerCli(api, orchestrator) {
27714
27855
  console.log(JSON.stringify(status, null, 2));
27715
27856
  console.log("OK");
27716
27857
  });
27858
+ cmd.command("trust-zone-promote").description("Dry-run or apply a trust-zone promotion with provenance enforcement").requiredOption("--record-id <recordId>", "Source trust-zone record id").requiredOption("--target-zone <targetZone>", "Promotion target zone (working|trusted)").requiredOption("--reason <reason>", "Human-readable promotion reason").option("--recorded-at <isoTimestamp>", "Promotion timestamp (defaults to now)").option("--summary <summary>", "Optional replacement summary for the promoted record").option("--dry-run", "Show the promotion plan without writing the promoted record").action(async (...args) => {
27859
+ const options = args[0] ?? {};
27860
+ const result = await runTrustZonePromoteCliCommand({
27861
+ memoryDir: orchestrator.config.memoryDir,
27862
+ trustZoneStoreDir: orchestrator.config.trustZoneStoreDir,
27863
+ trustZonesEnabled: orchestrator.config.trustZonesEnabled,
27864
+ quarantinePromotionEnabled: orchestrator.config.quarantinePromotionEnabled,
27865
+ sourceRecordId: String(options.recordId ?? ""),
27866
+ targetZone: String(options.targetZone ?? ""),
27867
+ promotionReason: String(options.reason ?? ""),
27868
+ recordedAt: typeof options.recordedAt === "string" ? options.recordedAt : void 0,
27869
+ summary: typeof options.summary === "string" ? options.summary : void 0,
27870
+ dryRun: options.dryRun === true
27871
+ });
27872
+ console.log(JSON.stringify(result, null, 2));
27873
+ console.log("OK");
27874
+ });
27717
27875
  cmd.command("conversation-index-health").description("Show conversation index backend health and index stats").action(async () => {
27718
27876
  const health = await runConversationIndexHealthCliCommand(orchestrator);
27719
27877
  console.log(JSON.stringify(health, null, 2));