@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,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped projection-recompute (ADR-011 multi-device conflict policy,
|
|
3
|
+
* FEAT-407 pull merge slice).
|
|
4
|
+
*
|
|
5
|
+
* Only atom_ids that received a NEW remote event during a pull round need
|
|
6
|
+
* their projection recomputed — atoms with no incoming remote event keep
|
|
7
|
+
* the existing single-device fast path in memory-events.ts, unchanged
|
|
8
|
+
* (ADR-011 "Implications for FEAT-407" point 3). This module is that
|
|
9
|
+
* recompute function: a deterministic, order-independent reduce over the
|
|
10
|
+
* FULL local event set for one atom_id, implementing conflict classes
|
|
11
|
+
* A (concurrent-edit), B (edit-vs-supersede), C (edit-vs-erase).
|
|
12
|
+
*
|
|
13
|
+
* Deterministic tie-break key everywhere below: (created_at, event_id) —
|
|
14
|
+
* created_at first, event_id (immutable, globally-unique) breaks ties. No
|
|
15
|
+
* device is structurally favored (ADR-011).
|
|
16
|
+
*/
|
|
17
|
+
import type { DB } from '../storage/db.js';
|
|
18
|
+
/**
|
|
19
|
+
* Recompute the materialized `memories` projection for one atom_id from its
|
|
20
|
+
* full local event set (local ∪ pulled-remote, already deduped by
|
|
21
|
+
* event_id). Order-independent: computed as a set-wise reduce, not a
|
|
22
|
+
* fold-in-arrival-order apply, so calling this any number of times, in any
|
|
23
|
+
* order relative to sibling atoms, converges to the same result (ADR-011
|
|
24
|
+
* convergence property).
|
|
25
|
+
*/
|
|
26
|
+
export declare function recomputeProjection(db: DB, atomId: string): void;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped projection-recompute (ADR-011 multi-device conflict policy,
|
|
3
|
+
* FEAT-407 pull merge slice).
|
|
4
|
+
*
|
|
5
|
+
* Only atom_ids that received a NEW remote event during a pull round need
|
|
6
|
+
* their projection recomputed — atoms with no incoming remote event keep
|
|
7
|
+
* the existing single-device fast path in memory-events.ts, unchanged
|
|
8
|
+
* (ADR-011 "Implications for FEAT-407" point 3). This module is that
|
|
9
|
+
* recompute function: a deterministic, order-independent reduce over the
|
|
10
|
+
* FULL local event set for one atom_id, implementing conflict classes
|
|
11
|
+
* A (concurrent-edit), B (edit-vs-supersede), C (edit-vs-erase).
|
|
12
|
+
*
|
|
13
|
+
* Deterministic tie-break key everywhere below: (created_at, event_id) —
|
|
14
|
+
* created_at first, event_id (immutable, globally-unique) breaks ties. No
|
|
15
|
+
* device is structurally favored (ADR-011).
|
|
16
|
+
*/
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
function loadEvents(db, atomId) {
|
|
19
|
+
return db
|
|
20
|
+
.prepare('SELECT * FROM memory_events WHERE atom_id = ? ORDER BY created_at ASC, event_id ASC')
|
|
21
|
+
.all(atomId);
|
|
22
|
+
}
|
|
23
|
+
function rowExists(db, atomId) {
|
|
24
|
+
return db.prepare('SELECT 1 FROM memories WHERE id = ?').get(atomId) !== undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Rule C materialization: hard-delete the memories row + its vec row, mirroring MemoryEventRepo.erase()'s row-side effect. */
|
|
27
|
+
function hardDeleteAtom(db, atomId) {
|
|
28
|
+
const row = db.prepare('SELECT rowid FROM memories WHERE id = ?').get(atomId);
|
|
29
|
+
if (!row)
|
|
30
|
+
return false;
|
|
31
|
+
db.prepare('DELETE FROM memories_vec WHERE rowid = ?').run(BigInt(row.rowid));
|
|
32
|
+
db.prepare('DELETE FROM memories WHERE id = ?').run(atomId); // FTS trigger fires
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function supersedeTarget(e) {
|
|
36
|
+
const payload = e.payload_json !== null ? JSON.parse(e.payload_json) : {};
|
|
37
|
+
const target = payload.superseded_by;
|
|
38
|
+
if (!target)
|
|
39
|
+
throw new Error(`supersede event ${e.event_id} on atom ${e.atom_id} is missing payload.superseded_by`);
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
42
|
+
/** max by (created_at, event_id) — the ADR-011 canonical tie-break. */
|
|
43
|
+
function maxByTieBreak(events) {
|
|
44
|
+
return events.reduce((best, e) => {
|
|
45
|
+
if (e.created_at > best.created_at)
|
|
46
|
+
return e;
|
|
47
|
+
if (e.created_at < best.created_at)
|
|
48
|
+
return best;
|
|
49
|
+
return e.event_id > best.event_id ? e : best;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Class A fork resolution: append a synthetic supersede event chaining the
|
|
54
|
+
* losing successor atom to the winning one, idempotent on replay (checks
|
|
55
|
+
* for an existing conflict_merge event with the same target before
|
|
56
|
+
* inserting). Reuses the exact event_type/payload shape the ordinary
|
|
57
|
+
* supersede() path uses (ADR-011: "using the exact same event type and
|
|
58
|
+
* materialization path memory-events.ts already has").
|
|
59
|
+
*/
|
|
60
|
+
function chainLoserToWinner(db, loserAtomId, winnerAtomId, at) {
|
|
61
|
+
const already = db
|
|
62
|
+
.prepare(`SELECT 1 FROM memory_events
|
|
63
|
+
WHERE atom_id = ? AND event_type = 'supersede'
|
|
64
|
+
AND json_extract(payload_json, '$.superseded_by') = ?
|
|
65
|
+
AND json_extract(payload_json, '$.reason') = 'conflict_merge'`)
|
|
66
|
+
.get(loserAtomId, winnerAtomId);
|
|
67
|
+
if (!already) {
|
|
68
|
+
db.prepare(`INSERT INTO memory_events (event_id, event_type, atom_id, payload_json, content_hash, created_at)
|
|
69
|
+
VALUES (?, 'supersede', ?, ?, NULL, ?)`).run(randomUUID(), loserAtomId, JSON.stringify({ superseded_by: winnerAtomId, reason: 'conflict_merge' }), at);
|
|
70
|
+
}
|
|
71
|
+
// Recompute the loser's own projection (not just poke its row) so the
|
|
72
|
+
// loser's full rule set (e.g. its own concurrent erase_request, Rule C)
|
|
73
|
+
// still takes precedence over this synthetic chaining — recomputeProjection
|
|
74
|
+
// is idempotent and safe to call from within an already-open transaction
|
|
75
|
+
// (better-sqlite3 nests via SAVEPOINT).
|
|
76
|
+
recomputeProjection(db, loserAtomId);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Recompute the materialized `memories` projection for one atom_id from its
|
|
80
|
+
* full local event set (local ∪ pulled-remote, already deduped by
|
|
81
|
+
* event_id). Order-independent: computed as a set-wise reduce, not a
|
|
82
|
+
* fold-in-arrival-order apply, so calling this any number of times, in any
|
|
83
|
+
* order relative to sibling atoms, converges to the same result (ADR-011
|
|
84
|
+
* convergence property).
|
|
85
|
+
*/
|
|
86
|
+
export function recomputeProjection(db, atomId) {
|
|
87
|
+
const tx = db.transaction(() => {
|
|
88
|
+
const events = loadEvents(db, atomId);
|
|
89
|
+
// Rule C: erasure always wins, unconditionally, regardless of any
|
|
90
|
+
// concurrent invalidate/supersede on the same atom_id.
|
|
91
|
+
if (events.some(e => e.event_type === 'erase_request')) {
|
|
92
|
+
hardDeleteAtom(db, atomId);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!rowExists(db, atomId)) {
|
|
96
|
+
// Row already gone (erased earlier by this device, or never
|
|
97
|
+
// materialized) — nothing left to project onto.
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const supersedes = events.filter(e => e.event_type === 'supersede');
|
|
101
|
+
const invalidates = events.filter(e => e.event_type === 'invalidate');
|
|
102
|
+
if (supersedes.length === 0 && invalidates.length === 0) {
|
|
103
|
+
// Only create/promote_scope/usefulness events, or archive/restore —
|
|
104
|
+
// none need reconciliation here. archive/restore are intentionally
|
|
105
|
+
// NOT synced (shipper.ts listUnsynced excludes them: archival is a
|
|
106
|
+
// per-device retention decision, not a semantic memory change), so
|
|
107
|
+
// recomputeProjection — which only ever runs on atoms that received a
|
|
108
|
+
// NEW REMOTE event during a pull round — never sees them.
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
if (supersedes.length > 0) {
|
|
113
|
+
// Rule B (supersede beats invalidate) composed with Rule A (fork
|
|
114
|
+
// tie-break): resolve the fork first, then supersede wins regardless
|
|
115
|
+
// of any concurrent invalidate — so invalidates are simply not
|
|
116
|
+
// consulted once any supersede event exists.
|
|
117
|
+
const winner = maxByTieBreak(supersedes);
|
|
118
|
+
const winnerTarget = supersedeTarget(winner);
|
|
119
|
+
const validTo = Math.min(...supersedes.map(e => e.created_at));
|
|
120
|
+
db.prepare('UPDATE memories SET valid_to = ?, superseded_by = ?, updated_at = ? WHERE id = ?')
|
|
121
|
+
.run(validTo, winnerTarget, now, atomId);
|
|
122
|
+
for (const loser of supersedes) {
|
|
123
|
+
if (loser === winner)
|
|
124
|
+
continue;
|
|
125
|
+
const loserTarget = supersedeTarget(loser);
|
|
126
|
+
if (loserTarget === winnerTarget)
|
|
127
|
+
continue; // two devices happened to pick the same successor — nothing to chain
|
|
128
|
+
chainLoserToWinner(db, loserTarget, winnerTarget, Math.max(loser.created_at, now));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Rule A, invalidate-vs-invalidate: order-independent, first-invalidated-wins.
|
|
133
|
+
const validTo = Math.min(...invalidates.map(e => e.created_at));
|
|
134
|
+
db.prepare('UPDATE memories SET valid_to = ?, updated_at = ? WHERE id = ?').run(validTo, now, atomId);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
tx();
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=conflict-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-resolve.js","sourceRoot":"","sources":["../../src/sync/conflict-resolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync puller (FEAT-407, ADR-011) — cloud/remote-device -> local merge.
|
|
3
|
+
* The mirror image of shipper.ts: shipper is local -> cloud (push, ADR-003,
|
|
4
|
+
* one-way); this module is cloud -> local (pull, additive, no push-side
|
|
5
|
+
* changes — ADR-011 point (e): "synthetic conflict events ride the existing
|
|
6
|
+
* shipper", no new push-side code).
|
|
7
|
+
*
|
|
8
|
+
* Merge shape (ADR-011 "Implications for FEAT-407"):
|
|
9
|
+
* 1. dedup-insert pulled events by event_id (AC-2 idempotent replay),
|
|
10
|
+
* 2. scoped projection-recompute (conflict-resolve.ts) for exactly the
|
|
11
|
+
* atom_ids that received a NEW event this round,
|
|
12
|
+
* 3. persist the watermark/cursor in the SAME transaction as (1)+(2), so
|
|
13
|
+
* a crash mid-pull cannot advance the watermark past events that were
|
|
14
|
+
* not actually durably merged (AC-4: resumable, no gap, no re-pull
|
|
15
|
+
* from zero).
|
|
16
|
+
*
|
|
17
|
+
* WIRE CONTRACT CAVEAT (be conservative — no live SaaS repo to consult):
|
|
18
|
+
* astramem-sync@1 (ADR-003) ships `memory_events` metadata ONLY — no atom
|
|
19
|
+
* body (text/type/hash/scope). That is sufficient for push (the cloud
|
|
20
|
+
* ledger materializes state from events it already trusts) but a `create`
|
|
21
|
+
* event pulled onto a device that has never seen this atom_id has nothing
|
|
22
|
+
* to materialize a `memories` row FROM. Real cross-service pull will need a
|
|
23
|
+
* cloud-side answer to "where does the atom body come from" that this repo
|
|
24
|
+
* does not currently define (ADR-003 explicitly scoped pull out: "NOT in
|
|
25
|
+
* v1: bidirectional sync, cross-device pull"). Rather than block on that,
|
|
26
|
+
* this module defines `atom_snapshot` as an OPTIONAL, additive field on the
|
|
27
|
+
* pulled event carrying just enough of the atom body to materialize a new
|
|
28
|
+
* row locally. This is a LOCAL, FEAT-407-side design choice, not a verified
|
|
29
|
+
* live cloud contract — flagged explicitly in the FEAT-407 completion
|
|
30
|
+
* report. `PullFetcher` is injectable specifically so tests can simulate
|
|
31
|
+
* this without a real cloud.
|
|
32
|
+
*/
|
|
33
|
+
import type { DB } from '../storage/db.js';
|
|
34
|
+
import type { MemoryScope, MemoryType } from '../contracts/memory.js';
|
|
35
|
+
import type { WireSyncEvent } from './shipper.js';
|
|
36
|
+
/** Minimal atom body needed to materialize a brand-new `memories` row on pull. See file header caveat. */
|
|
37
|
+
export interface AtomSnapshot {
|
|
38
|
+
type: MemoryType;
|
|
39
|
+
text: string;
|
|
40
|
+
normalized_text: string;
|
|
41
|
+
hash: string;
|
|
42
|
+
scope: MemoryScope;
|
|
43
|
+
repo?: string | null;
|
|
44
|
+
project?: string | null;
|
|
45
|
+
branch?: string | null;
|
|
46
|
+
agent?: string | null;
|
|
47
|
+
session_id?: string | null;
|
|
48
|
+
importance?: number;
|
|
49
|
+
confidence?: number;
|
|
50
|
+
source_hash?: string | null;
|
|
51
|
+
evidence?: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface RemotePullEvent extends WireSyncEvent {
|
|
54
|
+
/** Required on pull (unlike push, where it's additive-optional) — it is the dedup key merge correctness depends on. */
|
|
55
|
+
event_id: string;
|
|
56
|
+
/** Present only for 'create' events the sender knows the puller may not have seen yet. See file header caveat. */
|
|
57
|
+
atom_snapshot?: AtomSnapshot | null;
|
|
58
|
+
}
|
|
59
|
+
export interface PullBatch {
|
|
60
|
+
events: RemotePullEvent[];
|
|
61
|
+
/** Opaque resume token. null means "no further pages" (nothing beyond this batch). */
|
|
62
|
+
next_cursor: string | null;
|
|
63
|
+
}
|
|
64
|
+
/** Injectable — the real implementation talks to the cloud; tests supply an in-memory fake event source. */
|
|
65
|
+
export type PullFetcher = (cursor: string | null, limit: number) => Promise<PullBatch>;
|
|
66
|
+
export interface PullOpts {
|
|
67
|
+
db: DB;
|
|
68
|
+
fetcher: PullFetcher;
|
|
69
|
+
batchSize?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface PullOnceResult {
|
|
72
|
+
/** Events returned by the fetcher this round (before dedup). */
|
|
73
|
+
fetched: number;
|
|
74
|
+
/** Events actually newly inserted (post dedup-by-event_id). */
|
|
75
|
+
inserted: number;
|
|
76
|
+
/** Distinct atom_ids whose projection was recomputed this round. */
|
|
77
|
+
atomsRecomputed: number;
|
|
78
|
+
/** Cursor persisted at the end of this round. */
|
|
79
|
+
cursor: string | null;
|
|
80
|
+
/** true when the fetcher returned nothing to pull. */
|
|
81
|
+
idle: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** Current persisted pull watermark, or null if pull has never run. */
|
|
84
|
+
export declare function getPullWatermark(db: DB): string | null;
|
|
85
|
+
/**
|
|
86
|
+
* One pull round: fetch a batch from `cursor`, dedup-insert by event_id,
|
|
87
|
+
* materialize any brand-new atoms, scoped-recompute touched atoms'
|
|
88
|
+
* projections, and persist the new watermark — all durably: the fetch
|
|
89
|
+
* itself is outside any transaction (network I/O can't hold a DB lock
|
|
90
|
+
* across a hung socket), but the merge + watermark advance is one atomic
|
|
91
|
+
* transaction (AC-4: a crash between fetch and merge simply re-fetches the
|
|
92
|
+
* same batch next round, which is safe because merge is idempotent).
|
|
93
|
+
*/
|
|
94
|
+
export declare function pullOnce(opts: PullOpts): Promise<PullOnceResult>;
|
|
95
|
+
export interface DrainPullResult {
|
|
96
|
+
rounds: number;
|
|
97
|
+
totalFetched: number;
|
|
98
|
+
totalInserted: number;
|
|
99
|
+
cursor: string | null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Drain every available page: calls pullOnce repeatedly until the fetcher
|
|
103
|
+
* reports idle (or a `next_cursor` of null after a non-empty batch). Bounds
|
|
104
|
+
* the number of rounds so a misbehaving fetcher can't loop forever.
|
|
105
|
+
*/
|
|
106
|
+
export declare function drainPull(opts: PullOpts, maxRounds?: number): Promise<DrainPullResult>;
|
|
107
|
+
/**
|
|
108
|
+
* Real HTTP pull fetcher: GET <url>/sync/pull?cursor=&limit=. NOT exercised
|
|
109
|
+
* against a live cloud in this slice's tests (no SaaS repo available
|
|
110
|
+
* locally) — see the completion report's "requires-live-cloud" section.
|
|
111
|
+
* Response shape assumed to mirror SyncEnvelope's event shape plus
|
|
112
|
+
* `next_cursor`; this is this daemon's expectation of the pull contract,
|
|
113
|
+
* not a verified cloud implementation.
|
|
114
|
+
*/
|
|
115
|
+
export declare function httpPullFetcher(url: string, token: string, fetchImpl?: typeof fetch): PullFetcher;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync puller (FEAT-407, ADR-011) — cloud/remote-device -> local merge.
|
|
3
|
+
* The mirror image of shipper.ts: shipper is local -> cloud (push, ADR-003,
|
|
4
|
+
* one-way); this module is cloud -> local (pull, additive, no push-side
|
|
5
|
+
* changes — ADR-011 point (e): "synthetic conflict events ride the existing
|
|
6
|
+
* shipper", no new push-side code).
|
|
7
|
+
*
|
|
8
|
+
* Merge shape (ADR-011 "Implications for FEAT-407"):
|
|
9
|
+
* 1. dedup-insert pulled events by event_id (AC-2 idempotent replay),
|
|
10
|
+
* 2. scoped projection-recompute (conflict-resolve.ts) for exactly the
|
|
11
|
+
* atom_ids that received a NEW event this round,
|
|
12
|
+
* 3. persist the watermark/cursor in the SAME transaction as (1)+(2), so
|
|
13
|
+
* a crash mid-pull cannot advance the watermark past events that were
|
|
14
|
+
* not actually durably merged (AC-4: resumable, no gap, no re-pull
|
|
15
|
+
* from zero).
|
|
16
|
+
*
|
|
17
|
+
* WIRE CONTRACT CAVEAT (be conservative — no live SaaS repo to consult):
|
|
18
|
+
* astramem-sync@1 (ADR-003) ships `memory_events` metadata ONLY — no atom
|
|
19
|
+
* body (text/type/hash/scope). That is sufficient for push (the cloud
|
|
20
|
+
* ledger materializes state from events it already trusts) but a `create`
|
|
21
|
+
* event pulled onto a device that has never seen this atom_id has nothing
|
|
22
|
+
* to materialize a `memories` row FROM. Real cross-service pull will need a
|
|
23
|
+
* cloud-side answer to "where does the atom body come from" that this repo
|
|
24
|
+
* does not currently define (ADR-003 explicitly scoped pull out: "NOT in
|
|
25
|
+
* v1: bidirectional sync, cross-device pull"). Rather than block on that,
|
|
26
|
+
* this module defines `atom_snapshot` as an OPTIONAL, additive field on the
|
|
27
|
+
* pulled event carrying just enough of the atom body to materialize a new
|
|
28
|
+
* row locally. This is a LOCAL, FEAT-407-side design choice, not a verified
|
|
29
|
+
* live cloud contract — flagged explicitly in the FEAT-407 completion
|
|
30
|
+
* report. `PullFetcher` is injectable specifically so tests can simulate
|
|
31
|
+
* this without a real cloud.
|
|
32
|
+
*/
|
|
33
|
+
import { recomputeProjection } from './conflict-resolve.js';
|
|
34
|
+
const DEFAULT_PULL_BATCH = 200;
|
|
35
|
+
/** Current persisted pull watermark, or null if pull has never run. */
|
|
36
|
+
export function getPullWatermark(db) {
|
|
37
|
+
const row = db.prepare('SELECT cursor FROM sync_pull_state WHERE id = 1').get();
|
|
38
|
+
return row?.cursor ?? null;
|
|
39
|
+
}
|
|
40
|
+
function setPullWatermark(db, cursor, at) {
|
|
41
|
+
db.prepare(`INSERT INTO sync_pull_state (id, cursor, updated_at) VALUES (1, ?, ?)
|
|
42
|
+
ON CONFLICT(id) DO UPDATE SET cursor = excluded.cursor, updated_at = excluded.updated_at`).run(cursor, at);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Dedup-insert one pulled event: `INSERT ... WHERE NOT EXISTS` on event_id
|
|
46
|
+
* (AC-2). Returns true iff a new row was actually inserted — callers use
|
|
47
|
+
* this to build the scoped "touched atom_ids" set for recompute (ADR-011
|
|
48
|
+
* point 3: recompute is scoped, not global).
|
|
49
|
+
*/
|
|
50
|
+
function dedupInsertEvent(db, event) {
|
|
51
|
+
const payloadJson = event.payload_json !== null ? JSON.stringify(event.payload_json) : null;
|
|
52
|
+
const result = db
|
|
53
|
+
.prepare(`INSERT INTO memory_events (event_id, event_type, atom_id, payload_json, content_hash, created_at)
|
|
54
|
+
SELECT ?, ?, ?, ?, ?, ?
|
|
55
|
+
WHERE NOT EXISTS (SELECT 1 FROM memory_events WHERE event_id = ?)`)
|
|
56
|
+
.run(event.event_id, event.event_type, event.atom_id, payloadJson, event.content_hash, event.created_at, event.event_id);
|
|
57
|
+
return result.changes > 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Materialize a brand-new `memories` row for a pulled 'create' event whose
|
|
61
|
+
* atom_id this device has never seen, using the atom_id from the event as
|
|
62
|
+
* the row id (so later events referencing this atom_id line up). No-op
|
|
63
|
+
* (returns false) if the row already exists (idempotent) or no snapshot was
|
|
64
|
+
* provided (event predates atom_snapshot support, or the sender assumed the
|
|
65
|
+
* receiver already had it — nothing this module can materialize from).
|
|
66
|
+
*/
|
|
67
|
+
function materializeAtomIfMissing(db, atomId, snapshot, createdAt) {
|
|
68
|
+
const exists = db.prepare('SELECT 1 FROM memories WHERE id = ?').get(atomId);
|
|
69
|
+
if (exists)
|
|
70
|
+
return false;
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
db.prepare(`INSERT INTO memories
|
|
73
|
+
(id, type, text, normalized_text, repo, project, branch, agent, session_id,
|
|
74
|
+
importance, confidence, hash, created_at, updated_at, source_hash, evidence, valid_from, scope)
|
|
75
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(atomId, snapshot.type, snapshot.text, snapshot.normalized_text, snapshot.repo ?? null, snapshot.project ?? null, snapshot.branch ?? null, snapshot.agent ?? null, snapshot.session_id ?? null, snapshot.importance ?? 0.5, snapshot.confidence ?? 0.5, snapshot.hash, createdAt, now, snapshot.source_hash ?? null, snapshot.evidence ?? null, createdAt, snapshot.scope);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One pull round: fetch a batch from `cursor`, dedup-insert by event_id,
|
|
80
|
+
* materialize any brand-new atoms, scoped-recompute touched atoms'
|
|
81
|
+
* projections, and persist the new watermark — all durably: the fetch
|
|
82
|
+
* itself is outside any transaction (network I/O can't hold a DB lock
|
|
83
|
+
* across a hung socket), but the merge + watermark advance is one atomic
|
|
84
|
+
* transaction (AC-4: a crash between fetch and merge simply re-fetches the
|
|
85
|
+
* same batch next round, which is safe because merge is idempotent).
|
|
86
|
+
*/
|
|
87
|
+
export async function pullOnce(opts) {
|
|
88
|
+
const batchSize = opts.batchSize ?? DEFAULT_PULL_BATCH;
|
|
89
|
+
const watermark = getPullWatermark(opts.db);
|
|
90
|
+
const batch = await opts.fetcher(watermark, batchSize);
|
|
91
|
+
if (batch.events.length === 0) {
|
|
92
|
+
return { fetched: 0, inserted: 0, atomsRecomputed: 0, cursor: watermark, idle: true };
|
|
93
|
+
}
|
|
94
|
+
let inserted = 0;
|
|
95
|
+
const touchedAtomIds = new Set();
|
|
96
|
+
const tx = opts.db.transaction(() => {
|
|
97
|
+
for (const event of batch.events) {
|
|
98
|
+
const wasInserted = dedupInsertEvent(opts.db, event);
|
|
99
|
+
if (!wasInserted)
|
|
100
|
+
continue;
|
|
101
|
+
inserted++;
|
|
102
|
+
touchedAtomIds.add(event.atom_id);
|
|
103
|
+
if (event.event_type === 'create' && event.atom_snapshot) {
|
|
104
|
+
materializeAtomIfMissing(opts.db, event.atom_id, event.atom_snapshot, event.created_at);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const atomId of touchedAtomIds) {
|
|
108
|
+
recomputeProjection(opts.db, atomId);
|
|
109
|
+
}
|
|
110
|
+
setPullWatermark(opts.db, batch.next_cursor, Date.now());
|
|
111
|
+
});
|
|
112
|
+
tx();
|
|
113
|
+
return {
|
|
114
|
+
fetched: batch.events.length,
|
|
115
|
+
inserted,
|
|
116
|
+
atomsRecomputed: touchedAtomIds.size,
|
|
117
|
+
cursor: batch.next_cursor,
|
|
118
|
+
idle: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Drain every available page: calls pullOnce repeatedly until the fetcher
|
|
123
|
+
* reports idle (or a `next_cursor` of null after a non-empty batch). Bounds
|
|
124
|
+
* the number of rounds so a misbehaving fetcher can't loop forever.
|
|
125
|
+
*/
|
|
126
|
+
export async function drainPull(opts, maxRounds = 1000) {
|
|
127
|
+
let rounds = 0;
|
|
128
|
+
let totalFetched = 0;
|
|
129
|
+
let totalInserted = 0;
|
|
130
|
+
let cursor = getPullWatermark(opts.db);
|
|
131
|
+
while (rounds < maxRounds) {
|
|
132
|
+
const result = await pullOnce(opts);
|
|
133
|
+
rounds++;
|
|
134
|
+
totalFetched += result.fetched;
|
|
135
|
+
totalInserted += result.inserted;
|
|
136
|
+
cursor = result.cursor;
|
|
137
|
+
if (result.idle || result.cursor === null)
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
return { rounds, totalFetched, totalInserted, cursor };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Real HTTP pull fetcher: GET <url>/sync/pull?cursor=&limit=. NOT exercised
|
|
144
|
+
* against a live cloud in this slice's tests (no SaaS repo available
|
|
145
|
+
* locally) — see the completion report's "requires-live-cloud" section.
|
|
146
|
+
* Response shape assumed to mirror SyncEnvelope's event shape plus
|
|
147
|
+
* `next_cursor`; this is this daemon's expectation of the pull contract,
|
|
148
|
+
* not a verified cloud implementation.
|
|
149
|
+
*/
|
|
150
|
+
export function httpPullFetcher(url, token, fetchImpl = fetch) {
|
|
151
|
+
return async (cursor, limit) => {
|
|
152
|
+
const qs = new URLSearchParams({ limit: String(limit) });
|
|
153
|
+
if (cursor !== null)
|
|
154
|
+
qs.set('cursor', cursor);
|
|
155
|
+
const res = await fetchImpl(`${url.replace(/\/$/, '')}/sync/pull?${qs.toString()}`, {
|
|
156
|
+
method: 'GET',
|
|
157
|
+
headers: { authorization: `Bearer ${token}` },
|
|
158
|
+
signal: AbortSignal.timeout(10_000),
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
throw new Error(`sync pull failed: HTTP ${res.status}`);
|
|
162
|
+
}
|
|
163
|
+
const body = (await res.json());
|
|
164
|
+
if (!Array.isArray(body.events)) {
|
|
165
|
+
throw new Error('sync pull failed: response missing events[]');
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
events: body.events,
|
|
169
|
+
next_cursor: typeof body.next_cursor === 'string' ? body.next_cursor : null,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=puller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"puller.js","sourceRoot":"","sources":["../../src/sync/puller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync shipper (Wave 3d, ADR-003) — one-way, append-only, idempotent log
|
|
3
|
+
* shipping: local memory_events -> cloud POST /sync/events.
|
|
4
|
+
*
|
|
5
|
+
* The log IS the queue: unsynced rows are `synced_at IS NULL`; a batch is
|
|
6
|
+
* acked by the cloud's `{ acked_seq }` and marked in one UPDATE. Crash-safe
|
|
7
|
+
* by construction — replaying a batch is always safe (cloud dedups on
|
|
8
|
+
* content_hash and (device_id, seq)).
|
|
9
|
+
*
|
|
10
|
+
* Scope filter (ADR-009): only events whose atom is team/org-scoped ship.
|
|
11
|
+
* `personal` atoms never leave the machine. erase_request events for atoms
|
|
12
|
+
* whose row is already hard-deleted ship when their payload carries a
|
|
13
|
+
* team/org scope (3f writes the scope into the erase payload for exactly
|
|
14
|
+
* this reason).
|
|
15
|
+
*
|
|
16
|
+
* Offline: unlimited — failures leave rows unsynced; retry with exponential
|
|
17
|
+
* backoff capped at BACKOFF_CAP_MS.
|
|
18
|
+
*/
|
|
19
|
+
import type { DB } from '../storage/db.js';
|
|
20
|
+
export declare const SYNC_PROTOCOL: "astramem-sync@1";
|
|
21
|
+
export interface WireSyncEvent {
|
|
22
|
+
seq: number;
|
|
23
|
+
event_type: string;
|
|
24
|
+
atom_id: string;
|
|
25
|
+
payload_json: Record<string, unknown> | null;
|
|
26
|
+
content_hash: string | null;
|
|
27
|
+
created_at: number;
|
|
28
|
+
/**
|
|
29
|
+
* Globally-unique event identity (ADR-011, migration 010). Additive field
|
|
30
|
+
* — no astramem-sync@1 version bump (ADR-001's additive-field evolution
|
|
31
|
+
* rule): older push clients that omit it are still valid envelopes; this
|
|
32
|
+
* daemon always populates it (migration 010 backfills every pre-existing
|
|
33
|
+
* row, and MemoryEventRepo.append() always assigns one going forward).
|
|
34
|
+
* It is the pull-side dedup key (FEAT-407 AC-2) and the ADR-011 conflict
|
|
35
|
+
* tie-break key (created_at, event_id).
|
|
36
|
+
*/
|
|
37
|
+
event_id: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SyncEnvelope {
|
|
40
|
+
protocol: typeof SYNC_PROTOCOL;
|
|
41
|
+
device_id: string;
|
|
42
|
+
workspace_id: string;
|
|
43
|
+
cursor: number;
|
|
44
|
+
events: WireSyncEvent[];
|
|
45
|
+
}
|
|
46
|
+
export interface ShipperOpts {
|
|
47
|
+
db: DB;
|
|
48
|
+
/** Cloud base URL, e.g. https://memory.example.com — POSTs to <url>/sync/events. */
|
|
49
|
+
url: string;
|
|
50
|
+
workspaceId: string;
|
|
51
|
+
deviceId: string;
|
|
52
|
+
/** Bearer for the cloud (device token). */
|
|
53
|
+
token: string;
|
|
54
|
+
batchSize?: number;
|
|
55
|
+
intervalMs?: number;
|
|
56
|
+
/** Injectable fetch for tests. */
|
|
57
|
+
fetchImpl?: typeof fetch;
|
|
58
|
+
}
|
|
59
|
+
export interface ShipperHandle {
|
|
60
|
+
/** Stop the loop. Resolves after any in-flight ship completes. */
|
|
61
|
+
stop(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Stable per-install device id, persisted as a plain file in the config dir
|
|
65
|
+
* (an identifier, not a secret — the device TOKEN lives in the keystore).
|
|
66
|
+
*/
|
|
67
|
+
export declare function getOrCreateDeviceId(configDir: string): string;
|
|
68
|
+
interface UnsyncedRow {
|
|
69
|
+
seq: number;
|
|
70
|
+
event_id: string;
|
|
71
|
+
event_type: string;
|
|
72
|
+
atom_id: string;
|
|
73
|
+
payload_json: string | null;
|
|
74
|
+
content_hash: string | null;
|
|
75
|
+
created_at: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Unsynced, ship-eligible events in seq order (ADR-009 scope filter).
|
|
79
|
+
* LEFT JOIN: an erase_request may outlive its memories row — it ships when
|
|
80
|
+
* its payload records a team/org scope.
|
|
81
|
+
*
|
|
82
|
+
* `archive`/`restore` (FEAT-404) are excluded unconditionally, regardless of
|
|
83
|
+
* scope: archival is a per-device retention/storage-hygiene decision (one
|
|
84
|
+
* device's local SQLite-growth mitigation), not a semantic memory change,
|
|
85
|
+
* so it must never leave the machine. The sync wire schema
|
|
86
|
+
* (contracts/schemas/sync-envelope.v1.schema.json) also never lists these
|
|
87
|
+
* two types in its event_type enum — shipping one would fail schema
|
|
88
|
+
* validation on the wire, on top of being the wrong policy.
|
|
89
|
+
*/
|
|
90
|
+
export declare function listUnsynced(db: DB, batchSize: number): UnsyncedRow[];
|
|
91
|
+
/** Last acked seq = highest synced event seq (0 when nothing has shipped). */
|
|
92
|
+
export declare function lastAckedSeq(db: DB): number;
|
|
93
|
+
export declare function buildEnvelope(deviceId: string, workspaceId: string, cursor: number, rows: UnsyncedRow[]): SyncEnvelope;
|
|
94
|
+
export interface ShipOnceResult {
|
|
95
|
+
shipped: number;
|
|
96
|
+
acked: number;
|
|
97
|
+
/** true when there was nothing eligible to ship. */
|
|
98
|
+
idle: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* One shipping round: read a batch, POST it, mark acked rows synced.
|
|
102
|
+
* Throws on transport/HTTP failure — the caller owns retry/backoff.
|
|
103
|
+
*/
|
|
104
|
+
export declare function shipOnce(opts: ShipperOpts): Promise<ShipOnceResult>;
|
|
105
|
+
/** Pure backoff schedule: base * 2^failures, capped. Exported for tests. */
|
|
106
|
+
export declare function backoffDelay(baseMs: number, consecutiveFailures: number): number;
|
|
107
|
+
/**
|
|
108
|
+
* Start the periodic shipper loop. Ships immediately, then every intervalMs;
|
|
109
|
+
* on failure the next attempt is delayed by exponential backoff.
|
|
110
|
+
*/
|
|
111
|
+
export declare function startShipper(opts: ShipperOpts): ShipperHandle;
|
|
112
|
+
export {};
|