@astragenie/astramemory-local 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +341 -0
- package/README.md +419 -0
- package/dist/backup/retention.d.ts +15 -0
- package/dist/backup/retention.js +62 -0
- package/dist/backup/retention.js.map +1 -0
- package/dist/backup/snapshot.d.ts +21 -0
- package/dist/backup/snapshot.js +55 -0
- package/dist/backup/snapshot.js.map +1 -0
- package/dist/backup/verify.d.ts +23 -0
- package/dist/backup/verify.js +77 -0
- package/dist/backup/verify.js.map +1 -0
- package/dist/budget/tracker.d.ts +58 -0
- package/dist/budget/tracker.js +102 -0
- package/dist/budget/tracker.js.map +1 -0
- package/dist/capture/codex.d.ts +63 -0
- package/dist/capture/codex.js +0 -0
- package/dist/capture/codex.js.map +1 -0
- package/dist/cli/backup.d.ts +1 -0
- package/dist/cli/backup.js +112 -0
- package/dist/cli/backup.js.map +1 -0
- package/dist/cli/budget.d.ts +7 -0
- package/dist/cli/budget.js +44 -0
- package/dist/cli/budget.js.map +1 -0
- package/dist/cli/capture.d.ts +10 -0
- package/dist/cli/capture.js +113 -0
- package/dist/cli/capture.js.map +1 -0
- package/dist/cli/consolidate.d.ts +16 -0
- package/dist/cli/consolidate.js +146 -0
- package/dist/cli/consolidate.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +54 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/entity-backfill.d.ts +10 -0
- package/dist/cli/entity-backfill.js +46 -0
- package/dist/cli/entity-backfill.js.map +1 -0
- package/dist/cli/hook-install.d.ts +45 -0
- package/dist/cli/hook-install.js +77 -0
- package/dist/cli/hook-install.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +312 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +16 -0
- package/dist/cli/init.js +431 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp-stdio.d.ts +18 -0
- package/dist/cli/mcp-stdio.js +67 -0
- package/dist/cli/mcp-stdio.js.map +1 -0
- package/dist/cli/memory.d.ts +15 -0
- package/dist/cli/memory.js +52 -0
- package/dist/cli/memory.js.map +1 -0
- package/dist/cli/open-runtime-db.d.ts +15 -0
- package/dist/cli/open-runtime-db.js +37 -0
- package/dist/cli/open-runtime-db.js.map +1 -0
- package/dist/cli/pair.d.ts +29 -0
- package/dist/cli/pair.js +64 -0
- package/dist/cli/pair.js.map +1 -0
- package/dist/cli/providers.d.ts +10 -0
- package/dist/cli/providers.js +97 -0
- package/dist/cli/providers.js.map +1 -0
- package/dist/cli/queue-purge.d.ts +5 -0
- package/dist/cli/queue-purge.js +92 -0
- package/dist/cli/queue-purge.js.map +1 -0
- package/dist/cli/queue.d.ts +29 -0
- package/dist/cli/queue.js +73 -0
- package/dist/cli/queue.js.map +1 -0
- package/dist/cli/rebuild.d.ts +15 -0
- package/dist/cli/rebuild.js +70 -0
- package/dist/cli/rebuild.js.map +1 -0
- package/dist/cli/reembed-dim.d.ts +21 -0
- package/dist/cli/reembed-dim.js +199 -0
- package/dist/cli/reembed-dim.js.map +1 -0
- package/dist/cli/reinstall.d.ts +1 -0
- package/dist/cli/reinstall.js +205 -0
- package/dist/cli/reinstall.js.map +1 -0
- package/dist/cli/restore.d.ts +1 -0
- package/dist/cli/restore.js +167 -0
- package/dist/cli/restore.js.map +1 -0
- package/dist/cli/retag.d.ts +14 -0
- package/dist/cli/retag.js +62 -0
- package/dist/cli/retag.js.map +1 -0
- package/dist/cli/search.d.ts +66 -0
- package/dist/cli/search.js +174 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/cli/serve.d.ts +9 -0
- package/dist/cli/serve.js +364 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/service.d.ts +1 -0
- package/dist/cli/service.js +121 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/cli/sync.d.ts +15 -0
- package/dist/cli/sync.js +61 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli/token.d.ts +24 -0
- package/dist/cli/token.js +77 -0
- package/dist/cli/token.js.map +1 -0
- package/dist/cli/wait-health.d.ts +4 -0
- package/dist/cli/wait-health.js +23 -0
- package/dist/cli/wait-health.js.map +1 -0
- package/dist/config/config.d.ts +127 -0
- package/dist/config/config.js +38 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/datadir.d.ts +30 -0
- package/dist/config/datadir.js +65 -0
- package/dist/config/datadir.js.map +1 -0
- package/dist/config/loader.d.ts +23 -0
- package/dist/config/loader.js +102 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/migrate-dirs.d.ts +36 -0
- package/dist/config/migrate-dirs.js +132 -0
- package/dist/config/migrate-dirs.js.map +1 -0
- package/dist/config/persist-envs.d.ts +23 -0
- package/dist/config/persist-envs.js +119 -0
- package/dist/config/persist-envs.js.map +1 -0
- package/dist/config/resolve-runtime.d.ts +19 -0
- package/dist/config/resolve-runtime.js +53 -0
- package/dist/config/resolve-runtime.js.map +1 -0
- package/dist/config/secrets.d.ts +28 -0
- package/dist/config/secrets.js +38 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/config/sync-settings.d.ts +16 -0
- package/dist/config/sync-settings.js +34 -0
- package/dist/config/sync-settings.js.map +1 -0
- package/dist/config/writer.d.ts +19 -0
- package/dist/config/writer.js +121 -0
- package/dist/config/writer.js.map +1 -0
- package/dist/consolidate/consolidate.d.ts +80 -0
- package/dist/consolidate/consolidate.js +0 -0
- package/dist/consolidate/consolidate.js.map +1 -0
- package/dist/consolidate/proposals.d.ts +35 -0
- package/dist/consolidate/proposals.js +66 -0
- package/dist/consolidate/proposals.js.map +1 -0
- package/dist/contracts/atom-wire.d.ts +48 -0
- package/dist/contracts/atom-wire.js +55 -0
- package/dist/contracts/atom-wire.js.map +1 -0
- package/dist/contracts/embed.d.ts +41 -0
- package/dist/contracts/embed.js +20 -0
- package/dist/contracts/embed.js.map +1 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.js +6 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/job.d.ts +113 -0
- package/dist/contracts/job.js +32 -0
- package/dist/contracts/job.js.map +1 -0
- package/dist/contracts/llm.d.ts +30 -0
- package/dist/contracts/llm.js +2 -0
- package/dist/contracts/llm.js.map +1 -0
- package/dist/contracts/memory.d.ts +47 -0
- package/dist/contracts/memory.js +5 -0
- package/dist/contracts/memory.js.map +1 -0
- package/dist/contracts/vector.d.ts +29 -0
- package/dist/contracts/vector.js +2 -0
- package/dist/contracts/vector.js.map +1 -0
- package/dist/distill/flatten-turns.d.ts +1 -0
- package/dist/distill/flatten-turns.js +50 -0
- package/dist/distill/flatten-turns.js.map +1 -0
- package/dist/distill/pipeline.d.ts +45 -0
- package/dist/distill/pipeline.js +113 -0
- package/dist/distill/pipeline.js.map +1 -0
- package/dist/distill/prompts/extract.d.ts +122 -0
- package/dist/distill/prompts/extract.js +67 -0
- package/dist/distill/prompts/extract.js.map +1 -0
- package/dist/distill/stages/01-cleanup.d.ts +9 -0
- package/dist/distill/stages/01-cleanup.js +67 -0
- package/dist/distill/stages/01-cleanup.js.map +1 -0
- package/dist/distill/stages/02-normalize.d.ts +9 -0
- package/dist/distill/stages/02-normalize.js +76 -0
- package/dist/distill/stages/02-normalize.js.map +1 -0
- package/dist/distill/stages/03-chunk.d.ts +22 -0
- package/dist/distill/stages/03-chunk.js +138 -0
- package/dist/distill/stages/03-chunk.js.map +1 -0
- package/dist/distill/stages/04-compact.d.ts +28 -0
- package/dist/distill/stages/04-compact.js +69 -0
- package/dist/distill/stages/04-compact.js.map +1 -0
- package/dist/distill/stages/05-extract.d.ts +35 -0
- package/dist/distill/stages/05-extract.js +101 -0
- package/dist/distill/stages/05-extract.js.map +1 -0
- package/dist/distill/stages/06-reduce.d.ts +16 -0
- package/dist/distill/stages/06-reduce.js +30 -0
- package/dist/distill/stages/06-reduce.js.map +1 -0
- package/dist/distill/stages/07-memory-normalize.d.ts +27 -0
- package/dist/distill/stages/07-memory-normalize.js +65 -0
- package/dist/distill/stages/07-memory-normalize.js.map +1 -0
- package/dist/distill/stages/08-embed-index.d.ts +31 -0
- package/dist/distill/stages/08-embed-index.js +82 -0
- package/dist/distill/stages/08-embed-index.js.map +1 -0
- package/dist/doctor/checks.d.ts +77 -0
- package/dist/doctor/checks.js +626 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/doctor/hardening-checks.d.ts +9 -0
- package/dist/doctor/hardening-checks.js +182 -0
- package/dist/doctor/hardening-checks.js.map +1 -0
- package/dist/doctor/probes/embed-probe.d.ts +19 -0
- package/dist/doctor/probes/embed-probe.js +47 -0
- package/dist/doctor/probes/embed-probe.js.map +1 -0
- package/dist/doctor/probes/llm-chat-probe.d.ts +11 -0
- package/dist/doctor/probes/llm-chat-probe.js +41 -0
- package/dist/doctor/probes/llm-chat-probe.js.map +1 -0
- package/dist/doctor/probes/plugin-coexistence.d.ts +14 -0
- package/dist/doctor/probes/plugin-coexistence.js +60 -0
- package/dist/doctor/probes/plugin-coexistence.js.map +1 -0
- package/dist/doctor/runner.d.ts +17 -0
- package/dist/doctor/runner.js +53 -0
- package/dist/doctor/runner.js.map +1 -0
- package/dist/doctor/types.d.ts +12 -0
- package/dist/doctor/types.js +2 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/entity/backfill.d.ts +30 -0
- package/dist/entity/backfill.js +55 -0
- package/dist/entity/backfill.js.map +1 -0
- package/dist/entity/extract-entities.d.ts +27 -0
- package/dist/entity/extract-entities.js +86 -0
- package/dist/entity/extract-entities.js.map +1 -0
- package/dist/entity/normalize.d.ts +17 -0
- package/dist/entity/normalize.js +20 -0
- package/dist/entity/normalize.js.map +1 -0
- package/dist/eval/harness.d.ts +96 -0
- package/dist/eval/harness.js +119 -0
- package/dist/eval/harness.js.map +1 -0
- package/dist/eval/metrics.d.ts +23 -0
- package/dist/eval/metrics.js +44 -0
- package/dist/eval/metrics.js.map +1 -0
- package/dist/log/correlation.d.ts +24 -0
- package/dist/log/correlation.js +33 -0
- package/dist/log/correlation.js.map +1 -0
- package/dist/log/logger.d.ts +38 -0
- package/dist/log/logger.js +129 -0
- package/dist/log/logger.js.map +1 -0
- package/dist/log/scrub.d.ts +33 -0
- package/dist/log/scrub.js +91 -0
- package/dist/log/scrub.js.map +1 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.js +553 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/memory-tool/adapter.d.ts +73 -0
- package/dist/memory-tool/adapter.js +269 -0
- package/dist/memory-tool/adapter.js.map +1 -0
- package/dist/pipeline/errors.d.ts +21 -0
- package/dist/pipeline/errors.js +34 -0
- package/dist/pipeline/errors.js.map +1 -0
- package/dist/pipeline/failure-classifier.d.ts +13 -0
- package/dist/pipeline/failure-classifier.js +72 -0
- package/dist/pipeline/failure-classifier.js.map +1 -0
- package/dist/pipeline/handler-ctx-ext.d.ts +23 -0
- package/dist/pipeline/handler-ctx-ext.js +19 -0
- package/dist/pipeline/handler-ctx-ext.js.map +1 -0
- package/dist/pipeline/handler.d.ts +20 -0
- package/dist/pipeline/handler.js +2 -0
- package/dist/pipeline/handler.js.map +1 -0
- package/dist/pipeline/handlers/cleanup.d.ts +14 -0
- package/dist/pipeline/handlers/cleanup.js +47 -0
- package/dist/pipeline/handlers/cleanup.js.map +1 -0
- package/dist/pipeline/handlers/consolidate.d.ts +8 -0
- package/dist/pipeline/handlers/consolidate.js +23 -0
- package/dist/pipeline/handlers/consolidate.js.map +1 -0
- package/dist/pipeline/handlers/distill-events.d.ts +15 -0
- package/dist/pipeline/handlers/distill-events.js +134 -0
- package/dist/pipeline/handlers/distill-events.js.map +1 -0
- package/dist/pipeline/handlers/distill.d.ts +17 -0
- package/dist/pipeline/handlers/distill.js +110 -0
- package/dist/pipeline/handlers/distill.js.map +1 -0
- package/dist/pipeline/handlers/reembed.d.ts +10 -0
- package/dist/pipeline/handlers/reembed.js +34 -0
- package/dist/pipeline/handlers/reembed.js.map +1 -0
- package/dist/pipeline/job-repo.d.ts +86 -0
- package/dist/pipeline/job-repo.js +168 -0
- package/dist/pipeline/job-repo.js.map +1 -0
- package/dist/pipeline/mock-providers.d.ts +49 -0
- package/dist/pipeline/mock-providers.js +175 -0
- package/dist/pipeline/mock-providers.js.map +1 -0
- package/dist/pipeline/registry.d.ts +15 -0
- package/dist/pipeline/registry.js +20 -0
- package/dist/pipeline/registry.js.map +1 -0
- package/dist/pipeline/worker.d.ts +41 -0
- package/dist/pipeline/worker.js +167 -0
- package/dist/pipeline/worker.js.map +1 -0
- package/dist/providers/embed/azure-openai.d.ts +25 -0
- package/dist/providers/embed/azure-openai.js +138 -0
- package/dist/providers/embed/azure-openai.js.map +1 -0
- package/dist/providers/embed/ollama.d.ts +17 -0
- package/dist/providers/embed/ollama.js +106 -0
- package/dist/providers/embed/ollama.js.map +1 -0
- package/dist/providers/index.d.ts +19 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/llm/azure-openai.d.ts +20 -0
- package/dist/providers/llm/azure-openai.js +135 -0
- package/dist/providers/llm/azure-openai.js.map +1 -0
- package/dist/providers/llm/ollama.d.ts +13 -0
- package/dist/providers/llm/ollama.js +113 -0
- package/dist/providers/llm/ollama.js.map +1 -0
- package/dist/providers/llm/pricing.d.ts +21 -0
- package/dist/providers/llm/pricing.js +22 -0
- package/dist/providers/llm/pricing.js.map +1 -0
- package/dist/recall/pack.d.ts +32 -0
- package/dist/recall/pack.js +90 -0
- package/dist/recall/pack.js.map +1 -0
- package/dist/recall/policy.d.ts +39 -0
- package/dist/recall/policy.js +96 -0
- package/dist/recall/policy.js.map +1 -0
- package/dist/redact/detectors.d.ts +20 -0
- package/dist/redact/detectors.js +85 -0
- package/dist/redact/detectors.js.map +1 -0
- package/dist/redact/entropy.d.ts +24 -0
- package/dist/redact/entropy.js +77 -0
- package/dist/redact/entropy.js.map +1 -0
- package/dist/redact/index.d.ts +47 -0
- package/dist/redact/index.js +165 -0
- package/dist/redact/index.js.map +1 -0
- package/dist/search/fuse.d.ts +108 -0
- package/dist/search/fuse.js +135 -0
- package/dist/search/fuse.js.map +1 -0
- package/dist/search/query.d.ts +28 -0
- package/dist/search/query.js +70 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/search.d.ts +164 -0
- package/dist/search/search.js +310 -0
- package/dist/search/search.js.map +1 -0
- package/dist/server/app.d.ts +17 -0
- package/dist/server/app.js +133 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/health-state.d.ts +29 -0
- package/dist/server/health-state.js +28 -0
- package/dist/server/health-state.js.map +1 -0
- package/dist/server/lib/network.d.ts +12 -0
- package/dist/server/lib/network.js +16 -0
- package/dist/server/lib/network.js.map +1 -0
- package/dist/server/lib/score-contract.d.ts +36 -0
- package/dist/server/lib/score-contract.js +54 -0
- package/dist/server/lib/score-contract.js.map +1 -0
- package/dist/server/lib/stable-stringify.d.ts +10 -0
- package/dist/server/lib/stable-stringify.js +27 -0
- package/dist/server/lib/stable-stringify.js.map +1 -0
- package/dist/server/lib/wire-meta.d.ts +7 -0
- package/dist/server/lib/wire-meta.js +29 -0
- package/dist/server/lib/wire-meta.js.map +1 -0
- package/dist/server/queries/dashboard.d.ts +142 -0
- package/dist/server/queries/dashboard.js +166 -0
- package/dist/server/queries/dashboard.js.map +1 -0
- package/dist/server/routes/consolidation.d.ts +14 -0
- package/dist/server/routes/consolidation.js +67 -0
- package/dist/server/routes/consolidation.js.map +1 -0
- package/dist/server/routes/dashboard-api-html.d.ts +15 -0
- package/dist/server/routes/dashboard-api-html.js +144 -0
- package/dist/server/routes/dashboard-api-html.js.map +1 -0
- package/dist/server/routes/dashboard-consolidation-html.d.ts +26 -0
- package/dist/server/routes/dashboard-consolidation-html.js +202 -0
- package/dist/server/routes/dashboard-consolidation-html.js.map +1 -0
- package/dist/server/routes/dashboard-html.d.ts +15 -0
- package/dist/server/routes/dashboard-html.js +365 -0
- package/dist/server/routes/dashboard-html.js.map +1 -0
- package/dist/server/routes/dashboard-jobs-html.d.ts +18 -0
- package/dist/server/routes/dashboard-jobs-html.js +186 -0
- package/dist/server/routes/dashboard-jobs-html.js.map +1 -0
- package/dist/server/routes/dashboard-search-html.d.ts +18 -0
- package/dist/server/routes/dashboard-search-html.js +189 -0
- package/dist/server/routes/dashboard-search-html.js.map +1 -0
- package/dist/server/routes/dashboard.d.ts +19 -0
- package/dist/server/routes/dashboard.js +68 -0
- package/dist/server/routes/dashboard.js.map +1 -0
- package/dist/server/routes/digest.d.ts +9 -0
- package/dist/server/routes/digest.js +37 -0
- package/dist/server/routes/digest.js.map +1 -0
- package/dist/server/routes/entities.d.ts +12 -0
- package/dist/server/routes/entities.js +46 -0
- package/dist/server/routes/entities.js.map +1 -0
- package/dist/server/routes/health.d.ts +14 -0
- package/dist/server/routes/health.js +100 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/ingest.d.ts +209 -0
- package/dist/server/routes/ingest.js +454 -0
- package/dist/server/routes/ingest.js.map +1 -0
- package/dist/server/routes/lifecycle.d.ts +21 -0
- package/dist/server/routes/lifecycle.js +132 -0
- package/dist/server/routes/lifecycle.js.map +1 -0
- package/dist/server/routes/mcp.d.ts +15 -0
- package/dist/server/routes/mcp.js +36 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/memory-tool.d.ts +14 -0
- package/dist/server/routes/memory-tool.js +28 -0
- package/dist/server/routes/memory-tool.js.map +1 -0
- package/dist/server/routes/memory.d.ts +7 -0
- package/dist/server/routes/memory.js +19 -0
- package/dist/server/routes/memory.js.map +1 -0
- package/dist/server/routes/recall.d.ts +15 -0
- package/dist/server/routes/recall.js +74 -0
- package/dist/server/routes/recall.js.map +1 -0
- package/dist/server/routes/search.d.ts +12 -0
- package/dist/server/routes/search.js +203 -0
- package/dist/server/routes/search.js.map +1 -0
- package/dist/server/routes/version.d.ts +2 -0
- package/dist/server/routes/version.js +11 -0
- package/dist/server/routes/version.js.map +1 -0
- package/dist/server/routes/why.d.ts +9 -0
- package/dist/server/routes/why.js +38 -0
- package/dist/server/routes/why.js.map +1 -0
- package/dist/service/index.d.ts +10 -0
- package/dist/service/index.js +25 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/install-flow.d.ts +18 -0
- package/dist/service/install-flow.js +47 -0
- package/dist/service/install-flow.js.map +1 -0
- package/dist/service/instance-lock.d.ts +26 -0
- package/dist/service/instance-lock.js +150 -0
- package/dist/service/instance-lock.js.map +1 -0
- package/dist/service/launchd.d.ts +11 -0
- package/dist/service/launchd.js +196 -0
- package/dist/service/launchd.js.map +1 -0
- package/dist/service/schtasks.d.ts +31 -0
- package/dist/service/schtasks.js +274 -0
- package/dist/service/schtasks.js.map +1 -0
- package/dist/service/shim.d.ts +21 -0
- package/dist/service/shim.js +80 -0
- package/dist/service/shim.js.map +1 -0
- package/dist/service/systemd.d.ts +11 -0
- package/dist/service/systemd.js +150 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/task-xml.d.ts +36 -0
- package/dist/service/task-xml.js +91 -0
- package/dist/service/task-xml.js.map +1 -0
- package/dist/service/types.d.ts +47 -0
- package/dist/service/types.js +2 -0
- package/dist/service/types.js.map +1 -0
- package/dist/storage/archival.d.ts +29 -0
- package/dist/storage/archival.js +47 -0
- package/dist/storage/archival.js.map +1 -0
- package/dist/storage/bearer-keystore.d.ts +34 -0
- package/dist/storage/bearer-keystore.js +75 -0
- package/dist/storage/bearer-keystore.js.map +1 -0
- package/dist/storage/db.d.ts +37 -0
- package/dist/storage/db.js +92 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/entities.d.ts +71 -0
- package/dist/storage/entities.js +141 -0
- package/dist/storage/entities.js.map +1 -0
- package/dist/storage/ingest-idempotency.d.ts +26 -0
- package/dist/storage/ingest-idempotency.js +29 -0
- package/dist/storage/ingest-idempotency.js.map +1 -0
- package/dist/storage/keystore.d.ts +64 -0
- package/dist/storage/keystore.js +194 -0
- package/dist/storage/keystore.js.map +1 -0
- package/dist/storage/memories.d.ts +51 -0
- package/dist/storage/memories.js +67 -0
- package/dist/storage/memories.js.map +1 -0
- package/dist/storage/memory-events.d.ts +145 -0
- package/dist/storage/memory-events.js +287 -0
- package/dist/storage/memory-events.js.map +1 -0
- package/dist/storage/migrate-encrypt.d.ts +16 -0
- package/dist/storage/migrate-encrypt.js +121 -0
- package/dist/storage/migrate-encrypt.js.map +1 -0
- package/dist/storage/migrate.d.ts +27 -0
- package/dist/storage/migrate.js +105 -0
- package/dist/storage/migrate.js.map +1 -0
- package/dist/storage/redaction-log.d.ts +18 -0
- package/dist/storage/redaction-log.js +27 -0
- package/dist/storage/redaction-log.js.map +1 -0
- package/dist/storage/usefulness.d.ts +115 -0
- package/dist/storage/usefulness.js +203 -0
- package/dist/storage/usefulness.js.map +1 -0
- package/dist/sync/conflict-resolve.d.ts +26 -0
- package/dist/sync/conflict-resolve.js +139 -0
- package/dist/sync/conflict-resolve.js.map +1 -0
- package/dist/sync/puller.d.ts +115 -0
- package/dist/sync/puller.js +173 -0
- package/dist/sync/puller.js.map +1 -0
- package/dist/sync/shipper.d.ts +112 -0
- package/dist/sync/shipper.js +189 -0
- package/dist/sync/shipper.js.map +1 -0
- package/dist/tag-hygiene/backfill.d.ts +50 -0
- package/dist/tag-hygiene/backfill.js +117 -0
- package/dist/tag-hygiene/backfill.js.map +1 -0
- package/dist/tag-hygiene/derive-repo.d.ts +9 -0
- package/dist/tag-hygiene/derive-repo.js +19 -0
- package/dist/tag-hygiene/derive-repo.js.map +1 -0
- package/dist/tag-hygiene/tier2-infer.d.ts +28 -0
- package/dist/tag-hygiene/tier2-infer.js +72 -0
- package/dist/tag-hygiene/tier2-infer.js.map +1 -0
- package/dist/vector/sqlite-vec.d.ts +16 -0
- package/dist/vector/sqlite-vec.js +49 -0
- package/dist/vector/sqlite-vec.js.map +1 -0
- package/migrations/001-init.sql +117 -0
- package/migrations/002-wire-v1.sql +16 -0
- package/migrations/003-expand-memory-types.sql +81 -0
- package/migrations/004-provenance.sql +4 -0
- package/migrations/005-security.sql +12 -0
- package/migrations/006-atom-v3.sql +28 -0
- package/migrations/007-memory-events.sql +30 -0
- package/migrations/008-consolidation.sql +31 -0
- package/migrations/009-tag-hygiene.sql +13 -0
- package/migrations/010-sync-pull.sql +53 -0
- package/migrations/011-embed-dim-migration.sql +28 -0
- package/migrations/012-entities.sql +36 -0
- package/migrations/013-archival.sql +50 -0
- package/package.json +50 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import Database from 'better-sqlite3-multiple-ciphers';
|
|
3
|
+
import * as sqliteVec from 'sqlite-vec';
|
|
4
|
+
/**
|
|
5
|
+
* Thrown by `openDb` when the file on disk cannot be trusted as an intact
|
|
6
|
+
* datadir (FEAT-420 AC-3), and NO encryption key was in play. Distinct from
|
|
7
|
+
* a raw `SqliteError` so callers and operators get an actionable message
|
|
8
|
+
* instead of a low-level driver string.
|
|
9
|
+
*/
|
|
10
|
+
export class CorruptDatadirError extends Error {
|
|
11
|
+
constructor(path, reason) {
|
|
12
|
+
super(`Refusing to open ${path}: ${reason}. This looks like a corrupted or truncated ` +
|
|
13
|
+
`SQLite datadir, not a fresh install — a fresh install never pre-creates this file. ` +
|
|
14
|
+
`Restore from a backup ('astramem-local restore') or, if you are certain there is no ` +
|
|
15
|
+
`data to lose, delete the file (and any -wal/-shm siblings) and restart to create a fresh one.`);
|
|
16
|
+
this.name = 'CorruptDatadirError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Thrown by `openDb` when a key WAS supplied but the file could not be read
|
|
21
|
+
* with it (FEAT-420 AC-3 review fix). SQLCipher cannot distinguish
|
|
22
|
+
* "wrong/missing key" from "corrupted file" at the driver level — both
|
|
23
|
+
* surface as the exact same `SqliteError: file is not a database`
|
|
24
|
+
* (SQLITE_NOTADB), which is also the documented stale-v0.3.4-global-install
|
|
25
|
+
* symptom (CLAUDE.md "Do NOT use bare astramem-local"). Reusing
|
|
26
|
+
* CorruptDatadirError's corruption-and-delete advice here would actively
|
|
27
|
+
* steer an operator with a recoverable key/config problem toward destroying
|
|
28
|
+
* a healthy encrypted database — so this is a distinct class with distinct,
|
|
29
|
+
* non-destructive advice and NO deletion suggestion.
|
|
30
|
+
*/
|
|
31
|
+
export class EncryptionKeyError extends Error {
|
|
32
|
+
constructor(path, reason) {
|
|
33
|
+
super(`Refusing to open ${path}: ${reason}. A key was supplied but could not decrypt this file — ` +
|
|
34
|
+
`this is the classic wrong-or-missing-encryption-key symptom, not necessarily corruption ` +
|
|
35
|
+
`(SQLCipher cannot tell the two apart at this layer). Before assuming data loss, check: ` +
|
|
36
|
+
`(1) the key material in secrets.env matches what encrypted this file; ` +
|
|
37
|
+
`(2) you are running the repo build ('node dist/cli/index.js ...'), not a stale globally-` +
|
|
38
|
+
`installed 'astramem-local' shadowing it with a different cipher driver; ` +
|
|
39
|
+
`(3) the key hasn't rotated without re-encrypting this file. Leave this file untouched until ` +
|
|
40
|
+
`one of those is resolved — it may still hold your data intact.`);
|
|
41
|
+
this.name = 'EncryptionKeyError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function openDb(path, opts = {}) {
|
|
45
|
+
// FEAT-420 AC-3: a pre-existing but EMPTY (0-byte) file is silently
|
|
46
|
+
// accepted by SQLite as a brand-new database — better-sqlite3 just
|
|
47
|
+
// initializes it, and migrate() then happily builds a pristine empty
|
|
48
|
+
// schema on top, masking what is actually a truncated/corrupted datadir
|
|
49
|
+
// as data loss with no error at all. A genuinely fresh install never has
|
|
50
|
+
// a pre-existing file at this path (the file doesn't exist until this
|
|
51
|
+
// call creates it), so "file exists AND is 0 bytes" is an unambiguous
|
|
52
|
+
// signal of truncation, not a legitimate first run.
|
|
53
|
+
if (path !== ':memory:' && existsSync(path) && statSync(path).size === 0) {
|
|
54
|
+
throw new CorruptDatadirError(path, 'file exists but is empty (0 bytes)');
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const db = new Database(path);
|
|
58
|
+
if (path !== ':memory:' && opts.key) {
|
|
59
|
+
db.pragma(`key='${opts.key.replace(/'/g, "''")}'`);
|
|
60
|
+
}
|
|
61
|
+
// Block-and-retry for up to 5s on a locked database instead of throwing
|
|
62
|
+
// SQLITE_BUSY immediately — covers concurrent writers (backup CLI's
|
|
63
|
+
// second connection, doctor probes) against the live daemon.
|
|
64
|
+
db.pragma('busy_timeout = 5000');
|
|
65
|
+
// better-sqlite3 opens the file handle lazily — "file is not a
|
|
66
|
+
// database" / "database disk image is malformed" (garbage bytes, a
|
|
67
|
+
// truncated real file) only surface on the FIRST pragma/exec that
|
|
68
|
+
// actually reads a page, not at `new Database()` itself. Everything
|
|
69
|
+
// through the vec extension load is wrapped so any of these still
|
|
70
|
+
// become the same actionable typed error as the 0-byte case above.
|
|
71
|
+
db.pragma('journal_mode = WAL');
|
|
72
|
+
db.pragma('foreign_keys = ON');
|
|
73
|
+
db.pragma('synchronous = NORMAL');
|
|
74
|
+
sqliteVec.load(db);
|
|
75
|
+
return db;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const alreadyTyped = err instanceof CorruptDatadirError || err instanceof EncryptionKeyError;
|
|
79
|
+
if (path !== ':memory:' && err instanceof Error && !alreadyTyped) {
|
|
80
|
+
// A key was supplied (opts.key) -> the same raw driver error is the
|
|
81
|
+
// wrong/missing-key symptom, not necessarily corruption — see
|
|
82
|
+
// EncryptionKeyError's doc comment. Only diagnose corruption when no
|
|
83
|
+
// key was ever in play.
|
|
84
|
+
if (opts.key) {
|
|
85
|
+
throw new EncryptionKeyError(path, err.message);
|
|
86
|
+
}
|
|
87
|
+
throw new CorruptDatadirError(path, err.message);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAexC;;;;;GAKG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,IAAY,EAAE,MAAc;QACtC,KAAK,CACH,oBAAoB,IAAI,KAAK,MAAM,6CAA6C;YAChF,qFAAqF;YACrF,sFAAsF;YACtF,+FAA+F,CAChG,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,IAAY,EAAE,MAAc;QACtC,KAAK,CACH,oBAAoB,IAAI,KAAK,MAAM,yDAAyD;YAC5F,0FAA0F;YAC1F,yFAAyF;YACzF,wEAAwE;YACxE,0FAA0F;YAC1F,0EAA0E;YAC1E,8FAA8F;YAC9F,gEAAgE,CACjE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,OAAmB,EAAE;IACxD,oEAAoE;IACpE,mEAAmE;IACnE,qEAAqE;IACrE,wEAAwE;IACxE,yEAAyE;IACzE,sEAAsE;IACtE,sEAAsE;IACtE,oDAAoD;IACpD,IAAI,IAAI,KAAK,UAAU,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,mBAAmB,CAAC,IAAI,EAAE,oCAAoC,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,EAAE,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,wEAAwE;QACxE,oEAAoE;QACpE,6DAA6D;QAC7D,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACjC,+DAA+D;QAC/D,mEAAmE;QACnE,kEAAkE;QAClE,oEAAoE;QACpE,kEAAkE;QAClE,mEAAmE;QACnE,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC/B,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAClC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,mBAAmB,IAAI,GAAG,YAAY,kBAAkB,CAAC;QAC7F,IAAI,IAAI,KAAK,UAAU,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;YACjE,oEAAoE;YACpE,8DAA8D;YAC9D,qEAAqE;YACrE,wBAAwB;YACxB,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAClD,CAAC;YACD,MAAM,IAAI,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entities + memory_entities repo (FEAT-402 slice 1, migration 012).
|
|
3
|
+
*
|
|
4
|
+
* `entities.name` is UNIQUE and always stores the NORMALIZED form
|
|
5
|
+
* (src/entity/normalize.ts normalizeEntityName — AC-9's single seam), so
|
|
6
|
+
* `upsert('Postgres', 'tech')` and `upsert('postgres', 'tech')` resolve to
|
|
7
|
+
* the same row. `memory_entities` is a plain link table written by both the
|
|
8
|
+
* distill-time embed-index stage (same LLM call as atom extraction) and the
|
|
9
|
+
* `entity-backfill` CLI command for the pre-existing corpus.
|
|
10
|
+
*/
|
|
11
|
+
import type { DB } from './db.js';
|
|
12
|
+
export interface EntityRow {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
}
|
|
17
|
+
export interface EntityInput {
|
|
18
|
+
name: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class EntityRepo {
|
|
22
|
+
private db;
|
|
23
|
+
constructor(db: DB);
|
|
24
|
+
/**
|
|
25
|
+
* Resolve `name` (normalized) to an entities.id, creating the row if it
|
|
26
|
+
* doesn't exist yet. `kind` is only applied on first-create — a later call
|
|
27
|
+
* with a different kind for the same normalized name does NOT overwrite
|
|
28
|
+
* it (first-writer-wins avoids kind flip-flopping across distill runs).
|
|
29
|
+
*/
|
|
30
|
+
upsert(name: string, kind: string): string;
|
|
31
|
+
/** Idempotent link — INSERT OR IGNORE on the (memory_id, entity_id) PK. */
|
|
32
|
+
link(memoryId: string, entityId: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Upsert + link every entity in one call — the shape both the distill-time
|
|
35
|
+
* embed-index stage and the backfill command consume. No-op for an empty
|
|
36
|
+
* or missing list.
|
|
37
|
+
*/
|
|
38
|
+
linkEntitiesForMemory(memoryId: string, entities: EntityInput[] | undefined): void;
|
|
39
|
+
/** Remove every memory_entities row for one memory (AC-3 supersede cascade). */
|
|
40
|
+
unlinkMemory(memoryId: string): void;
|
|
41
|
+
/** All entities linked to a memory — used by tests and the (slice 2) query surface. */
|
|
42
|
+
listForMemory(memoryId: string): EntityRow[];
|
|
43
|
+
/** True when `name` (normalized) already resolves to an entities row. */
|
|
44
|
+
exists(name: string): boolean;
|
|
45
|
+
/** Single entity by id — GET /entities/:id/memories 404s when this returns undefined. */
|
|
46
|
+
getById(id: string): EntityRow | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Autocomplete (FEAT-402 slice 2 AC-5, `GET /entities?q=`) — substring
|
|
49
|
+
* match on the normalized name, case/whitespace-insensitive via the same
|
|
50
|
+
* AC-9 seam every other lookup goes through. Empty/whitespace `query`
|
|
51
|
+
* returns the first `limit` entities alphabetically rather than nothing,
|
|
52
|
+
* so an empty-q autocomplete call still has sane behavior.
|
|
53
|
+
*/
|
|
54
|
+
search(query: string, limit?: number): EntityRow[];
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a free-form entity query (e.g. the search `entity=` filter,
|
|
57
|
+
* FEAT-402 slice 2 AC-4) to entity ids via the AC-9 normalize seam: exact
|
|
58
|
+
* match on the normalized name first, falling back to a LIKE substring
|
|
59
|
+
* match only when no exact row exists. Returns [] when nothing matches —
|
|
60
|
+
* callers MUST treat that as "no entity-linked candidates" rather than
|
|
61
|
+
* falling back to an unfiltered search.
|
|
62
|
+
*/
|
|
63
|
+
resolveIds(query: string): string[];
|
|
64
|
+
/**
|
|
65
|
+
* All distinct memory ids linked to any of `entityIds` — the join behind
|
|
66
|
+
* both the search `entity=` pre-fusion intersection (AC-4) and
|
|
67
|
+
* `GET /entities/:id/memories` (AC-5). Empty input returns an empty set
|
|
68
|
+
* rather than every memory (an empty entityIds list is never "no filter").
|
|
69
|
+
*/
|
|
70
|
+
memoryIdsForEntities(entityIds: string[]): Set<string>;
|
|
71
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entities + memory_entities repo (FEAT-402 slice 1, migration 012).
|
|
3
|
+
*
|
|
4
|
+
* `entities.name` is UNIQUE and always stores the NORMALIZED form
|
|
5
|
+
* (src/entity/normalize.ts normalizeEntityName — AC-9's single seam), so
|
|
6
|
+
* `upsert('Postgres', 'tech')` and `upsert('postgres', 'tech')` resolve to
|
|
7
|
+
* the same row. `memory_entities` is a plain link table written by both the
|
|
8
|
+
* distill-time embed-index stage (same LLM call as atom extraction) and the
|
|
9
|
+
* `entity-backfill` CLI command for the pre-existing corpus.
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { normalizeEntityName } from '../entity/normalize.js';
|
|
13
|
+
export class EntityRepo {
|
|
14
|
+
db;
|
|
15
|
+
constructor(db) {
|
|
16
|
+
this.db = db;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve `name` (normalized) to an entities.id, creating the row if it
|
|
20
|
+
* doesn't exist yet. `kind` is only applied on first-create — a later call
|
|
21
|
+
* with a different kind for the same normalized name does NOT overwrite
|
|
22
|
+
* it (first-writer-wins avoids kind flip-flopping across distill runs).
|
|
23
|
+
*/
|
|
24
|
+
upsert(name, kind) {
|
|
25
|
+
const normalized = normalizeEntityName(name);
|
|
26
|
+
const existing = this.db
|
|
27
|
+
.prepare('SELECT id FROM entities WHERE name = ?')
|
|
28
|
+
.get(normalized);
|
|
29
|
+
if (existing)
|
|
30
|
+
return existing.id;
|
|
31
|
+
const id = randomUUID();
|
|
32
|
+
this.db.prepare('INSERT INTO entities (id, name, kind) VALUES (?, ?, ?)').run(id, normalized, kind);
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
35
|
+
/** Idempotent link — INSERT OR IGNORE on the (memory_id, entity_id) PK. */
|
|
36
|
+
link(memoryId, entityId) {
|
|
37
|
+
this.db
|
|
38
|
+
.prepare('INSERT OR IGNORE INTO memory_entities (memory_id, entity_id) VALUES (?, ?)')
|
|
39
|
+
.run(memoryId, entityId);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Upsert + link every entity in one call — the shape both the distill-time
|
|
43
|
+
* embed-index stage and the backfill command consume. No-op for an empty
|
|
44
|
+
* or missing list.
|
|
45
|
+
*/
|
|
46
|
+
linkEntitiesForMemory(memoryId, entities) {
|
|
47
|
+
if (!entities || entities.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
for (const e of entities) {
|
|
50
|
+
const entityId = this.upsert(e.name, e.kind);
|
|
51
|
+
this.link(memoryId, entityId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Remove every memory_entities row for one memory (AC-3 supersede cascade). */
|
|
55
|
+
unlinkMemory(memoryId) {
|
|
56
|
+
this.db.prepare('DELETE FROM memory_entities WHERE memory_id = ?').run(memoryId);
|
|
57
|
+
}
|
|
58
|
+
/** All entities linked to a memory — used by tests and the (slice 2) query surface. */
|
|
59
|
+
listForMemory(memoryId) {
|
|
60
|
+
return this.db
|
|
61
|
+
.prepare(`
|
|
62
|
+
SELECT e.id, e.name, e.kind
|
|
63
|
+
FROM entities e
|
|
64
|
+
JOIN memory_entities me ON me.entity_id = e.id
|
|
65
|
+
WHERE me.memory_id = ?
|
|
66
|
+
ORDER BY e.name ASC
|
|
67
|
+
`)
|
|
68
|
+
.all(memoryId);
|
|
69
|
+
}
|
|
70
|
+
/** True when `name` (normalized) already resolves to an entities row. */
|
|
71
|
+
exists(name) {
|
|
72
|
+
const normalized = normalizeEntityName(name);
|
|
73
|
+
const row = this.db.prepare('SELECT 1 AS x FROM entities WHERE name = ?').get(normalized);
|
|
74
|
+
return row !== undefined;
|
|
75
|
+
}
|
|
76
|
+
/** Single entity by id — GET /entities/:id/memories 404s when this returns undefined. */
|
|
77
|
+
getById(id) {
|
|
78
|
+
return this.db.prepare('SELECT id, name, kind FROM entities WHERE id = ?').get(id);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Autocomplete (FEAT-402 slice 2 AC-5, `GET /entities?q=`) — substring
|
|
82
|
+
* match on the normalized name, case/whitespace-insensitive via the same
|
|
83
|
+
* AC-9 seam every other lookup goes through. Empty/whitespace `query`
|
|
84
|
+
* returns the first `limit` entities alphabetically rather than nothing,
|
|
85
|
+
* so an empty-q autocomplete call still has sane behavior.
|
|
86
|
+
*/
|
|
87
|
+
search(query, limit = 20) {
|
|
88
|
+
const normalized = normalizeEntityName(query);
|
|
89
|
+
if (!normalized) {
|
|
90
|
+
return this.db
|
|
91
|
+
.prepare('SELECT id, name, kind FROM entities ORDER BY name ASC LIMIT ?')
|
|
92
|
+
.all(limit);
|
|
93
|
+
}
|
|
94
|
+
const escaped = escapeLike(normalized);
|
|
95
|
+
return this.db
|
|
96
|
+
.prepare(`SELECT id, name, kind FROM entities WHERE name LIKE ? ESCAPE '\\' ORDER BY name ASC LIMIT ?`)
|
|
97
|
+
.all(`%${escaped}%`, limit);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a free-form entity query (e.g. the search `entity=` filter,
|
|
101
|
+
* FEAT-402 slice 2 AC-4) to entity ids via the AC-9 normalize seam: exact
|
|
102
|
+
* match on the normalized name first, falling back to a LIKE substring
|
|
103
|
+
* match only when no exact row exists. Returns [] when nothing matches —
|
|
104
|
+
* callers MUST treat that as "no entity-linked candidates" rather than
|
|
105
|
+
* falling back to an unfiltered search.
|
|
106
|
+
*/
|
|
107
|
+
resolveIds(query) {
|
|
108
|
+
const normalized = normalizeEntityName(query);
|
|
109
|
+
if (!normalized)
|
|
110
|
+
return [];
|
|
111
|
+
const exact = this.db.prepare('SELECT id FROM entities WHERE name = ?').get(normalized);
|
|
112
|
+
if (exact)
|
|
113
|
+
return [exact.id];
|
|
114
|
+
const escaped = escapeLike(normalized);
|
|
115
|
+
const rows = this.db
|
|
116
|
+
.prepare(`SELECT id FROM entities WHERE name LIKE ? ESCAPE '\\'`)
|
|
117
|
+
.all(`%${escaped}%`);
|
|
118
|
+
return rows.map(r => r.id);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* All distinct memory ids linked to any of `entityIds` — the join behind
|
|
122
|
+
* both the search `entity=` pre-fusion intersection (AC-4) and
|
|
123
|
+
* `GET /entities/:id/memories` (AC-5). Empty input returns an empty set
|
|
124
|
+
* rather than every memory (an empty entityIds list is never "no filter").
|
|
125
|
+
*/
|
|
126
|
+
memoryIdsForEntities(entityIds) {
|
|
127
|
+
if (entityIds.length === 0)
|
|
128
|
+
return new Set();
|
|
129
|
+
const placeholders = entityIds.map(() => '?').join(',');
|
|
130
|
+
const rows = this.db
|
|
131
|
+
.prepare(`SELECT DISTINCT memory_id FROM memory_entities WHERE entity_id IN (${placeholders})`)
|
|
132
|
+
.all(...entityIds);
|
|
133
|
+
return new Set(rows.map(r => r.memory_id));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Escape SQL LIKE wildcards (`%`, `_`) in a substring pattern the caller
|
|
137
|
+
* did not intend as wildcard syntax — used with `ESCAPE '\\'` above. */
|
|
138
|
+
function escapeLike(value) {
|
|
139
|
+
return value.replace(/[\\%_]/g, ch => `\\${ch}`);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/storage/entities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D-DEF2 fix — `summary_memory_id` backfill.
|
|
3
|
+
*
|
|
4
|
+
* `POST /ingest/transcript` (server/routes/ingest.ts) writes the transcript
|
|
5
|
+
* row's own id into `ingest_idempotency.summary_memory_id` at insert time —
|
|
6
|
+
* distillation is async (queued as a `distill` job), so there is no real
|
|
7
|
+
* memory id yet when the HTTP response is built. That placeholder value was
|
|
8
|
+
* never updated afterwards, so idempotent replays kept returning the
|
|
9
|
+
* transcript id forever, even after distillation produced real memories
|
|
10
|
+
* (CHANGELOG 0.2.0 "Known issues" — D-DEF2: "Wave-3 distillation will
|
|
11
|
+
* produce a real summary memory... semantic meaning will change when
|
|
12
|
+
* distillation is wired end-to-end").
|
|
13
|
+
*
|
|
14
|
+
* This backfills the row once the distill job actually produces a memory.
|
|
15
|
+
* No schema change: `summary_memory_id` already accepts an arbitrary TEXT
|
|
16
|
+
* id (it has never had a FK constraint tying it to `transcripts.id`), so
|
|
17
|
+
* repointing it at a `memories.id` is a value change, not a shape change.
|
|
18
|
+
*/
|
|
19
|
+
import type { DB } from './db.js';
|
|
20
|
+
/**
|
|
21
|
+
* Repoint any `ingest_idempotency` row still holding `transcriptId` as its
|
|
22
|
+
* placeholder `summary_memory_id` to the real `memoryId` produced by
|
|
23
|
+
* distillation. No-op if no such row exists (e.g. the original POST carried
|
|
24
|
+
* no `Idempotency-Key`, so no row was ever created).
|
|
25
|
+
*/
|
|
26
|
+
export declare function backfillSummaryMemoryId(db: DB, transcriptId: string, memoryId: string): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D-DEF2 fix — `summary_memory_id` backfill.
|
|
3
|
+
*
|
|
4
|
+
* `POST /ingest/transcript` (server/routes/ingest.ts) writes the transcript
|
|
5
|
+
* row's own id into `ingest_idempotency.summary_memory_id` at insert time —
|
|
6
|
+
* distillation is async (queued as a `distill` job), so there is no real
|
|
7
|
+
* memory id yet when the HTTP response is built. That placeholder value was
|
|
8
|
+
* never updated afterwards, so idempotent replays kept returning the
|
|
9
|
+
* transcript id forever, even after distillation produced real memories
|
|
10
|
+
* (CHANGELOG 0.2.0 "Known issues" — D-DEF2: "Wave-3 distillation will
|
|
11
|
+
* produce a real summary memory... semantic meaning will change when
|
|
12
|
+
* distillation is wired end-to-end").
|
|
13
|
+
*
|
|
14
|
+
* This backfills the row once the distill job actually produces a memory.
|
|
15
|
+
* No schema change: `summary_memory_id` already accepts an arbitrary TEXT
|
|
16
|
+
* id (it has never had a FK constraint tying it to `transcripts.id`), so
|
|
17
|
+
* repointing it at a `memories.id` is a value change, not a shape change.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Repoint any `ingest_idempotency` row still holding `transcriptId` as its
|
|
21
|
+
* placeholder `summary_memory_id` to the real `memoryId` produced by
|
|
22
|
+
* distillation. No-op if no such row exists (e.g. the original POST carried
|
|
23
|
+
* no `Idempotency-Key`, so no row was ever created).
|
|
24
|
+
*/
|
|
25
|
+
export function backfillSummaryMemoryId(db, transcriptId, memoryId) {
|
|
26
|
+
db.prepare('UPDATE ingest_idempotency SET summary_memory_id = ? WHERE summary_memory_id = ?')
|
|
27
|
+
.run(memoryId, transcriptId);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=ingest-idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest-idempotency.js","sourceRoot":"","sources":["../../src/storage/ingest-idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAAM,EAAE,YAAoB,EAAE,QAAgB;IACpF,EAAE,CAAC,OAAO,CAAC,iFAAiF,CAAC;SAC1F,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keystore.ts — encryption-at-rest key management (SEC-1/2, Wave 1 task 1b).
|
|
3
|
+
*
|
|
4
|
+
* Provider chain (per spec §4.1 / OQ-1):
|
|
5
|
+
* (a) OS credential store — Windows Credential Manager / macOS Keychain /
|
|
6
|
+
* Linux libsecret, via `@napi-rs/keyring` (prebuilt N-API binding,
|
|
7
|
+
* verified to install cleanly with prebuilds for win32-x64-msvc,
|
|
8
|
+
* darwin-x64/arm64, and linux-x64-gnu — the repo's 3 CI targets).
|
|
9
|
+
* (b) key-file fallback (`<configDir>/db.key`, mode 0600) with a WARN log —
|
|
10
|
+
* used when the credential store throws (e.g. headless Linux with no
|
|
11
|
+
* secret-service/dbus session). Spec OQ-1 explicitly allows this
|
|
12
|
+
* fallback for v0.4.
|
|
13
|
+
*
|
|
14
|
+
* Key lookup identity is fixed: service `'astramem-local'`, account `'db-key'`.
|
|
15
|
+
* The key is a 32-byte cryptographically random value, hex-encoded (64 chars),
|
|
16
|
+
* used as the SQLCipher passphrase via `PRAGMA key='<hex>'` — the exact
|
|
17
|
+
* PRAGMA form validated end-to-end (open/reopen/wrong-key) by the 1a spike
|
|
18
|
+
* (tests/security/cipher-spike.test.ts).
|
|
19
|
+
*
|
|
20
|
+
* getOrCreateKey() is idempotent: a second call against the same configDir
|
|
21
|
+
* (and, for the credential-store path, the same machine/user) returns the
|
|
22
|
+
* same key rather than minting a new one.
|
|
23
|
+
*/
|
|
24
|
+
import { Entry } from '@napi-rs/keyring';
|
|
25
|
+
export type KeySource = 'credential-store' | 'key-file';
|
|
26
|
+
export interface KeyResult {
|
|
27
|
+
key: string;
|
|
28
|
+
source: KeySource;
|
|
29
|
+
}
|
|
30
|
+
/** Test-only hook: override (or restore) the credential-store Entry constructor. */
|
|
31
|
+
export declare function __setEntryCtorForTests(ctor: typeof Entry | undefined): void;
|
|
32
|
+
/**
|
|
33
|
+
* Returns the encryption key for this machine/user, creating one on first
|
|
34
|
+
* use. Tries the OS credential store first; falls back to a 0600 key file
|
|
35
|
+
* under `configDir` (with a WARN log) when the credential store throws.
|
|
36
|
+
*
|
|
37
|
+
* NOTE: deviates slightly from the originally-sketched `(configDir) => string`
|
|
38
|
+
* signature — returns `{ key, source }` instead of a bare string so callers
|
|
39
|
+
* (doctor, /health) can report key location (SEC-8/AC-5) without a second,
|
|
40
|
+
* duplicate resolution pass. `.key` is what openDb()/migrate-encrypt need.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getOrCreateKey(configDir: string): KeyResult;
|
|
43
|
+
export declare function keyFilePath(configDir: string): string;
|
|
44
|
+
export interface BearerStoreResult {
|
|
45
|
+
stored: boolean;
|
|
46
|
+
source: 'credential-store' | 'secrets-env-fallback';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Store `token` in the OS credential store. On failure (no credential store
|
|
50
|
+
* / no secret-service session), WARNs and returns a fallback marker so the
|
|
51
|
+
* caller persists the bearer into secrets.env instead (SEC-10).
|
|
52
|
+
*/
|
|
53
|
+
export declare function storeBearer(token: string): BearerStoreResult;
|
|
54
|
+
/**
|
|
55
|
+
* Read the bearer token from the OS credential store, if present.
|
|
56
|
+
* Returns null on any error (store unavailable, or no entry yet) — same
|
|
57
|
+
* "throw and getPassword()->null are both just 'no key yet'" tolerance as
|
|
58
|
+
* getOrCreateCredentialStoreKey above. Callers fall back to secrets.env.
|
|
59
|
+
*/
|
|
60
|
+
export declare function readBearer(): string | null;
|
|
61
|
+
/** Store the cloud sync device token (ADR-003 auth) in the credential store. */
|
|
62
|
+
export declare function storeSyncToken(token: string): BearerStoreResult;
|
|
63
|
+
/** Read the cloud sync device token; null when absent/unavailable. */
|
|
64
|
+
export declare function readSyncToken(): string | null;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keystore.ts — encryption-at-rest key management (SEC-1/2, Wave 1 task 1b).
|
|
3
|
+
*
|
|
4
|
+
* Provider chain (per spec §4.1 / OQ-1):
|
|
5
|
+
* (a) OS credential store — Windows Credential Manager / macOS Keychain /
|
|
6
|
+
* Linux libsecret, via `@napi-rs/keyring` (prebuilt N-API binding,
|
|
7
|
+
* verified to install cleanly with prebuilds for win32-x64-msvc,
|
|
8
|
+
* darwin-x64/arm64, and linux-x64-gnu — the repo's 3 CI targets).
|
|
9
|
+
* (b) key-file fallback (`<configDir>/db.key`, mode 0600) with a WARN log —
|
|
10
|
+
* used when the credential store throws (e.g. headless Linux with no
|
|
11
|
+
* secret-service/dbus session). Spec OQ-1 explicitly allows this
|
|
12
|
+
* fallback for v0.4.
|
|
13
|
+
*
|
|
14
|
+
* Key lookup identity is fixed: service `'astramem-local'`, account `'db-key'`.
|
|
15
|
+
* The key is a 32-byte cryptographically random value, hex-encoded (64 chars),
|
|
16
|
+
* used as the SQLCipher passphrase via `PRAGMA key='<hex>'` — the exact
|
|
17
|
+
* PRAGMA form validated end-to-end (open/reopen/wrong-key) by the 1a spike
|
|
18
|
+
* (tests/security/cipher-spike.test.ts).
|
|
19
|
+
*
|
|
20
|
+
* getOrCreateKey() is idempotent: a second call against the same configDir
|
|
21
|
+
* (and, for the credential-store path, the same machine/user) returns the
|
|
22
|
+
* same key rather than minting a new one.
|
|
23
|
+
*/
|
|
24
|
+
import { Entry } from '@napi-rs/keyring';
|
|
25
|
+
import { randomBytes } from 'node:crypto';
|
|
26
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
const SERVICE = 'astramem-local';
|
|
30
|
+
const ACCOUNT = 'db-key';
|
|
31
|
+
const BEARER_ACCOUNT = 'bearer';
|
|
32
|
+
// Test-only indirection so tests can simulate a credential-store outage
|
|
33
|
+
// (e.g. headless-Linux fallback) without needing an actual broken OS
|
|
34
|
+
// keychain, and so keystore tests don't have to mutate the developer's
|
|
35
|
+
// real OS credential store as a side effect of `npm test`.
|
|
36
|
+
let EntryCtor = Entry;
|
|
37
|
+
/** Test-only hook: override (or restore) the credential-store Entry constructor. */
|
|
38
|
+
export function __setEntryCtorForTests(ctor) {
|
|
39
|
+
EntryCtor = ctor ?? Entry;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns the encryption key for this machine/user, creating one on first
|
|
43
|
+
* use. Tries the OS credential store first; falls back to a 0600 key file
|
|
44
|
+
* under `configDir` (with a WARN log) when the credential store throws.
|
|
45
|
+
*
|
|
46
|
+
* NOTE: deviates slightly from the originally-sketched `(configDir) => string`
|
|
47
|
+
* signature — returns `{ key, source }` instead of a bare string so callers
|
|
48
|
+
* (doctor, /health) can report key location (SEC-8/AC-5) without a second,
|
|
49
|
+
* duplicate resolution pass. `.key` is what openDb()/migrate-encrypt need.
|
|
50
|
+
*/
|
|
51
|
+
export function getOrCreateKey(configDir) {
|
|
52
|
+
try {
|
|
53
|
+
return getOrCreateCredentialStoreKey();
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.warn(`[astramem-local] WARNING: OS credential store unavailable (${errMessage(err)}); ` +
|
|
57
|
+
`falling back to a key file with 0600 permissions. This is a weaker guarantee than ` +
|
|
58
|
+
`an OS credential store — see docs/specs/2026-07-02-encryption-and-secret-redaction.md OQ-1.`);
|
|
59
|
+
return getOrCreateKeyFile(configDir);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function errMessage(err) {
|
|
63
|
+
return err instanceof Error ? err.message : String(err);
|
|
64
|
+
}
|
|
65
|
+
function generateKeyHex() {
|
|
66
|
+
return randomBytes(32).toString('hex');
|
|
67
|
+
}
|
|
68
|
+
// ─── Provider (a): OS credential store ────────────────────────────────────
|
|
69
|
+
function getOrCreateCredentialStoreKey() {
|
|
70
|
+
const entry = new EntryCtor(SERVICE, ACCOUNT);
|
|
71
|
+
// getPassword() returns null when absent on some platforms but throws a
|
|
72
|
+
// NoEntry error on others (per @napi-rs/keyring docs) — treat both as
|
|
73
|
+
// "no key yet", not as "credential store broken". Only the constructor
|
|
74
|
+
// and setPassword() throwing should trigger the key-file fallback.
|
|
75
|
+
let existing;
|
|
76
|
+
try {
|
|
77
|
+
existing = entry.getPassword();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
existing = null;
|
|
81
|
+
}
|
|
82
|
+
if (existing) {
|
|
83
|
+
return { key: existing, source: 'credential-store' };
|
|
84
|
+
}
|
|
85
|
+
const key = generateKeyHex();
|
|
86
|
+
entry.setPassword(key);
|
|
87
|
+
return { key, source: 'credential-store' };
|
|
88
|
+
}
|
|
89
|
+
// ─── Provider (b): key-file fallback ──────────────────────────────────────
|
|
90
|
+
function getOrCreateKeyFile(configDir) {
|
|
91
|
+
mkdirSync(configDir, { recursive: true });
|
|
92
|
+
const keyPath = join(configDir, 'db.key');
|
|
93
|
+
if (existsSync(keyPath)) {
|
|
94
|
+
const key = readFileSync(keyPath, 'utf8').trim();
|
|
95
|
+
restrictKeyFileAcl(keyPath); // heal ACLs on files created by older builds
|
|
96
|
+
return { key, source: 'key-file' };
|
|
97
|
+
}
|
|
98
|
+
const key = generateKeyHex();
|
|
99
|
+
writeFileSync(keyPath, key, { mode: 0o600 });
|
|
100
|
+
try {
|
|
101
|
+
// Belt-and-braces: writeFileSync's mode option is honored on creation on
|
|
102
|
+
// POSIX; chmodSync is a no-op on Windows (no POSIX bits) — Windows gets a
|
|
103
|
+
// real ACL below via icacls instead.
|
|
104
|
+
chmodSync(keyPath, 0o600);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* best-effort on platforms without POSIX permission bits */
|
|
108
|
+
}
|
|
109
|
+
restrictKeyFileAcl(keyPath);
|
|
110
|
+
return { key, source: 'key-file' };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Windows: apply a real ACL to the key file — strip inheritance, grant only
|
|
114
|
+
* the current user. Without this, chmod is a no-op and the DB encryption key
|
|
115
|
+
* sits world-readable next to the database it protects (SEC OQ-1).
|
|
116
|
+
* Best-effort but LOUD on failure: a key file without an ACL is a real
|
|
117
|
+
* security downgrade the user must know about.
|
|
118
|
+
*/
|
|
119
|
+
function restrictKeyFileAcl(keyPath) {
|
|
120
|
+
if (process.platform !== 'win32')
|
|
121
|
+
return;
|
|
122
|
+
const user = process.env['USERNAME'];
|
|
123
|
+
if (!user)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
execFileSync('icacls', [keyPath, '/inheritance:r', '/grant:r', `${user}:F`], { stdio: 'pipe', timeout: 10_000 });
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.warn(`[astramem-local] WARNING: could not restrict ACL on key file ${keyPath} ` +
|
|
130
|
+
`(icacls failed: ${errMessage(err)}). The DB encryption key is readable by other ` +
|
|
131
|
+
`local accounts — fix manually: icacls "${keyPath}" /inheritance:r /grant:r ${user}:F`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function keyFilePath(configDir) {
|
|
135
|
+
return join(configDir, 'db.key');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Store `token` in the OS credential store. On failure (no credential store
|
|
139
|
+
* / no secret-service session), WARNs and returns a fallback marker so the
|
|
140
|
+
* caller persists the bearer into secrets.env instead (SEC-10).
|
|
141
|
+
*/
|
|
142
|
+
export function storeBearer(token) {
|
|
143
|
+
try {
|
|
144
|
+
const entry = new EntryCtor(SERVICE, BEARER_ACCOUNT);
|
|
145
|
+
entry.setPassword(token);
|
|
146
|
+
return { stored: true, source: 'credential-store' };
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.warn(`[astramem-local] WARNING: OS credential store unavailable (${errMessage(err)}); ` +
|
|
150
|
+
`falling back to writing the bearer token into secrets.env. This is a weaker guarantee ` +
|
|
151
|
+
`than an OS credential store — see docs/specs/2026-07-02-encryption-and-secret-redaction.md SEC-10.`);
|
|
152
|
+
return { stored: false, source: 'secrets-env-fallback' };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read the bearer token from the OS credential store, if present.
|
|
157
|
+
* Returns null on any error (store unavailable, or no entry yet) — same
|
|
158
|
+
* "throw and getPassword()->null are both just 'no key yet'" tolerance as
|
|
159
|
+
* getOrCreateCredentialStoreKey above. Callers fall back to secrets.env.
|
|
160
|
+
*/
|
|
161
|
+
export function readBearer() {
|
|
162
|
+
try {
|
|
163
|
+
const entry = new EntryCtor(SERVICE, BEARER_ACCOUNT);
|
|
164
|
+
return entry.getPassword();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const SYNC_TOKEN_ACCOUNT = 'sync-device-token';
|
|
171
|
+
/** Store the cloud sync device token (ADR-003 auth) in the credential store. */
|
|
172
|
+
export function storeSyncToken(token) {
|
|
173
|
+
try {
|
|
174
|
+
const entry = new EntryCtor(SERVICE, SYNC_TOKEN_ACCOUNT);
|
|
175
|
+
entry.setPassword(token);
|
|
176
|
+
return { stored: true, source: 'credential-store' };
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.warn(`[astramem-local] WARNING: OS credential store unavailable (${errMessage(err)}); ` +
|
|
180
|
+
`sync device token was NOT stored. Set ASTRA_SYNC_TOKEN in the environment instead.`);
|
|
181
|
+
return { stored: false, source: 'secrets-env-fallback' };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Read the cloud sync device token; null when absent/unavailable. */
|
|
185
|
+
export function readSyncToken() {
|
|
186
|
+
try {
|
|
187
|
+
const entry = new EntryCtor(SERVICE, SYNC_TOKEN_ACCOUNT);
|
|
188
|
+
return entry.getPassword();
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/storage/keystore.ts"],"names":[],"mappings":"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"}
|