@remnic/core 9.8.0 → 9.9.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.
Files changed (56) hide show
  1. package/dist/access-admin-ops-surface.d.ts +1 -1
  2. package/dist/access-admin-ops-surface.js +2 -2
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.js +2 -2
  5. package/dist/access-cli.js +6 -6
  6. package/dist/access-http.d.ts +1 -1
  7. package/dist/access-http.js +4 -4
  8. package/dist/access-identity-continuity-surface.js +2 -2
  9. package/dist/access-lcm-surface.d.ts +1 -1
  10. package/dist/access-lcm-surface.js +2 -2
  11. package/dist/access-mcp.d.ts +1 -1
  12. package/dist/access-mcp.js +3 -3
  13. package/dist/access-observe-write-surface.d.ts +1 -1
  14. package/dist/access-observe-write-surface.js +2 -2
  15. package/dist/access-operations-batch.js +2 -2
  16. package/dist/access-operations.d.ts +1 -1
  17. package/dist/access-operations.js +3 -3
  18. package/dist/access-recall-concurrency.d.ts +1 -1
  19. package/dist/access-recall-concurrency.js +2 -2
  20. package/dist/access-recall-response.d.ts +1 -1
  21. package/dist/access-recall-response.js +2 -2
  22. package/dist/access-recall-surface.d.ts +1 -1
  23. package/dist/access-recall-surface.js +2 -2
  24. package/dist/{access-service-DEL4qCrc.d.ts → access-service-DbZ1G3zV.d.ts} +4 -4
  25. package/dist/access-service.d.ts +1 -1
  26. package/dist/access-service.js +2 -2
  27. package/dist/access-surface-catalog.d.ts +1 -1
  28. package/dist/{chunk-2EJOEICD.js → chunk-BI2OJF6J.js} +2 -2
  29. package/dist/{chunk-IDZFBSHN.js → chunk-OGCAYLNA.js} +2 -2
  30. package/dist/{chunk-SICXL4MK.js → chunk-PJSK6OJM.js} +44 -8
  31. package/dist/chunk-PJSK6OJM.js.map +1 -0
  32. package/dist/{chunk-76GLCM57.js → chunk-PMUD7ERF.js} +3 -3
  33. package/dist/{chunk-TDTXKOAJ.js → chunk-XQDRKZ7V.js} +7 -5
  34. package/dist/chunk-XQDRKZ7V.js.map +1 -0
  35. package/dist/{chunk-IYNGIMVG.js → chunk-ZVAYK7GU.js} +3 -3
  36. package/dist/{cli-C_pqqTCw.d.ts → cli-5tNLVTvJ.d.ts} +1 -1
  37. package/dist/cli.d.ts +2 -2
  38. package/dist/cli.js +5 -5
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js +6 -6
  41. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  42. package/dist/orchestrator.js +6 -6
  43. package/dist/write-quarantine.d.ts +18 -3
  44. package/dist/write-quarantine.js +1 -1
  45. package/package.json +2 -2
  46. package/src/access-observe-quarantine.test.ts +52 -0
  47. package/src/access-observe-write-surface.ts +12 -5
  48. package/src/access-service.ts +4 -4
  49. package/src/write-quarantine.test.ts +52 -0
  50. package/src/write-quarantine.ts +56 -7
  51. package/dist/chunk-SICXL4MK.js.map +0 -1
  52. package/dist/chunk-TDTXKOAJ.js.map +0 -1
  53. /package/dist/{chunk-2EJOEICD.js.map → chunk-BI2OJF6J.js.map} +0 -0
  54. /package/dist/{chunk-IDZFBSHN.js.map → chunk-OGCAYLNA.js.map} +0 -0
  55. /package/dist/{chunk-76GLCM57.js.map → chunk-PMUD7ERF.js.map} +0 -0
  56. /package/dist/{chunk-IYNGIMVG.js.map → chunk-ZVAYK7GU.js.map} +0 -0
@@ -19,7 +19,7 @@ import {
19
19
  } from "./chunk-CXZQOVIE.js";
20
20
  import {
21
21
  RelayMissionStoreError
22
- } from "./chunk-IDZFBSHN.js";
22
+ } from "./chunk-OGCAYLNA.js";
23
23
  import {
24
24
  executeResolution,
25
25
  isValidResolutionVerb
@@ -37,7 +37,7 @@ import {
37
37
  } from "./chunk-KF5EUVKQ.js";
38
38
  import {
39
39
  CorrectionContractError
40
- } from "./chunk-TDTXKOAJ.js";
40
+ } from "./chunk-XQDRKZ7V.js";
41
41
  import {
42
42
  isTrustZoneName
43
43
  } from "./chunk-JGSKJHF7.js";
@@ -3132,4 +3132,4 @@ function positiveIntQueryParam(value, label) {
3132
3132
  export {
3133
3133
  EngramAccessHttpServer
3134
3134
  };
3135
- //# sourceMappingURL=chunk-IYNGIMVG.js.map
3135
+ //# sourceMappingURL=chunk-ZVAYK7GU.js.map
@@ -17,7 +17,7 @@ import { WorkProjectStatus, WorkTaskStatus, WorkTaskPriority } from './work/type
17
17
  import { RoutePatternType } from './routing/engine.js';
18
18
  import { TailscaleSyncOptions } from './network/tailscale.js';
19
19
  import { DashboardStatus } from './dashboard-runtime.js';
20
- import { F as EngramAccessService } from './access-service-DEL4qCrc.js';
20
+ import { F as EngramAccessService } from './access-service-DbZ1G3zV.js';
21
21
  import { ResolveSecretRefFn } from './resolve-auth-token.js';
22
22
  import { CompatRunner, CompatReport } from './compat/types.js';
23
23
  import { EvalBaselineDeltaReport, EvalBaselineSnapshot, EvalCiGateReport, EvalBenchmarkPackSummary, EvalHarnessStatus, EvalStoredBaselineCiGateReport } from './evals.js';
package/dist/cli.d.ts CHANGED
@@ -11,12 +11,12 @@ import './orchestrator-CQJ8U7En.js';
11
11
  import './replay/runner.js';
12
12
  import './replay/types.js';
13
13
  import './pipeline-Cn-kvYeV.js';
14
- export { A as AccessHttpServeCliCommandOptions, a as ArchiveObservationsCliCommandOptions, B as BulkImportCliCommandOptions, C as CliApi, b as CliCommand, c as CliProgram, d as CompatCliCommandOptions, e as ConnectorsRunCliOutputOptions, f as ConversationIndexHealthCliOrchestrator, D as DashboardStartCliCommandOptions, g as DedupeCandidate, E as ExactDedupePlan, G as GraphHealthCliCommandOptions, M as MemoryActionAuditCliCommandOptions, h as MemoryActionAuditCliNamespaceSummary, i as MemoryActionAuditCliReport, j as MemoryGovernanceCliCommandOptions, k as MemoryGovernanceReportCliCommandOptions, l as MemoryGovernanceRestoreCliCommandOptions, m as MemoryReviewDispositionCliCommandOptions, n as MemoryTimelineCliCommandOptions, o as MigrateCliOrchestrator, q as MigrateCliReport, s as MigrateObservationsCliCommandOptions, P as PolicyDiffCliReport, t as PolicyDiffEntry, u as PolicyRollbackCliReport, v as PolicyStatusCliReport, w as PolicyTuningCliOrchestrator, R as RebuildMemoryLifecycleLedgerCliCommandOptions, x as RebuildMemoryProjectionCliCommandOptions, y as RebuildObservationsCliCommandOptions, z as RepairMemoryProjectionCliCommandOptions, F as ReplayCliCommandOptions, H as ReplayCliOrchestrator, I as RouteCliCommandOptions, S as SessionIntegrityCliCommandOptions, J as SessionRepairCliCommandOptions, T as TailscaleStatusCliCommandOptions, K as TailscaleSyncCliCommandOptions, L as TierMigrateCliAdapter, N as TierMigrateCoordinatorCliAdapter, O as TierMigrateLegacyCliAdapter, Q as TierStatusCliAdapter, U as TierStatusCoordinatorCliAdapter, V as TierStatusLegacyCliAdapter, W as TrainingExportCliCommandOptions, X as VerifyMemoryProjectionCliCommandOptions, Y as WebDavServeCliCommandOptions, Z as WorkProjectCliCommandOptions, _ as WorkTaskCliCommandOptions, $ as emitConnectorsRunCliResult, a0 as filterNormalMemorySearchResults, a1 as hasDestructivePurgeFailures, a2 as isNormalRetrievalVisibleMemory, a3 as listMemoryMarkdownFilePaths, a4 as parseDurationToMs, a5 as parseStrictCliDate, a6 as planAggressiveDuplicateDeletions, a7 as planExactDuplicateDeletions, a8 as registerCli, a9 as resolveAccessPrincipalOverride, aa as resolveMemoryDirForNamespace, ab as runAbstractionNodeStatusCliCommand, ac as runAccessHttpServeCliCommand, ad as runAccessHttpStatusCliCommand, ae as runAccessHttpStopCliCommand, af as runAccessMcpServeCliCommand, ag as runArchiveObservationsCliCommand, ah as runBenchmarkBaselineReportCliCommand, ai as runBenchmarkBaselineSnapshotCliCommand, aj as runBenchmarkCiGateCliCommand, ak as runBenchmarkImportCliCommand, al as runBenchmarkStatusCliCommand, am as runBenchmarkStoredBaselineCiGateCliCommand, an as runBenchmarkValidateCliCommand, r as runBulkImportCliCommand, ao as runCausalTrajectoryStatusCliCommand, ap as runCommitmentLifecycleCliCommand, aq as runCommitmentRecordCliCommand, ar as runCommitmentSetStateCliCommand, as as runCommitmentStatusCliCommand, at as runCompatCliCommand, au as runCompoundingPromoteCliCommand, av as runConversationIndexHealthCliCommand, aw as runConversationIndexInspectCliCommand, ax as runConversationIndexRebuildCliCommand, ay as runCueAnchorStatusCliCommand, az as runDashboardStartCliCommand, aA as runDashboardStatusCliCommand, aB as runDashboardStopCliCommand, aC as runGraphHealthCliCommand, aD as runHarmonicSearchCliCommand, aE as runMemoryActionAuditCliCommand, aF as runMemoryGovernanceCliCommand, aG as runMemoryGovernanceReportCliCommand, aH as runMemoryGovernanceRestoreCliCommand, aI as runMemoryReviewDispositionCliCommand, aJ as runMemoryTimelineCliCommand, aK as runMigrateNormalizeFrontmatterCliCommand, aL as runMigrateObservationsCliCommand, aM as runMigrateRechunkCliCommand, aN as runMigrateReextractCliCommand, aO as runMigrateRescoreImportanceCliCommand, aP as runObjectiveStateStatusCliCommand, aQ as runPolicyDiffCliCommand, aR as runPolicyRollbackCliCommand, aS as runPolicyStatusCliCommand, aT as runRebuildMemoryLifecycleLedgerCliCommand, aU as runRebuildMemoryProjectionCliCommand, aV as runRebuildObservationsCliCommand, aW as runRepairMemoryProjectionCliCommand, aX as runReplayCliCommand, aY as runResumeBundleBuildCliCommand, aZ as runResumeBundleRecordCliCommand, a_ as runResumeBundleStatusCliCommand, a$ as runRouteCliCommand, b0 as runSemanticRulePromoteCliCommand, b1 as runSemanticRuleVerifyCliCommand, b2 as runSessionCheckCliCommand, b3 as runSessionRepairCliCommand, b4 as runTailscaleStatusCliCommand, b5 as runTailscaleSyncCliCommand, b6 as runTierMigrateCliCommand, b7 as runTierStatusCliCommand, b8 as runTrainingExportCliCommand, b9 as runTrustZoneDemoSeedCliCommand, ba as runTrustZonePromoteCliCommand, bb as runTrustZoneStatusCliCommand, bc as runUtilityLearningCliCommand, bd as runUtilityLearningStatusCliCommand, be as runUtilityTelemetryRecordCliCommand, bf as runUtilityTelemetryStatusCliCommand, bg as runVerifiedRecallSearchCliCommand, bh as runVerifyMemoryProjectionCliCommand, bi as runWebDavServeCliCommand, bj as runWebDavStopCliCommand, bk as runWorkProductRecallSearchCliCommand, bl as runWorkProductRecordCliCommand, bm as runWorkProductStatusCliCommand, bn as runWorkProjectCliCommand, bo as runWorkTaskCliCommand } from './cli-C_pqqTCw.js';
14
+ export { A as AccessHttpServeCliCommandOptions, a as ArchiveObservationsCliCommandOptions, B as BulkImportCliCommandOptions, C as CliApi, b as CliCommand, c as CliProgram, d as CompatCliCommandOptions, e as ConnectorsRunCliOutputOptions, f as ConversationIndexHealthCliOrchestrator, D as DashboardStartCliCommandOptions, g as DedupeCandidate, E as ExactDedupePlan, G as GraphHealthCliCommandOptions, M as MemoryActionAuditCliCommandOptions, h as MemoryActionAuditCliNamespaceSummary, i as MemoryActionAuditCliReport, j as MemoryGovernanceCliCommandOptions, k as MemoryGovernanceReportCliCommandOptions, l as MemoryGovernanceRestoreCliCommandOptions, m as MemoryReviewDispositionCliCommandOptions, n as MemoryTimelineCliCommandOptions, o as MigrateCliOrchestrator, q as MigrateCliReport, s as MigrateObservationsCliCommandOptions, P as PolicyDiffCliReport, t as PolicyDiffEntry, u as PolicyRollbackCliReport, v as PolicyStatusCliReport, w as PolicyTuningCliOrchestrator, R as RebuildMemoryLifecycleLedgerCliCommandOptions, x as RebuildMemoryProjectionCliCommandOptions, y as RebuildObservationsCliCommandOptions, z as RepairMemoryProjectionCliCommandOptions, F as ReplayCliCommandOptions, H as ReplayCliOrchestrator, I as RouteCliCommandOptions, S as SessionIntegrityCliCommandOptions, J as SessionRepairCliCommandOptions, T as TailscaleStatusCliCommandOptions, K as TailscaleSyncCliCommandOptions, L as TierMigrateCliAdapter, N as TierMigrateCoordinatorCliAdapter, O as TierMigrateLegacyCliAdapter, Q as TierStatusCliAdapter, U as TierStatusCoordinatorCliAdapter, V as TierStatusLegacyCliAdapter, W as TrainingExportCliCommandOptions, X as VerifyMemoryProjectionCliCommandOptions, Y as WebDavServeCliCommandOptions, Z as WorkProjectCliCommandOptions, _ as WorkTaskCliCommandOptions, $ as emitConnectorsRunCliResult, a0 as filterNormalMemorySearchResults, a1 as hasDestructivePurgeFailures, a2 as isNormalRetrievalVisibleMemory, a3 as listMemoryMarkdownFilePaths, a4 as parseDurationToMs, a5 as parseStrictCliDate, a6 as planAggressiveDuplicateDeletions, a7 as planExactDuplicateDeletions, a8 as registerCli, a9 as resolveAccessPrincipalOverride, aa as resolveMemoryDirForNamespace, ab as runAbstractionNodeStatusCliCommand, ac as runAccessHttpServeCliCommand, ad as runAccessHttpStatusCliCommand, ae as runAccessHttpStopCliCommand, af as runAccessMcpServeCliCommand, ag as runArchiveObservationsCliCommand, ah as runBenchmarkBaselineReportCliCommand, ai as runBenchmarkBaselineSnapshotCliCommand, aj as runBenchmarkCiGateCliCommand, ak as runBenchmarkImportCliCommand, al as runBenchmarkStatusCliCommand, am as runBenchmarkStoredBaselineCiGateCliCommand, an as runBenchmarkValidateCliCommand, r as runBulkImportCliCommand, ao as runCausalTrajectoryStatusCliCommand, ap as runCommitmentLifecycleCliCommand, aq as runCommitmentRecordCliCommand, ar as runCommitmentSetStateCliCommand, as as runCommitmentStatusCliCommand, at as runCompatCliCommand, au as runCompoundingPromoteCliCommand, av as runConversationIndexHealthCliCommand, aw as runConversationIndexInspectCliCommand, ax as runConversationIndexRebuildCliCommand, ay as runCueAnchorStatusCliCommand, az as runDashboardStartCliCommand, aA as runDashboardStatusCliCommand, aB as runDashboardStopCliCommand, aC as runGraphHealthCliCommand, aD as runHarmonicSearchCliCommand, aE as runMemoryActionAuditCliCommand, aF as runMemoryGovernanceCliCommand, aG as runMemoryGovernanceReportCliCommand, aH as runMemoryGovernanceRestoreCliCommand, aI as runMemoryReviewDispositionCliCommand, aJ as runMemoryTimelineCliCommand, aK as runMigrateNormalizeFrontmatterCliCommand, aL as runMigrateObservationsCliCommand, aM as runMigrateRechunkCliCommand, aN as runMigrateReextractCliCommand, aO as runMigrateRescoreImportanceCliCommand, aP as runObjectiveStateStatusCliCommand, aQ as runPolicyDiffCliCommand, aR as runPolicyRollbackCliCommand, aS as runPolicyStatusCliCommand, aT as runRebuildMemoryLifecycleLedgerCliCommand, aU as runRebuildMemoryProjectionCliCommand, aV as runRebuildObservationsCliCommand, aW as runRepairMemoryProjectionCliCommand, aX as runReplayCliCommand, aY as runResumeBundleBuildCliCommand, aZ as runResumeBundleRecordCliCommand, a_ as runResumeBundleStatusCliCommand, a$ as runRouteCliCommand, b0 as runSemanticRulePromoteCliCommand, b1 as runSemanticRuleVerifyCliCommand, b2 as runSessionCheckCliCommand, b3 as runSessionRepairCliCommand, b4 as runTailscaleStatusCliCommand, b5 as runTailscaleSyncCliCommand, b6 as runTierMigrateCliCommand, b7 as runTierStatusCliCommand, b8 as runTrainingExportCliCommand, b9 as runTrustZoneDemoSeedCliCommand, ba as runTrustZonePromoteCliCommand, bb as runTrustZoneStatusCliCommand, bc as runUtilityLearningCliCommand, bd as runUtilityLearningStatusCliCommand, be as runUtilityTelemetryRecordCliCommand, bf as runUtilityTelemetryStatusCliCommand, bg as runVerifiedRecallSearchCliCommand, bh as runVerifyMemoryProjectionCliCommand, bi as runWebDavServeCliCommand, bj as runWebDavStopCliCommand, bk as runWorkProductRecallSearchCliCommand, bl as runWorkProductRecordCliCommand, bm as runWorkProductStatusCliCommand, bn as runWorkProjectCliCommand, bo as runWorkTaskCliCommand } from './cli-5tNLVTvJ.js';
15
15
  import './work/types.js';
16
16
  import './routing/engine.js';
17
17
  import './network/tailscale.js';
18
18
  import './dashboard-runtime.js';
19
- import './access-service-DEL4qCrc.js';
19
+ import './access-service-DbZ1G3zV.js';
20
20
  import './resolve-auth-token.js';
21
21
  import './compat/types.js';
22
22
  import './evals.js';
package/dist/cli.js CHANGED
@@ -90,7 +90,7 @@ import {
90
90
  runWorkProductStatusCliCommand,
91
91
  runWorkProjectCliCommand,
92
92
  runWorkTaskCliCommand
93
- } from "./chunk-76GLCM57.js";
93
+ } from "./chunk-PMUD7ERF.js";
94
94
  import "./chunk-MMACM5G2.js";
95
95
  import "./chunk-54BGIBCR.js";
96
96
  import "./chunk-H4CKS5B2.js";
@@ -170,7 +170,7 @@ import "./chunk-K6MV3TRQ.js";
170
170
  import "./chunk-PIG4WJFQ.js";
171
171
  import "./chunk-LBJBNWS2.js";
172
172
  import "./chunk-KFZY2OFH.js";
173
- import "./chunk-IYNGIMVG.js";
173
+ import "./chunk-ZVAYK7GU.js";
174
174
  import "./chunk-V7IZKJI3.js";
175
175
  import "./chunk-SEDEKFYQ.js";
176
176
  import "./chunk-RKNJBZ55.js";
@@ -183,7 +183,7 @@ import "./chunk-MKQBXQKU.js";
183
183
  import "./chunk-CXZQOVIE.js";
184
184
  import "./chunk-IC2TCZJ2.js";
185
185
  import "./chunk-D24OXEPB.js";
186
- import "./chunk-IDZFBSHN.js";
186
+ import "./chunk-OGCAYLNA.js";
187
187
  import "./chunk-OQJIB32H.js";
188
188
  import "./chunk-HNZKVLAN.js";
189
189
  import "./chunk-EQWEIIKQ.js";
@@ -196,12 +196,12 @@ import "./chunk-2VEOKUPP.js";
196
196
  import "./chunk-5RUE3GHQ.js";
197
197
  import "./chunk-OADWQ5CR.js";
198
198
  import "./chunk-KF5EUVKQ.js";
199
- import "./chunk-TDTXKOAJ.js";
199
+ import "./chunk-XQDRKZ7V.js";
200
200
  import "./chunk-GDASG7NC.js";
201
201
  import "./chunk-GDB4J2H3.js";
202
202
  import "./chunk-AJ7QCSIS.js";
203
203
  import "./chunk-VQ7B2Q6M.js";
204
- import "./chunk-SICXL4MK.js";
204
+ import "./chunk-PJSK6OJM.js";
205
205
  import "./chunk-GNVZQ5JN.js";
206
206
  import "./chunk-YN2UN7N3.js";
207
207
  import "./chunk-JGSKJHF7.js";
package/dist/index.d.ts CHANGED
@@ -26,8 +26,8 @@ import { aD as CodingContext, aT as CodingModeConfig, aF as WearableConversation
26
26
  export { a6 as AgentAccessAuthToken, k as BriefingActiveThread, aY as BriefingConfig, i as BriefingFocus, h as BriefingFollowup, aZ as BriefingOpenCommitment, m as BriefingRecentEntity, l as BriefingResult, B as BriefingSections, a_ as BriefingWindow, a0 as BufferSurpriseEvent, a$ as BulkImportError, a2 as BulkImportOptions, a4 as BulkImportResult, a3 as BulkImportSource, b0 as BulkImportSourceAdapter, j as CalendarEvent, C as CalendarSource, b1 as CodexCompatConfig, aS as ConsolidationObservation, b2 as DEFAULT_RECALL_DISCLOSURE, E as EntityFile, ag as EntityStructuredSection, b3 as ExtractedFact, G as GatewayConfig, b4 as ImportSourceRole, a1 as ImportTurn, b5 as ImportTurnValidationIssue, aj as MemoryActionEligibilityContext, b6 as MemoryActionEligibilitySource, aa as MemoryActionType, M as MemoryFile, o as MemoryFrontmatter, b7 as MemoryObservation, b8 as MemoryScope, b9 as RECALL_DISCLOSURE_LEVELS, b as RecallDisclosure, a5 as SecretRef, ba as WearableAuthCheck, bb as WearableFetchOptions, bc as WearableFetchPage, aI as WearableMemoryMode, aH as WearableNativeMemory, bd as WearableNativeMemoryPage, aM as WearableSourceStatus, aL as WearableSyncSummary, be as WearableTranscriptSegment, bf as isImportRole, bg as isRecallDisclosure, bh as parseIsoTimestamp, bi as validateImportTurn } from './types-D_fG2ix8.js';
27
27
  export { JudgeBatchResult, JudgeCandidate, JudgeVerdict, JudgeVerdictKind, clearVerdictCache, createVerdictCache, getVerdictKind, isDurableVerdict, isValidCachedVerdict, judgeFactDurability, normalizeCachedVerdict, verdictCacheSize } from './extraction-judge.js';
28
28
  export { hasBroadGraphIntent, inferIntentFromText, intentCompatibilityScore, isTaskInitiationIntent, planRecallMode } from './intent.js';
29
- import { F as EngramAccessService } from './access-service-DEL4qCrc.js';
30
- export { P as ProcedureStatsConfigSnapshot, G as ProcedureStatsRecent, H as ProcedureStatsReport, I as ProcedureStatusCounts, J as computeProcedureStats, K as formatProcedureStatsText } from './access-service-DEL4qCrc.js';
29
+ import { F as EngramAccessService } from './access-service-DbZ1G3zV.js';
30
+ export { P as ProcedureStatsConfigSnapshot, G as ProcedureStatsRecent, H as ProcedureStatsReport, I as ProcedureStatusCounts, J as computeProcedureStats, K as formatProcedureStatsText } from './access-service-DbZ1G3zV.js';
31
31
  export { FILTER_LABELS as DIRECT_ANSWER_FILTER_LABELS, DirectAnswerCandidate, DirectAnswerConfig, DirectAnswerInput, DirectAnswerReason, DirectAnswerResult, isDirectAnswerEligible } from './direct-answer.js';
32
32
  export { MemoryTier, TierRoutingPolicy, TierTransitionDecision, computeTierValueScore, decideTierTransition } from './tier-routing.js';
33
33
  export { ApplyReasoningTraceBoostOptions, BoostableResult, DEFAULT_REASONING_TRACE_BOOST, applyReasoningTraceBoost, isReasoningTracePath, looksLikeProblemSolvingQuery } from './reasoning-trace-recall.js';
@@ -86,7 +86,7 @@ export { a as TaxonomyCategory } from './types-DJhqDJUV.js';
86
86
  export { EnrichmentAuditEntry, EnrichmentCandidate, EnrichmentCostTier, EnrichmentPipelineConfig, EnrichmentProvider, EnrichmentProviderConfig, EnrichmentProviderRegistry, EnrichmentResult, EntityEnrichmentInput, WebSearchFn, WebSearchProvider, WebSearchProviderOptions, appendAuditEntry, defaultEnrichmentPipelineConfig, readAuditLog, runEnrichmentPipeline } from './enrichment/index.js';
87
87
  export { clearBulkImportSources, getBulkImportSource, listBulkImportSources, registerBulkImportSource } from './bulk-import/index.js';
88
88
  export { a as ProcessBatchContext, P as ProcessBatchFn, b as ProcessBatchResult, f as formatBatchTranscript, r as resolveBulkImportContext, c as runBulkImportPipeline, v as validateBatchSize } from './pipeline-Cn-kvYeV.js';
89
- export { B as BulkImportCliCommandOptions, p as parseStrictCliDate, r as runBulkImportCliCommand } from './cli-C_pqqTCw.js';
89
+ export { B as BulkImportCliCommandOptions, p as parseStrictCliDate, r as runBulkImportCliCommand } from './cli-5tNLVTvJ.js';
90
90
  export { DEFAULT_IMPORT_BATCH_SIZE, ImportProgress, ImportedMemory, ImporterAdapter, ImporterParseOptions, ImporterTransformOptions, ImporterWriteResult, ImporterWriteTarget, RunImportOptions, RunImporterResult, defaultWriteMemoriesToOrchestrator, importedMemoryToTurn, runImporter, validateImportBatchSize, validateImportRateLimit } from './importers/index.js';
91
91
  export { FallbackLlmClient, FallbackLlmOptions, FallbackLlmResponse, FallbackLlmRuntimeContext } from './fallback-llm.js';
92
92
  export { ComputeMemoryWorthInput, MemoryWorthResult, computeMemoryWorth } from './memory-worth.js';
package/dist/index.js CHANGED
@@ -96,7 +96,7 @@ import {
96
96
  validateTaxonomy,
97
97
  watchForChanges,
98
98
  writeManifest
99
- } from "./chunk-2EJOEICD.js";
99
+ } from "./chunk-BI2OJF6J.js";
100
100
  import {
101
101
  WEARABLE_SOURCE_PREFIX,
102
102
  buildExtractionTurns,
@@ -350,7 +350,7 @@ import {
350
350
  registerTrainingExportAdapter,
351
351
  runBulkImportCliCommand,
352
352
  runWearablesCliCommand
353
- } from "./chunk-76GLCM57.js";
353
+ } from "./chunk-PMUD7ERF.js";
354
354
  import "./chunk-MMACM5G2.js";
355
355
  import "./chunk-54BGIBCR.js";
356
356
  import "./chunk-H4CKS5B2.js";
@@ -554,7 +554,7 @@ import "./chunk-H3FZVNRN.js";
554
554
  import "./chunk-KFZY2OFH.js";
555
555
  import {
556
556
  EngramAccessHttpServer
557
- } from "./chunk-IYNGIMVG.js";
557
+ } from "./chunk-ZVAYK7GU.js";
558
558
  import {
559
559
  WearablesInputError,
560
560
  describeErrorForOperator
@@ -598,7 +598,7 @@ import {
598
598
  relayMissionAppendOperation,
599
599
  relayMissionReadOperation,
600
600
  relayMissionReceiptDigest
601
- } from "./chunk-IDZFBSHN.js";
601
+ } from "./chunk-OGCAYLNA.js";
602
602
  import "./chunk-OQJIB32H.js";
603
603
  import "./chunk-HNZKVLAN.js";
604
604
  import "./chunk-EQWEIIKQ.js";
@@ -647,12 +647,12 @@ import {
647
647
  EngramAccessService,
648
648
  computeProcedureStats,
649
649
  formatProcedureStatsText
650
- } from "./chunk-TDTXKOAJ.js";
650
+ } from "./chunk-XQDRKZ7V.js";
651
651
  import "./chunk-GDASG7NC.js";
652
652
  import "./chunk-GDB4J2H3.js";
653
653
  import "./chunk-AJ7QCSIS.js";
654
654
  import "./chunk-VQ7B2Q6M.js";
655
- import "./chunk-SICXL4MK.js";
655
+ import "./chunk-PJSK6OJM.js";
656
656
  import "./chunk-GNVZQ5JN.js";
657
657
  import "./chunk-YN2UN7N3.js";
658
658
  import {
@@ -1,4 +1,4 @@
1
- import { x as EngramAccessRecallResponse } from './access-service-DEL4qCrc.js';
1
+ import { x as EngramAccessRecallResponse } from './access-service-DbZ1G3zV.js';
2
2
  import { ActionConfidenceRequest } from './access-schema.js';
3
3
  import { RecallXraySnapshot } from './recall-xray.js';
4
4
  import { ActionConfidenceResult } from './action-confidence.js';
@@ -41,7 +41,7 @@ import {
41
41
  throwIfRecallAborted,
42
42
  tokenizeRecallQuery,
43
43
  utcDateKeysForLocalDay
44
- } from "./chunk-2EJOEICD.js";
44
+ } from "./chunk-BI2OJF6J.js";
45
45
  import "./chunk-5SB7SC2G.js";
46
46
  import "./chunk-I74SUMNI.js";
47
47
  import "./chunk-ILNPU5W5.js";
@@ -125,7 +125,7 @@ import "./chunk-6HEM6HTQ.js";
125
125
  import "./chunk-6QYST5XN.js";
126
126
  import "./chunk-YROFNEG5.js";
127
127
  import "./chunk-S2FSBU4H.js";
128
- import "./chunk-76GLCM57.js";
128
+ import "./chunk-PMUD7ERF.js";
129
129
  import "./chunk-MMACM5G2.js";
130
130
  import "./chunk-54BGIBCR.js";
131
131
  import "./chunk-H4CKS5B2.js";
@@ -218,7 +218,7 @@ import "./chunk-KD3QD3A5.js";
218
218
  import "./chunk-MYVPLJOO.js";
219
219
  import "./chunk-H3FZVNRN.js";
220
220
  import "./chunk-KFZY2OFH.js";
221
- import "./chunk-IYNGIMVG.js";
221
+ import "./chunk-ZVAYK7GU.js";
222
222
  import "./chunk-V7IZKJI3.js";
223
223
  import "./chunk-SEDEKFYQ.js";
224
224
  import "./chunk-RKNJBZ55.js";
@@ -231,7 +231,7 @@ import "./chunk-MKQBXQKU.js";
231
231
  import "./chunk-CXZQOVIE.js";
232
232
  import "./chunk-IC2TCZJ2.js";
233
233
  import "./chunk-D24OXEPB.js";
234
- import "./chunk-IDZFBSHN.js";
234
+ import "./chunk-OGCAYLNA.js";
235
235
  import "./chunk-OQJIB32H.js";
236
236
  import "./chunk-HNZKVLAN.js";
237
237
  import "./chunk-EQWEIIKQ.js";
@@ -247,12 +247,12 @@ import "./chunk-KF5EUVKQ.js";
247
247
  import {
248
248
  hasIdentityRecoveryIntent,
249
249
  resolveEffectiveIdentityInjectionMode
250
- } from "./chunk-TDTXKOAJ.js";
250
+ } from "./chunk-XQDRKZ7V.js";
251
251
  import "./chunk-GDASG7NC.js";
252
252
  import "./chunk-GDB4J2H3.js";
253
253
  import "./chunk-AJ7QCSIS.js";
254
254
  import "./chunk-VQ7B2Q6M.js";
255
- import "./chunk-SICXL4MK.js";
255
+ import "./chunk-PJSK6OJM.js";
256
256
  import "./chunk-GNVZQ5JN.js";
257
257
  import "./chunk-YN2UN7N3.js";
258
258
  import "./chunk-JGSKJHF7.js";
@@ -37,6 +37,11 @@ interface QuarantinedRecord {
37
37
  timestamp: string;
38
38
  payload: unknown;
39
39
  }
40
+ /** A quarantined record paired with the absolute on-disk path it was read from. */
41
+ interface QuarantineEntry {
42
+ record: QuarantinedRecord;
43
+ path: string;
44
+ }
40
45
  interface QuarantineCaps {
41
46
  /** Keep at most this many records per principal (newest win). */
42
47
  maxEntriesPerPrincipal: number;
@@ -63,10 +68,20 @@ declare class WriteQuarantineStore {
63
68
  quarantine(input: QuarantineEntryInput): Promise<string>;
64
69
  /**
65
70
  * All valid, non-expired quarantined records across every principal, oldest
66
- * first. Expired records are deleted on read (lazy GC), so an inactive
67
- * principal bucket with no follow-up write is still bounded by age.
71
+ * first, each paired with its absolute on-disk path. Expired records are
72
+ * deleted on read (lazy GC), so an inactive principal bucket with no
73
+ * follow-up write is still bounded by age.
68
74
  */
75
+ entries(): Promise<QuarantineEntry[]>;
76
+ /** As {@link entries} but projected to records only (backward-compatible view). */
69
77
  list(): Promise<QuarantinedRecord[]>;
78
+ /**
79
+ * Delete one quarantined entry by its absolute on-disk path (as returned by
80
+ * {@link entries}). The path MUST resolve inside the quarantine root, mirroring
81
+ * the store's containment posture; a path outside the root — or one already
82
+ * absent — removes nothing. Returns true only when a file was actually deleted.
83
+ */
84
+ removeEntry(entryPath: string): Promise<boolean>;
70
85
  /** Total valid, non-expired quarantined record count across every principal. */
71
86
  count(): Promise<number>;
72
87
  /** lstat (no symlink follow): true only for a real directory. */
@@ -76,4 +91,4 @@ declare class WriteQuarantineStore {
76
91
  private pruneDir;
77
92
  }
78
93
 
79
- export { DEFAULT_QUARANTINE_CAPS, type QuarantineCaps, type QuarantineEntryInput, type QuarantineOperation, type QuarantinedRecord, WriteQuarantineStore };
94
+ export { DEFAULT_QUARANTINE_CAPS, type QuarantineCaps, type QuarantineEntry, type QuarantineEntryInput, type QuarantineOperation, type QuarantinedRecord, WriteQuarantineStore };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_QUARANTINE_CAPS,
3
3
  WriteQuarantineStore
4
- } from "./chunk-SICXL4MK.js";
4
+ } from "./chunk-PJSK6OJM.js";
5
5
  import "./chunk-FF4KLI5W.js";
6
6
  import "./chunk-PZ5AY32C.js";
7
7
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.8.0",
3
+ "version": "9.9.0",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3045,7 +3045,7 @@
3045
3045
  "core"
3046
3046
  ],
3047
3047
  "peerDependencies": {
3048
- "@remnic/coding-graph": "^9.8.0"
3048
+ "@remnic/coding-graph": "^9.9.0"
3049
3049
  },
3050
3050
  "peerDependenciesMeta": {
3051
3051
  "@remnic/coding-graph": {
@@ -10,6 +10,7 @@ import { tmpdir } from "node:os";
10
10
  import path from "node:path";
11
11
  import test from "node:test";
12
12
 
13
+ import { memoryStoreRequestSchema, observeRequestSchema } from "./access-schema.js";
13
14
  import { EngramAccessService, NamespaceNotWritableError } from "./access-service.js";
14
15
  import { Orchestrator } from "./orchestrator.js";
15
16
  import type { CodingContext, PluginConfig } from "./types.js";
@@ -118,3 +119,54 @@ test("a dry-run rejected write is NOT quarantined (#1888)", async () => {
118
119
  assert.equal(await new WriteQuarantineStore(dir).count(), 0);
119
120
  });
120
121
  });
122
+
123
+ test("suppressQuarantine skips dead-lettering an ACL-rejected write; without it it is parked (#1888 replay)", async () => {
124
+ await withService(async (service, dir) => {
125
+ const store = new WriteQuarantineStore(dir);
126
+ // Replay re-submits with suppressQuarantine set: the ACL rejection still
127
+ // throws, but the attempt is NOT re-parked, so replay cannot duplicate it.
128
+ await assert.rejects(
129
+ service.memoryStore({
130
+ content: "replayed",
131
+ namespace: "victim-secret",
132
+ authenticatedPrincipal: "alice",
133
+ sessionKey: "s-5",
134
+ suppressQuarantine: true,
135
+ } as unknown as Parameters<EngramAccessService["memoryStore"]>[0]),
136
+ NamespaceNotWritableError
137
+ );
138
+ assert.equal(await store.count(), 0);
139
+
140
+ // The identical write WITHOUT the flag is dead-lettered as before.
141
+ await assert.rejects(
142
+ service.memoryStore({
143
+ content: "replayed",
144
+ namespace: "victim-secret",
145
+ authenticatedPrincipal: "alice",
146
+ sessionKey: "s-5",
147
+ } as unknown as Parameters<EngramAccessService["memoryStore"]>[0]),
148
+ NamespaceNotWritableError
149
+ );
150
+ assert.equal(await store.count(), 1);
151
+ });
152
+ });
153
+
154
+ test("public write schemas strip suppressQuarantine — external callers cannot skip quarantine", () => {
155
+ // The write surface honors suppressQuarantine (in-process replay sets it), but
156
+ // it is NOT a field on the public HTTP/MCP request schemas. zod strips unknown
157
+ // keys, so a body carrying it is scrubbed before the service — and the access
158
+ // boundary re-validates the cleaned envelope — keeping ACL-rejected external
159
+ // writes parked rather than silently discarded.
160
+ const stored = memoryStoreRequestSchema.parse({
161
+ sessionKey: "s",
162
+ content: "hello",
163
+ suppressQuarantine: true,
164
+ } as Record<string, unknown>);
165
+ assert.equal("suppressQuarantine" in stored, false);
166
+ const observed = observeRequestSchema.parse({
167
+ sessionKey: "s",
168
+ messages: [{ role: "user", content: "hi" }],
169
+ suppressQuarantine: true,
170
+ } as Record<string, unknown>);
171
+ assert.equal("suppressQuarantine" in observed, false);
172
+ });
@@ -463,8 +463,10 @@ export class AccessObserveWriteSurface {
463
463
  try {
464
464
  namespace = await this.deps.resolveCodingScopedWriteNamespace(request);
465
465
  } catch (err) {
466
- // A dry run is a no-persist validation, so never dead-letter it.
467
- if (request.dryRun !== true) {
466
+ // A dry run is a no-persist validation, and a replay re-submit sets
467
+ // suppressQuarantine so a still-unwritable target propagates instead of
468
+ // re-parking (issue #1888): never dead-letter either.
469
+ if (request.dryRun !== true && request.suppressQuarantine !== true) {
468
470
  await this.parkRejectedWrite(err, "memory_store", request);
469
471
  }
470
472
  throw err;
@@ -548,8 +550,9 @@ export class AccessObserveWriteSurface {
548
550
  try {
549
551
  namespace = await this.deps.resolveCodingScopedWriteNamespace(request);
550
552
  } catch (err) {
551
- // A dry run is a no-persist validation, so never dead-letter it.
552
- if (request.dryRun !== true) {
553
+ // A dry run never persists, and a replay re-submit sets suppressQuarantine
554
+ // so a still-unwritable target propagates instead of re-parking (#1888).
555
+ if (request.dryRun !== true && request.suppressQuarantine !== true) {
553
556
  await this.parkRejectedWrite(err, "suggestion_submit", request);
554
557
  }
555
558
  throw err;
@@ -655,7 +658,11 @@ export class AccessObserveWriteSurface {
655
658
  try {
656
659
  scope = await this.deps.resolveMemoryScopePlan(request);
657
660
  } catch (err) {
658
- await this.parkRejectedWrite(err, "observe", request);
661
+ // A replay re-submit sets suppressQuarantine so a still-unwritable target
662
+ // propagates instead of re-parking (#1888); observe has no dryRun path.
663
+ if (request.suppressQuarantine !== true) {
664
+ await this.parkRejectedWrite(err, "observe", request);
665
+ }
659
666
  throw err;
660
667
  }
661
668
  const writeNamespace = scope.writeNamespace;
@@ -943,11 +943,10 @@ export interface EngramAccessWriteEnvelope {
943
943
  idempotencyKey?: string;
944
944
  dryRun?: boolean;
945
945
  sessionKey?: string;
946
- /**
947
- * Trusted transport-bound principal. This must never come from untrusted client payloads.
948
- * When present, write authorization is evaluated against this principal instead of sessionKey.
949
- */
946
+ /** Trusted transport-bound principal (never from client payloads); authorizes writes instead of sessionKey. */
950
947
  authenticatedPrincipal?: string;
948
+ /** When set, an ACL rejection is NOT dead-lettered (#1888) so `remnic quarantine replay` can re-submit through this same surface without re-quarantining and duplicating the parked record. */
949
+ readonly suppressQuarantine?: boolean;
951
950
  }
952
951
 
953
952
  /**
@@ -1068,6 +1067,7 @@ export interface EngramAccessObserveRequest extends SourceConnectorProvenance {
1068
1067
  * Creates a `CodingContext` with `projectId: "tag:<projectTag>"`.
1069
1068
  */
1070
1069
  projectTag?: string;
1070
+ readonly suppressQuarantine?: boolean; // #1888: mirrors EngramAccessWriteEnvelope.suppressQuarantine (replay re-submit skips dead-lettering).
1071
1071
  }
1072
1072
 
1073
1073
  /**
@@ -191,3 +191,55 @@ test("list() surfaces a symlink-escaping root instead of reporting empty", async
191
191
  }
192
192
  });
193
193
  });
194
+
195
+ test("entries() pairs each record with its on-disk path and list() projects records", async () => {
196
+ await withTempDir(async (dir) => {
197
+ const store = new WriteQuarantineStore(dir);
198
+ const written = await store.quarantine({
199
+ operation: "memory_store",
200
+ principal: "alice",
201
+ attemptedNamespace: "victim-secret",
202
+ payload: { content: "hi" },
203
+ });
204
+ const entries = await store.entries();
205
+ assert.equal(entries.length, 1);
206
+ assert.equal(entries[0]?.path, written);
207
+ assert.ok(isInside(store.root, entries[0]?.path ?? ""));
208
+ assert.equal(entries[0]?.record.operation, "memory_store");
209
+ assert.deepEqual(entries[0]?.record.payload, { content: "hi" });
210
+ assert.deepEqual(
211
+ await store.list(),
212
+ entries.map((entry) => entry.record)
213
+ );
214
+ });
215
+ });
216
+
217
+ test("removeEntry deletes an entry by its path and is idempotent", async () => {
218
+ await withTempDir(async (dir) => {
219
+ const store = new WriteQuarantineStore(dir);
220
+ await store.quarantine({ operation: "observe", principal: "alice", attemptedNamespace: "ns", payload: 1 });
221
+ const [entry] = await store.entries();
222
+ assert.ok(entry);
223
+ assert.equal(await store.removeEntry(entry.path), true);
224
+ assert.equal(await store.count(), 0);
225
+ // An already-absent path removes nothing.
226
+ assert.equal(await store.removeEntry(entry.path), false);
227
+ });
228
+ });
229
+
230
+ test("removeEntry refuses a path outside the quarantine root", async () => {
231
+ await withTempDir(async (dir) => {
232
+ const store = new WriteQuarantineStore(dir);
233
+ await store.quarantine({ operation: "observe", principal: "alice", attemptedNamespace: "ns", payload: 1 });
234
+ const outside = await mkdtemp(path.join(tmpdir(), "remnic-quarantine-outside-"));
235
+ const victim = path.join(outside, "keep.json");
236
+ await writeFile(victim, "{}", "utf8");
237
+ try {
238
+ assert.equal(await store.removeEntry(victim), false);
239
+ assert.equal((await readdir(outside)).length, 1, "file outside root must survive");
240
+ assert.equal(await store.count(), 1, "in-root entry untouched");
241
+ } finally {
242
+ await rm(outside, { recursive: true, force: true });
243
+ }
244
+ });
245
+ });
@@ -48,6 +48,12 @@ export interface QuarantinedRecord {
48
48
  payload: unknown;
49
49
  }
50
50
 
51
+ /** A quarantined record paired with the absolute on-disk path it was read from. */
52
+ export interface QuarantineEntry {
53
+ record: QuarantinedRecord;
54
+ path: string;
55
+ }
56
+
51
57
  export interface QuarantineCaps {
52
58
  /** Keep at most this many records per principal (newest win). */
53
59
  maxEntriesPerPrincipal: number;
@@ -145,12 +151,13 @@ export class WriteQuarantineStore {
145
151
 
146
152
  /**
147
153
  * All valid, non-expired quarantined records across every principal, oldest
148
- * first. Expired records are deleted on read (lazy GC), so an inactive
149
- * principal bucket with no follow-up write is still bounded by age.
154
+ * first, each paired with its absolute on-disk path. Expired records are
155
+ * deleted on read (lazy GC), so an inactive principal bucket with no
156
+ * follow-up write is still bounded by age.
150
157
  */
151
- async list(): Promise<QuarantinedRecord[]> {
158
+ async entries(): Promise<QuarantineEntry[]> {
152
159
  const now = Date.now();
153
- const records: QuarantinedRecord[] = [];
160
+ const entries: QuarantineEntry[] = [];
154
161
  for (const dir of await this.principalDirs()) {
155
162
  for (const file of await this.entryFiles(dir)) {
156
163
  const info = await stat(file).catch(() => null);
@@ -165,11 +172,53 @@ export class WriteQuarantineStore {
165
172
  } catch {
166
173
  continue;
167
174
  }
168
- if (isQuarantinedRecord(parsed)) records.push(parsed);
175
+ if (isQuarantinedRecord(parsed)) entries.push({ record: parsed, path: file });
169
176
  }
170
177
  }
171
- records.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));
172
- return records;
178
+ entries.sort((a, b) => {
179
+ const at = a.record.timestamp;
180
+ const bt = b.record.timestamp;
181
+ return at < bt ? -1 : at > bt ? 1 : 0;
182
+ });
183
+ return entries;
184
+ }
185
+
186
+ /** As {@link entries} but projected to records only (backward-compatible view). */
187
+ async list(): Promise<QuarantinedRecord[]> {
188
+ return (await this.entries()).map((entry) => entry.record);
189
+ }
190
+
191
+ /**
192
+ * Delete one quarantined entry by its absolute on-disk path (as returned by
193
+ * {@link entries}). The path MUST resolve inside the quarantine root, mirroring
194
+ * the store's containment posture; a path outside the root — or one already
195
+ * absent — removes nothing. Returns true only when a file was actually deleted.
196
+ */
197
+ async removeEntry(entryPath: string): Promise<boolean> {
198
+ const rootAbs = path.resolve(this.root);
199
+ const rel = path.relative(rootAbs, path.resolve(entryPath));
200
+ if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
201
+ return false;
202
+ }
203
+ // Symlink-race-safe resolution (issue #1888): resolveSafeStoragePath rejects
204
+ // any symlinked path component and verifies the REAL path stays inside the
205
+ // real quarantine root before we touch it, so a swapped component cannot
206
+ // redirect the deletion outside the store — lexical containment alone is not
207
+ // sufficient against a TOCTOU symlink swap.
208
+ let safe: string;
209
+ try {
210
+ safe = await resolveSafeStoragePath(rootAbs, rel);
211
+ } catch {
212
+ return false;
213
+ }
214
+ try {
215
+ await rm(safe, { force: false });
216
+ return true;
217
+ } catch (err) {
218
+ const errno = err as NodeJS.ErrnoException;
219
+ if (errno?.code === "ENOENT") return false;
220
+ throw err;
221
+ }
173
222
  }
174
223
 
175
224
  /** Total valid, non-expired quarantined record count across every principal. */
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/write-quarantine.ts"],"sourcesContent":["/**\n * Dead-letter quarantine for writes rejected by the namespace write-ACL\n * (issue #1888).\n *\n * When `observe` / `memory_store` / `suggestion_submit` fail namespace\n * authorization, the payload used to be dropped with no recoverable copy: a\n * client configured with a namespace that matches no policy (and is not the\n * default/shared namespace) had every write in the window silently destroyed.\n * The ACL rejection is CORRECT and stays fail-closed; this store only\n * preserves the payload so it can be replayed after the config is fixed,\n * following the AGENTS.md \"reject loudly but never destroy state\" posture\n * (patterns 14/42).\n *\n * Records live OUTSIDE any memory namespace at\n * `<memoryDir>/state/quarantine/<principalSafe>/<timestamp>-<id>.json`, keyed\n * by the encoded principal, and are bounded per principal by count and age.\n * Every path is resolved from `memoryDir` through `resolveSafeStoragePath`, so\n * a symlinked `state`/`quarantine` cannot redirect writes outside the memory\n * directory. The store never re-routes a payload into a writable namespace.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { encodeStoragePathSegment, resolveSafeStoragePath } from \"./storage-paths.js\";\n\nexport type QuarantineOperation = \"observe\" | \"memory_store\" | \"suggestion_submit\";\n\nexport interface QuarantineEntryInput {\n operation: QuarantineOperation;\n /** Authenticated principal the write was attributed to, if any. */\n principal: string | undefined;\n /** Namespace the caller attempted to write and was denied. */\n attemptedNamespace: string;\n /** The original request payload, preserved verbatim for replay. */\n payload: unknown;\n /** ISO timestamp; defaults to now. Injectable for deterministic tests. */\n timestamp?: string;\n}\n\nexport interface QuarantinedRecord {\n operation: QuarantineOperation;\n principal: string | null;\n attemptedNamespace: string;\n timestamp: string;\n payload: unknown;\n}\n\nexport interface QuarantineCaps {\n /** Keep at most this many records per principal (newest win). */\n maxEntriesPerPrincipal: number;\n /** Drop records older than this many milliseconds. */\n maxAgeMs: number;\n}\n\nexport const DEFAULT_QUARANTINE_CAPS: QuarantineCaps = {\n maxEntriesPerPrincipal: 500,\n maxAgeMs: 14 * 24 * 60 * 60 * 1000,\n};\n\nconst QUARANTINE_DIRNAME = \"quarantine\";\n\nfunction validateCaps(caps: QuarantineCaps): void {\n if (!Number.isInteger(caps.maxEntriesPerPrincipal) || caps.maxEntriesPerPrincipal < 1) {\n throw new Error(\n `WriteQuarantineStore: maxEntriesPerPrincipal must be a positive integer (got ${caps.maxEntriesPerPrincipal})`\n );\n }\n if (!Number.isFinite(caps.maxAgeMs) || caps.maxAgeMs <= 0) {\n throw new Error(\n `WriteQuarantineStore: maxAgeMs must be a positive finite number of milliseconds (got ${caps.maxAgeMs})`\n );\n }\n}\n\nfunction isQuarantinedRecord(value: unknown): value is QuarantinedRecord {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return (\n (record.operation === \"observe\" ||\n record.operation === \"memory_store\" ||\n record.operation === \"suggestion_submit\") &&\n (record.principal === null || typeof record.principal === \"string\") &&\n typeof record.attemptedNamespace === \"string\" &&\n typeof record.timestamp === \"string\" &&\n \"payload\" in record\n );\n}\n\nexport class WriteQuarantineStore {\n private readonly memoryDir: string;\n\n constructor(\n memoryDir: string,\n private readonly caps: QuarantineCaps = DEFAULT_QUARANTINE_CAPS\n ) {\n validateCaps(caps);\n this.memoryDir = path.resolve(memoryDir);\n }\n\n /** Absolute quarantine root (`<memoryDir>/state/quarantine`). */\n get root(): string {\n return path.join(this.memoryDir, \"state\", QUARANTINE_DIRNAME);\n }\n\n /**\n * Resolve a path under the quarantine root, checked for containment and\n * symlink traversal from `memoryDir` down — so a symlinked `state` or\n * `quarantine` component that escapes the memory directory is rejected.\n */\n private safeDir(...segments: string[]): Promise<string> {\n return resolveSafeStoragePath(this.memoryDir, \"state\", QUARANTINE_DIRNAME, ...segments);\n }\n\n /**\n * Park a rejected write. Returns the absolute path of the record written.\n * Prunes the principal's bucket to the configured caps afterward.\n */\n async quarantine(input: QuarantineEntryInput): Promise<string> {\n const timestamp = input.timestamp ?? new Date().toISOString();\n const record: QuarantinedRecord = {\n operation: input.operation,\n principal: input.principal ?? null,\n attemptedNamespace: input.attemptedNamespace,\n timestamp,\n payload: input.payload,\n };\n const principalSegment = encodeStoragePathSegment(input.principal ?? \"unknown\");\n const fileName = `${timestamp.replace(/[:.]/g, \"-\")}-${randomUUID().slice(0, 8)}.json`;\n\n const dir = await this.safeDir(principalSegment);\n await mkdir(dir, { recursive: true });\n // The temp write uses the exclusive `wx` flag so a pre-planted symlink at\n // the temp name can never be followed (O_EXCL); rename is atomic (rule 42).\n const finalPath = await this.safeDir(principalSegment, fileName);\n const tmpPath = await this.safeDir(principalSegment, `${fileName}.tmp`);\n await writeFile(tmpPath, JSON.stringify(record, null, 2), { encoding: \"utf8\", flag: \"wx\" });\n await rename(tmpPath, finalPath);\n\n await this.pruneDir(dir);\n return finalPath;\n }\n\n /**\n * All valid, non-expired quarantined records across every principal, oldest\n * first. Expired records are deleted on read (lazy GC), so an inactive\n * principal bucket with no follow-up write is still bounded by age.\n */\n async list(): Promise<QuarantinedRecord[]> {\n const now = Date.now();\n const records: QuarantinedRecord[] = [];\n for (const dir of await this.principalDirs()) {\n for (const file of await this.entryFiles(dir)) {\n const info = await stat(file).catch(() => null);\n if (!info) continue;\n if (now - info.mtimeMs > this.caps.maxAgeMs) {\n await rm(file, { force: true }).catch(() => {});\n continue;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(file, \"utf8\"));\n } catch {\n continue;\n }\n if (isQuarantinedRecord(parsed)) records.push(parsed);\n }\n }\n records.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));\n return records;\n }\n\n /** Total valid, non-expired quarantined record count across every principal. */\n async count(): Promise<number> {\n return (await this.list()).length;\n }\n\n /** lstat (no symlink follow): true only for a real directory. */\n private async isRealDir(dir: string): Promise<boolean> {\n const info = await lstat(dir).catch(() => null);\n return info?.isDirectory() === true;\n }\n\n private async principalDirs(): Promise<string[]> {\n // safeDir() rejects a symlinked/containment-escaping root — let that\n // propagate instead of masquerading as an empty store.\n const root = await this.safeDir();\n if (!(await this.isRealDir(root))) return [];\n let entries: Dirent[];\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch (err) {\n // Root vanished mid-scan → legitimately empty. Any other failure\n // (permissions, IO) is a genuine inspection failure and must surface.\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw err;\n }\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(root, entry.name))\n .sort();\n }\n\n private async entryFiles(dir: string): Promise<string[]> {\n if (!(await this.isRealDir(dir))) return [];\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw err;\n }\n return entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => path.join(dir, entry.name))\n .sort();\n }\n\n private async pruneDir(dir: string): Promise<void> {\n const files = await this.entryFiles(dir);\n const now = Date.now();\n const survivors: Array<{ path: string; mtimeMs: number }> = [];\n for (const file of files) {\n const info = await stat(file).catch(() => null);\n if (!info) continue;\n if (now - info.mtimeMs > this.caps.maxAgeMs) {\n await rm(file, { force: true }).catch(() => {});\n continue;\n }\n survivors.push({ path: file, mtimeMs: info.mtimeMs });\n }\n const surplus = survivors.length - this.caps.maxEntriesPerPrincipal;\n if (surplus <= 0) return;\n survivors.sort((a, b) => a.mtimeMs - b.mtimeMs || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n for (const victim of survivors.slice(0, surplus)) {\n await rm(victim.path, { force: true }).catch(() => {});\n }\n }\n}\n"],"mappings":"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","names":[]}