@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.
- package/dist/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +2 -2
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +2 -2
- package/dist/access-cli.js +6 -6
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +4 -4
- package/dist/access-identity-continuity-surface.js +2 -2
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +2 -2
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +3 -3
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +2 -2
- package/dist/access-operations-batch.js +2 -2
- package/dist/access-operations.d.ts +1 -1
- package/dist/access-operations.js +3 -3
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +2 -2
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +2 -2
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +2 -2
- package/dist/{access-service-DEL4qCrc.d.ts → access-service-DbZ1G3zV.d.ts} +4 -4
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +2 -2
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/{chunk-2EJOEICD.js → chunk-BI2OJF6J.js} +2 -2
- package/dist/{chunk-IDZFBSHN.js → chunk-OGCAYLNA.js} +2 -2
- package/dist/{chunk-SICXL4MK.js → chunk-PJSK6OJM.js} +44 -8
- package/dist/chunk-PJSK6OJM.js.map +1 -0
- package/dist/{chunk-76GLCM57.js → chunk-PMUD7ERF.js} +3 -3
- package/dist/{chunk-TDTXKOAJ.js → chunk-XQDRKZ7V.js} +7 -5
- package/dist/chunk-XQDRKZ7V.js.map +1 -0
- package/dist/{chunk-IYNGIMVG.js → chunk-ZVAYK7GU.js} +3 -3
- package/dist/{cli-C_pqqTCw.d.ts → cli-5tNLVTvJ.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +5 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -6
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/orchestrator.js +6 -6
- package/dist/write-quarantine.d.ts +18 -3
- package/dist/write-quarantine.js +1 -1
- package/package.json +2 -2
- package/src/access-observe-quarantine.test.ts +52 -0
- package/src/access-observe-write-surface.ts +12 -5
- package/src/access-service.ts +4 -4
- package/src/write-quarantine.test.ts +52 -0
- package/src/write-quarantine.ts +56 -7
- package/dist/chunk-SICXL4MK.js.map +0 -1
- package/dist/chunk-TDTXKOAJ.js.map +0 -1
- /package/dist/{chunk-2EJOEICD.js.map → chunk-BI2OJF6J.js.map} +0 -0
- /package/dist/{chunk-IDZFBSHN.js.map → chunk-OGCAYLNA.js.map} +0 -0
- /package/dist/{chunk-76GLCM57.js.map → chunk-PMUD7ERF.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
30
|
-
export { P as ProcedureStatsConfigSnapshot, G as ProcedureStatsRecent, H as ProcedureStatsReport, I as ProcedureStatusCounts, J as computeProcedureStats, K as formatProcedureStatsText } from './access-service-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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';
|
package/dist/orchestrator.js
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
throwIfRecallAborted,
|
|
42
42
|
tokenizeRecallQuery,
|
|
43
43
|
utcDateKeysForLocalDay
|
|
44
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
67
|
-
*
|
|
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 };
|
package/dist/write-quarantine.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.
|
|
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.
|
|
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,
|
|
467
|
-
|
|
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
|
|
552
|
-
|
|
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
|
-
|
|
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;
|
package/src/access-service.ts
CHANGED
|
@@ -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
|
+
});
|
package/src/write-quarantine.ts
CHANGED
|
@@ -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
|
|
149
|
-
*
|
|
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
|
|
158
|
+
async entries(): Promise<QuarantineEntry[]> {
|
|
152
159
|
const now = Date.now();
|
|
153
|
-
const
|
|
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))
|
|
175
|
+
if (isQuarantinedRecord(parsed)) entries.push({ record: parsed, path: file });
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
|
-
|
|
172
|
-
|
|
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":[]}
|