@remnic/core 9.54.4 → 9.54.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +12 -11
- package/dist/access-admin-ops-surface.js +46 -44
- package/dist/access-authorization-probe.d.ts +12 -11
- package/dist/access-authorization-probe.js +47 -45
- package/dist/access-boundary.d.ts +12 -11
- package/dist/access-boundary.js +46 -44
- package/dist/access-cli.js +92 -90
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +12 -11
- package/dist/access-extraction-force-flush.js +46 -44
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +12 -11
- package/dist/access-http-lifecycle-flush.d.ts +12 -11
- package/dist/access-http-offline-stream.d.ts +12 -11
- package/dist/access-http-query.js +46 -44
- package/dist/access-http.d.ts +12 -11
- package/dist/access-http.js +56 -54
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +46 -44
- package/dist/access-lcm-surface.d.ts +12 -11
- package/dist/access-lcm-surface.js +46 -44
- package/dist/access-mcp.d.ts +12 -11
- package/dist/access-mcp.js +52 -50
- package/dist/access-memory-search-fanout.d.ts +22 -2
- package/dist/access-memory-search-fanout.js +1 -1
- package/dist/{access-namespace-preflight-C7n4tgWC.d.ts → access-namespace-preflight-BdHlGd_T.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +46 -44
- package/dist/access-observe-write-surface.d.ts +12 -11
- package/dist/access-observe-write-surface.js +46 -44
- package/dist/access-offline-manifest.d.ts +10 -9
- package/dist/access-offline-manifest.js +12 -10
- package/dist/access-operations-batch.js +48 -46
- package/dist/access-operations.d.ts +15 -14
- package/dist/access-operations.js +51 -49
- package/dist/access-recall-concurrency.d.ts +12 -11
- package/dist/access-recall-concurrency.js +46 -44
- package/dist/access-recall-response.d.ts +12 -11
- package/dist/access-recall-response.js +46 -44
- package/dist/access-recall-surface.d.ts +12 -11
- package/dist/access-recall-surface.js +46 -44
- package/dist/access-schema.d.ts +90 -90
- package/dist/access-schema.js +3 -2
- package/dist/{access-service-CXvbxLK-.d.ts → access-service-DL7X9hrg.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +12 -11
- package/dist/access-service.d.ts +12 -11
- package/dist/access-service.js +46 -44
- package/dist/access-surface-catalog.d.ts +12 -11
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +13 -1
- package/dist/active-memory-bridge.js +6 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -9
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +30 -9
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +3 -3
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-BOOxl-I2.d.ts → catalog-B90is4d8.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +31 -10
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-6LAPLZOS.js → chunk-2DD6ZHOM.js} +8 -3
- package/dist/chunk-2DD6ZHOM.js.map +1 -0
- package/dist/{chunk-47P3RYZP.js → chunk-2INWPC6E.js} +2 -2
- package/dist/{chunk-3CSX3ETE.js → chunk-2LWQSV6A.js} +23 -11
- package/dist/chunk-2LWQSV6A.js.map +1 -0
- package/dist/{chunk-7BZOBA5J.js → chunk-2PNXOQ4P.js} +69 -11
- package/dist/chunk-2PNXOQ4P.js.map +1 -0
- package/dist/{chunk-WOPA3SGI.js → chunk-3OTSVCPK.js} +8 -8
- package/dist/{chunk-PX2LSHYM.js → chunk-3SXLD5T3.js} +3 -3
- package/dist/{chunk-T2JHZWH6.js → chunk-3WGI7F4V.js} +237 -93
- package/dist/chunk-3WGI7F4V.js.map +1 -0
- package/dist/{chunk-C4ZNGVK3.js → chunk-5JKUSBMX.js} +2 -2
- package/dist/{chunk-S75M5ZRK.js → chunk-6LEIWWAF.js} +16 -1
- package/dist/chunk-6LEIWWAF.js.map +1 -0
- package/dist/{chunk-MAXVH44E.js → chunk-6NNNURLK.js} +4 -4
- package/dist/{chunk-LJS7KUPV.js → chunk-6PZGKGAO.js} +2 -2
- package/dist/{chunk-YN6ASZUG.js → chunk-6QPI2ITJ.js} +5 -5
- package/dist/{chunk-CYF6WVE7.js → chunk-7HG7MGAP.js} +2688 -496
- package/dist/chunk-7HG7MGAP.js.map +1 -0
- package/dist/{chunk-CGVHPCP2.js → chunk-7Q7JWLQ4.js} +2 -2
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
- package/dist/{chunk-PDDTI7IC.js → chunk-B4T3FQMT.js} +1 -1
- package/dist/{chunk-UU3G6GUP.js → chunk-BIF4SHMD.js} +1 -1
- package/dist/{chunk-UU3G6GUP.js.map → chunk-BIF4SHMD.js.map} +1 -1
- package/dist/{chunk-APPFNGX6.js → chunk-BSXGTDVJ.js} +2 -2
- package/dist/{chunk-4IIFL7GC.js → chunk-CLKBV3Z3.js} +10 -10
- package/dist/{chunk-ZYLC7NFO.js → chunk-CSY44OKU.js} +4 -4
- package/dist/{chunk-HLYR2TQL.js → chunk-CUTFSLSD.js} +2 -2
- package/dist/{chunk-RDJRFDJY.js → chunk-DBY26JJJ.js} +2 -2
- package/dist/{chunk-QWGE2PUH.js → chunk-DDA55IVB.js} +10 -10
- package/dist/{chunk-VMMQDEN5.js → chunk-DN2IDKI5.js} +2 -2
- package/dist/{chunk-2GFK22OZ.js → chunk-DOOWFDWN.js} +2 -2
- package/dist/{chunk-7FGL6CGA.js → chunk-DVYRD4HY.js} +2 -2
- package/dist/{chunk-YGLY6QFV.js → chunk-F2Y6I74C.js} +2 -2
- package/dist/{chunk-UU7TRFNE.js → chunk-FFEBSFVT.js} +2 -2
- package/dist/chunk-HNDIND7P.js +331 -0
- package/dist/chunk-HNDIND7P.js.map +1 -0
- package/dist/{chunk-NIS256NW.js → chunk-JKW663ZN.js} +2 -2
- package/dist/{chunk-G7A5HLUS.js → chunk-KJH6W2I7.js} +2 -2
- package/dist/{chunk-IXEBUYO4.js → chunk-KM55SMLG.js} +11 -2
- package/dist/{chunk-IXEBUYO4.js.map → chunk-KM55SMLG.js.map} +1 -1
- package/dist/{chunk-G2LUMAUF.js → chunk-L7RTUEDG.js} +12 -5
- package/dist/chunk-L7RTUEDG.js.map +1 -0
- package/dist/chunk-LC2BDILZ.js +57 -0
- package/dist/chunk-LC2BDILZ.js.map +1 -0
- package/dist/{chunk-JV5YUBIZ.js → chunk-LC3FD4BV.js} +52 -46
- package/dist/chunk-LC3FD4BV.js.map +1 -0
- package/dist/{chunk-HWRD23DW.js → chunk-LVKYLMSM.js} +3 -3
- package/dist/{chunk-RUMNY5DT.js → chunk-MRGJZ75M.js} +6 -2
- package/dist/chunk-MRGJZ75M.js.map +1 -0
- package/dist/{chunk-ZMYDYNI4.js → chunk-N7CXQ5TA.js} +4 -4
- package/dist/{chunk-3JLPGJ43.js → chunk-NGT35IGQ.js} +5 -5
- package/dist/{chunk-GUKBW4HL.js → chunk-NQMSEB5D.js} +6 -6
- package/dist/{chunk-WRJVB3C2.js → chunk-PO7NXBG2.js} +2 -2
- package/dist/{chunk-WM65LU67.js → chunk-QOQQMVR2.js} +2 -2
- package/dist/{chunk-QHFV6VNU.js → chunk-QPAQSG3F.js} +2 -2
- package/dist/{chunk-UTRO5UJU.js → chunk-QPW3PAIH.js} +5 -1
- package/dist/chunk-QPW3PAIH.js.map +1 -0
- package/dist/{chunk-6R4G7N4S.js → chunk-QQXWFDRD.js} +128 -74
- package/dist/chunk-QQXWFDRD.js.map +1 -0
- package/dist/{chunk-UES4BHA2.js → chunk-QTO6BMQZ.js} +2 -2
- package/dist/{chunk-THPPCISA.js → chunk-RBII7637.js} +5 -5
- package/dist/{chunk-QT2FRYI4.js → chunk-RG426DJO.js} +2 -2
- package/dist/{chunk-XUAMCLY4.js → chunk-TKFGFDPJ.js} +17 -13
- package/dist/chunk-TKFGFDPJ.js.map +1 -0
- package/dist/{chunk-VKXWK44Q.js → chunk-TY6MKIWP.js} +3 -3
- package/dist/{chunk-AZM4CCAC.js → chunk-UWJNKGBA.js} +5 -5
- package/dist/{chunk-JDZPNCMA.js → chunk-UYOXFFEY.js} +6 -3
- package/dist/chunk-UYOXFFEY.js.map +1 -0
- package/dist/{chunk-ZPJ4QSGP.js → chunk-VTU5IEKY.js} +2 -2
- package/dist/{chunk-RZ4XGKOJ.js → chunk-VYQ4URQB.js} +8 -8
- package/dist/{chunk-WDBX6VER.js → chunk-VZRFGJCH.js} +5 -5
- package/dist/{chunk-IBKR2TFR.js → chunk-WM5LD6AT.js} +2 -2
- package/dist/{chunk-K4QS7LXU.js → chunk-WZWZRZ2V.js} +2 -2
- package/dist/{chunk-G4T7L557.js → chunk-XKAR4RF6.js} +2 -2
- package/dist/{chunk-DZD576ZU.js → chunk-XSV2YDCJ.js} +2 -2
- package/dist/{chunk-G7Z6EENO.js → chunk-YDWPYDML.js} +4 -4
- package/dist/{chunk-6HPHEFXT.js → chunk-YKZLOPTC.js} +2 -2
- package/dist/{chunk-NH5IR2WM.js → chunk-YR65W7BV.js} +6 -2
- package/dist/chunk-YR65W7BV.js.map +1 -0
- package/dist/{chunk-GYPQL6XN.js → chunk-Z4N7SVVF.js} +35 -47
- package/dist/chunk-Z4N7SVVF.js.map +1 -0
- package/dist/{cli-DVUFIa5E.d.ts → cli-jfuZXixq.d.ts} +5 -5
- package/dist/cli.d.ts +14 -13
- package/dist/cli.js +74 -72
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +30 -9
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +30 -9
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +29 -9
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +3 -1
- package/dist/contradiction-scan-KCADHXPE.js +20 -0
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +32 -11
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +21 -20
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-XzF76C9n.d.ts → dependency-propagation-delivery-CTDuAVJc.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +30 -9
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/explicit-capture.js +6 -4
- package/dist/external-wiki-access.d.ts +12 -11
- package/dist/external-wiki-access.js +47 -45
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +12 -11
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +29 -13
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-626C5554.js → first-start-migration-LMMZFLKK.js} +13 -5
- package/dist/first-start-migration-LMMZFLKK.js.map +1 -0
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +1439 -716
- package/dist/index.js +207 -121
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +5 -5
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +30 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +30 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +31 -10
- package/dist/{maintenance-BJzNIKBE.d.ts → maintenance-heMZSq7G.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +12 -11
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-mutations.js +2 -2
- package/dist/memory-projection-store.d.ts +6 -2
- package/dist/memory-projection-store.js +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-snapshot.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +20 -19
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +30 -9
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-file-io.d.ts +20 -0
- package/dist/offline-sync-file-io.js +18 -0
- package/dist/offline-sync-file-io.js.map +1 -0
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/offline-sync-impression-drain.js +5 -5
- package/dist/offline-sync.d.ts +8 -6
- package/dist/offline-sync.js +2 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +22 -21
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +34 -32
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +35 -15
- package/dist/{orchestrator-BiXgBJBQ.d.ts → orchestrator-BQQPsSjs.d.ts} +11 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +93 -91
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-sz_dcuLg.d.ts → pipeline-DFfmsnGk.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{purge-533FZLS4.js → purge-GB7PZHT2.js} +2 -2
- package/dist/{qmd-eAWObK4M.d.ts → qmd-BF5bcVRs.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/reconcile/cursor.d.ts +2 -1
- package/dist/reconcile/manifest.d.ts +2 -1
- package/dist/reconcile/manifest.js +6 -5
- package/dist/reconcile/plan.d.ts +2 -1
- package/dist/reconcile/plan.js +3 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -3
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +138 -138
- package/dist/{scope-profiles-BBQsXuyj.d.ts → scope-profiles-CbUOsE_E.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +4 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +6 -6
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-HD8RkzVA.d.ts → semantic-consolidation-DAB3xdKM.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +31 -10
- package/dist/semantic-rule-promotion.js +30 -9
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +30 -9
- package/dist/{service-DIrtHHEg.d.ts → service-DGKg2O6r.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +28 -12
- package/dist/{storage-DqCzXi56.d.ts → storage-CC1jKcWg.d.ts} +46 -13
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +30 -9
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +2 -2
- package/dist/tier-routing.js +7 -1
- package/dist/{tier-stats-2JCZYEF7.js → tier-stats-JDN6HRNT.js} +8 -2
- package/dist/{tier-stats-2JCZYEF7.js.map → tier-stats-JDN6HRNT.js.map} +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-d_j4vC6N.d.ts → types-BnQza1y8.d.ts} +2 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +2 -2
- package/dist/verified-recall.js +30 -9
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-http.test.ts +31 -0
- package/src/access-memory-search-fanout.ts +24 -1
- package/src/access-offline-manifest.ts +5 -0
- package/src/access-service-namespace.test.ts +14 -0
- package/src/access-service-observe-scope.test.ts +2 -0
- package/src/access-service-offline-file-content.test.ts +211 -0
- package/src/access-service.ts +20 -43
- package/src/active-memory-bridge.test.ts +151 -0
- package/src/active-memory-bridge.ts +87 -11
- package/src/briefing.test.ts +38 -0
- package/src/briefing.ts +3 -3
- package/src/cli.ts +6 -1
- package/src/connectors/codex-materialize-runner.ts +2 -0
- package/src/contradiction/contradiction-scan.ts +2 -0
- package/src/contradiction/contradiction.test.ts +42 -0
- package/src/correction/correction-access-wiring.ts +8 -2
- package/src/correction/correction-storage-integration.test.ts +7 -0
- package/src/explicit-capture.test.ts +24 -0
- package/src/explicit-capture.ts +2 -0
- package/src/index.ts +3 -0
- package/src/maintenance/first-start-migration.ts +2 -1
- package/src/maintenance/memory-governance.test.ts +25 -0
- package/src/maintenance/memory-governance.ts +4 -1
- package/src/maintenance/pattern-reinforcement.test.ts +20 -0
- package/src/maintenance/pattern-reinforcement.ts +2 -1
- package/src/maintenance/rebuild-memory-projection.ts +8 -8
- package/src/memory-projection-store.ts +25 -27
- package/src/offline-sync-file-io.ts +68 -0
- package/src/offline-sync.ts +43 -57
- package/src/orchestration/consolidation-run.ts +5 -4
- package/src/orchestration/contradiction-linking-coordinator.ts +7 -0
- package/src/orchestration/contradiction-localization.integration.test.ts +115 -0
- package/src/orchestration/lifecycle-policy-coordinator.ts +2 -1
- package/src/orchestration/qmd-result-resolver.test.ts +215 -14
- package/src/orchestration/qmd-result-resolver.ts +142 -82
- package/src/orchestration/recall-search-pipeline.test.ts +41 -0
- package/src/orchestration/recall-search-pipeline.ts +12 -0
- package/src/orchestration/semantic-consolidation-coordinator.ts +2 -1
- package/src/orchestration/tier-migration-coordinator.ts +23 -56
- package/src/orchestration/workspace-ops.test.ts +37 -0
- package/src/orchestration/workspace-ops.ts +5 -29
- package/src/orchestrator.ts +7 -8
- package/src/storage/supersession-audit.ts +7 -1
- package/src/storage/supersession-side-effects.ts +17 -5
- package/src/storage.ts +18 -19
- package/src/support-passport/card-projection.test.ts +129 -0
- package/src/support-passport/card-projection.ts +287 -0
- package/src/support-passport/card-service.test.ts +2462 -0
- package/src/support-passport/card-service.ts +1042 -0
- package/src/support-passport/contracts.test.ts +143 -0
- package/src/support-passport/contracts.ts +136 -0
- package/src/support-passport/errors.ts +24 -0
- package/src/support-passport/grant-contracts.ts +139 -0
- package/src/support-passport/grant-service-cache.test.ts +148 -0
- package/src/support-passport/grant-service-cancellation.test.ts +81 -0
- package/src/support-passport/grant-service.test.ts +2882 -0
- package/src/support-passport/grant-service.ts +364 -0
- package/src/support-passport/grant-store-commit-hook.test.ts +123 -0
- package/src/support-passport/grant-store-order.test.ts +60 -0
- package/src/support-passport/grant-store-path-safety.test.ts +30 -0
- package/src/support-passport/grant-store-recovery.test.ts +448 -0
- package/src/support-passport/grant-store.ts +902 -0
- package/src/support-passport/index.ts +7 -0
- package/src/support-passport/offline-sync-guard.ts +230 -0
- package/src/support-passport/owner-lock.ts +53 -0
- package/src/support-passport/private-file.ts +371 -0
- package/src/tier-routing.ts +28 -17
- package/src/training-export/converter.test.ts +24 -0
- package/src/training-export/converter.ts +2 -0
- package/src/types.ts +1 -1
- package/src/wearables/service.test.ts +13 -0
- package/src/wearables/service.ts +2 -0
- package/dist/chunk-3CSX3ETE.js.map +0 -1
- package/dist/chunk-6LAPLZOS.js.map +0 -1
- package/dist/chunk-6R4G7N4S.js.map +0 -1
- package/dist/chunk-7BZOBA5J.js.map +0 -1
- package/dist/chunk-CYF6WVE7.js.map +0 -1
- package/dist/chunk-G2LUMAUF.js.map +0 -1
- package/dist/chunk-GYPQL6XN.js.map +0 -1
- package/dist/chunk-JDZPNCMA.js.map +0 -1
- package/dist/chunk-JV5YUBIZ.js.map +0 -1
- package/dist/chunk-NH5IR2WM.js.map +0 -1
- package/dist/chunk-RUMNY5DT.js.map +0 -1
- package/dist/chunk-S75M5ZRK.js.map +0 -1
- package/dist/chunk-T2JHZWH6.js.map +0 -1
- package/dist/chunk-UTRO5UJU.js.map +0 -1
- package/dist/chunk-XUAMCLY4.js.map +0 -1
- package/dist/contradiction-scan-HYLIRMJ2.js +0 -14
- package/dist/first-start-migration-626C5554.js.map +0 -1
- /package/dist/{chunk-47P3RYZP.js.map → chunk-2INWPC6E.js.map} +0 -0
- /package/dist/{chunk-WOPA3SGI.js.map → chunk-3OTSVCPK.js.map} +0 -0
- /package/dist/{chunk-PX2LSHYM.js.map → chunk-3SXLD5T3.js.map} +0 -0
- /package/dist/{chunk-C4ZNGVK3.js.map → chunk-5JKUSBMX.js.map} +0 -0
- /package/dist/{chunk-MAXVH44E.js.map → chunk-6NNNURLK.js.map} +0 -0
- /package/dist/{chunk-LJS7KUPV.js.map → chunk-6PZGKGAO.js.map} +0 -0
- /package/dist/{chunk-YN6ASZUG.js.map → chunk-6QPI2ITJ.js.map} +0 -0
- /package/dist/{chunk-CGVHPCP2.js.map → chunk-7Q7JWLQ4.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-PDDTI7IC.js.map → chunk-B4T3FQMT.js.map} +0 -0
- /package/dist/{chunk-APPFNGX6.js.map → chunk-BSXGTDVJ.js.map} +0 -0
- /package/dist/{chunk-4IIFL7GC.js.map → chunk-CLKBV3Z3.js.map} +0 -0
- /package/dist/{chunk-ZYLC7NFO.js.map → chunk-CSY44OKU.js.map} +0 -0
- /package/dist/{chunk-HLYR2TQL.js.map → chunk-CUTFSLSD.js.map} +0 -0
- /package/dist/{chunk-RDJRFDJY.js.map → chunk-DBY26JJJ.js.map} +0 -0
- /package/dist/{chunk-QWGE2PUH.js.map → chunk-DDA55IVB.js.map} +0 -0
- /package/dist/{chunk-VMMQDEN5.js.map → chunk-DN2IDKI5.js.map} +0 -0
- /package/dist/{chunk-2GFK22OZ.js.map → chunk-DOOWFDWN.js.map} +0 -0
- /package/dist/{chunk-7FGL6CGA.js.map → chunk-DVYRD4HY.js.map} +0 -0
- /package/dist/{chunk-YGLY6QFV.js.map → chunk-F2Y6I74C.js.map} +0 -0
- /package/dist/{chunk-UU7TRFNE.js.map → chunk-FFEBSFVT.js.map} +0 -0
- /package/dist/{chunk-NIS256NW.js.map → chunk-JKW663ZN.js.map} +0 -0
- /package/dist/{chunk-G7A5HLUS.js.map → chunk-KJH6W2I7.js.map} +0 -0
- /package/dist/{chunk-HWRD23DW.js.map → chunk-LVKYLMSM.js.map} +0 -0
- /package/dist/{chunk-ZMYDYNI4.js.map → chunk-N7CXQ5TA.js.map} +0 -0
- /package/dist/{chunk-3JLPGJ43.js.map → chunk-NGT35IGQ.js.map} +0 -0
- /package/dist/{chunk-GUKBW4HL.js.map → chunk-NQMSEB5D.js.map} +0 -0
- /package/dist/{chunk-WRJVB3C2.js.map → chunk-PO7NXBG2.js.map} +0 -0
- /package/dist/{chunk-WM65LU67.js.map → chunk-QOQQMVR2.js.map} +0 -0
- /package/dist/{chunk-QHFV6VNU.js.map → chunk-QPAQSG3F.js.map} +0 -0
- /package/dist/{chunk-UES4BHA2.js.map → chunk-QTO6BMQZ.js.map} +0 -0
- /package/dist/{chunk-THPPCISA.js.map → chunk-RBII7637.js.map} +0 -0
- /package/dist/{chunk-QT2FRYI4.js.map → chunk-RG426DJO.js.map} +0 -0
- /package/dist/{chunk-VKXWK44Q.js.map → chunk-TY6MKIWP.js.map} +0 -0
- /package/dist/{chunk-AZM4CCAC.js.map → chunk-UWJNKGBA.js.map} +0 -0
- /package/dist/{chunk-ZPJ4QSGP.js.map → chunk-VTU5IEKY.js.map} +0 -0
- /package/dist/{chunk-RZ4XGKOJ.js.map → chunk-VYQ4URQB.js.map} +0 -0
- /package/dist/{chunk-WDBX6VER.js.map → chunk-VZRFGJCH.js.map} +0 -0
- /package/dist/{chunk-IBKR2TFR.js.map → chunk-WM5LD6AT.js.map} +0 -0
- /package/dist/{chunk-K4QS7LXU.js.map → chunk-WZWZRZ2V.js.map} +0 -0
- /package/dist/{chunk-G4T7L557.js.map → chunk-XKAR4RF6.js.map} +0 -0
- /package/dist/{chunk-DZD576ZU.js.map → chunk-XSV2YDCJ.js.map} +0 -0
- /package/dist/{chunk-G7Z6EENO.js.map → chunk-YDWPYDML.js.map} +0 -0
- /package/dist/{chunk-6HPHEFXT.js.map → chunk-YKZLOPTC.js.map} +0 -0
- /package/dist/{contradiction-scan-HYLIRMJ2.js.map → contradiction-scan-KCADHXPE.js.map} +0 -0
- /package/dist/{purge-533FZLS4.js.map → purge-GB7PZHT2.js.map} +0 -0
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
import type { StorageManager } from "../index.js";
|
|
2
|
+
import { log } from "../logger.js";
|
|
3
|
+
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
4
|
+
import type { MemoryFile } from "../types.js";
|
|
5
|
+
import type { HeldFileLockController } from "../utils/serialize-mutations.js";
|
|
6
|
+
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
7
|
+
import {
|
|
8
|
+
SUPPORT_PASSPORT_ATTRIBUTE_KEYS,
|
|
9
|
+
SUPPORT_PASSPORT_CARD_TAG,
|
|
10
|
+
type StoredSupportPassportCard,
|
|
11
|
+
computeSupportPassportOwnerKey,
|
|
12
|
+
decodeSupportPassportNamespaceAttributes,
|
|
13
|
+
encodeSupportPassportNamespaceAttributes,
|
|
14
|
+
projectSupportPassportCard,
|
|
15
|
+
} from "./card-projection.js";
|
|
16
|
+
import {
|
|
17
|
+
type SupportPassportCard,
|
|
18
|
+
type SupportPassportCardCategory,
|
|
19
|
+
SupportPassportCardListSchema,
|
|
20
|
+
type SupportPassportCardMutationInput,
|
|
21
|
+
SupportPassportCardMutationInputSchema,
|
|
22
|
+
SupportPassportListCardsInputSchema,
|
|
23
|
+
type SupportPassportManualDraftInput,
|
|
24
|
+
SupportPassportManualDraftInputSchema,
|
|
25
|
+
SupportPassportNamespaceSchema,
|
|
26
|
+
type SupportPassportReplaceCardInput,
|
|
27
|
+
SupportPassportReplaceCardInputSchema,
|
|
28
|
+
computeSupportPassportCardRevision,
|
|
29
|
+
} from "./contracts.js";
|
|
30
|
+
import { SupportPassportError } from "./errors.js";
|
|
31
|
+
import { withSupportPassportOwnerLock } from "./owner-lock.js";
|
|
32
|
+
|
|
33
|
+
export interface SupportPassportOwnerScope {
|
|
34
|
+
principal: string;
|
|
35
|
+
namespace: string;
|
|
36
|
+
storage: StorageManager;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SupportPassportCardServiceDependencies {
|
|
40
|
+
resolveOwner(principal: string): Promise<SupportPassportOwnerScope>;
|
|
41
|
+
now?: () => Date;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const OWNER_VISIBLE_STATUSES = new Set(["pending_review", "active"]);
|
|
45
|
+
const MAX_OWNER_VISIBLE_CARDS = 100;
|
|
46
|
+
|
|
47
|
+
function invalidInput(): SupportPassportError {
|
|
48
|
+
return new SupportPassportError("invalid_input", "The support card request is invalid.", 400);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isStorageConflict(error: unknown): error is SupportPassportError {
|
|
52
|
+
return error instanceof SupportPassportError && error.code === "storage_conflict";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class SupportPassportCardService {
|
|
56
|
+
private readonly resolveOwner: SupportPassportCardServiceDependencies["resolveOwner"];
|
|
57
|
+
private readonly now: () => Date;
|
|
58
|
+
|
|
59
|
+
constructor(dependencies: SupportPassportCardServiceDependencies) {
|
|
60
|
+
this.resolveOwner = dependencies.resolveOwner;
|
|
61
|
+
this.now = dependencies.now ?? (() => new Date());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async listCards(input: { principal: string }): Promise<SupportPassportCard[]> {
|
|
65
|
+
const parsed = SupportPassportListCardsInputSchema.safeParse(input);
|
|
66
|
+
if (!parsed.success) throw invalidInput();
|
|
67
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
68
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
69
|
+
const stored = await this.readStoredCards(storage, lock, principal, namespace);
|
|
70
|
+
const cards = this.ownerVisibleCards(stored)
|
|
71
|
+
.sort((a, b) => a.order - b.order || a.card.cardId.localeCompare(b.card.cardId))
|
|
72
|
+
.map((item) => item.card);
|
|
73
|
+
const output = SupportPassportCardListSchema.safeParse(cards);
|
|
74
|
+
if (!output.success) {
|
|
75
|
+
throw new SupportPassportError("card_data_invalid", "The support card data is invalid.", 500);
|
|
76
|
+
}
|
|
77
|
+
return output.data;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async createManualDraft(input: SupportPassportManualDraftInput): Promise<SupportPassportCard> {
|
|
82
|
+
const parsed = SupportPassportManualDraftInputSchema.safeParse(input);
|
|
83
|
+
if (!parsed.success) throw invalidInput();
|
|
84
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
85
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, (lock) =>
|
|
86
|
+
this.createDraft(
|
|
87
|
+
storage,
|
|
88
|
+
{
|
|
89
|
+
title: parsed.data.title,
|
|
90
|
+
statement: parsed.data.statement,
|
|
91
|
+
category: parsed.data.category,
|
|
92
|
+
reviewBy: parsed.data.reviewBy,
|
|
93
|
+
sourceMemoryIds: [],
|
|
94
|
+
},
|
|
95
|
+
lock,
|
|
96
|
+
principal,
|
|
97
|
+
namespace
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async replaceCard(input: SupportPassportReplaceCardInput): Promise<SupportPassportCard> {
|
|
103
|
+
const parsed = SupportPassportReplaceCardInputSchema.safeParse(input);
|
|
104
|
+
if (!parsed.success) throw invalidInput();
|
|
105
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
106
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
107
|
+
const loadedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
108
|
+
const storedCards = await this.readProjectedCards(storage, namespace, principal);
|
|
109
|
+
const interruptedReplacements = storedCards.filter(
|
|
110
|
+
(item) =>
|
|
111
|
+
item.card.status === "pending_review" &&
|
|
112
|
+
(item.replacesDraftId === loadedPrior.card.cardId ||
|
|
113
|
+
item.memory.frontmatter.supersedes === loadedPrior.card.cardId)
|
|
114
|
+
);
|
|
115
|
+
if (interruptedReplacements.length > 1) {
|
|
116
|
+
throw new SupportPassportError(
|
|
117
|
+
"storage_conflict",
|
|
118
|
+
"Multiple support card edits already completed. Reload the support passport.",
|
|
119
|
+
409
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const [interruptedReplacement] = interruptedReplacements;
|
|
123
|
+
if (interruptedReplacement) {
|
|
124
|
+
if (interruptedReplacement.replacedRevision !== parsed.data.expectedRevision) {
|
|
125
|
+
throw new SupportPassportError("revision_conflict", "The support card changed after it was loaded.", 409);
|
|
126
|
+
}
|
|
127
|
+
const matchesRetry =
|
|
128
|
+
interruptedReplacement.card.title === parsed.data.title &&
|
|
129
|
+
interruptedReplacement.card.statement === parsed.data.statement &&
|
|
130
|
+
interruptedReplacement.card.category === parsed.data.category &&
|
|
131
|
+
interruptedReplacement.card.reviewBy === parsed.data.reviewBy;
|
|
132
|
+
if (!matchesRetry) {
|
|
133
|
+
throw new SupportPassportError(
|
|
134
|
+
"storage_conflict",
|
|
135
|
+
"A different support card edit already completed. Reload the support passport.",
|
|
136
|
+
409
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (!interruptedReplacement.draftReplacementPrepared) return interruptedReplacement.card;
|
|
140
|
+
const recovered = await this.recoverReplacementTransition(
|
|
141
|
+
storage,
|
|
142
|
+
interruptedReplacement.memory,
|
|
143
|
+
lock,
|
|
144
|
+
principal,
|
|
145
|
+
namespace
|
|
146
|
+
);
|
|
147
|
+
return this.projectRequiredCard(recovered, namespace, principal).card;
|
|
148
|
+
}
|
|
149
|
+
this.requireRevision(loadedPrior, parsed.data.expectedRevision);
|
|
150
|
+
await this.readStoredCards(storage, lock, principal, namespace);
|
|
151
|
+
const refreshedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
152
|
+
this.requireRevision(refreshedPrior, parsed.data.expectedRevision);
|
|
153
|
+
const recoveredMemory = await this.recoverReplacementTransition(
|
|
154
|
+
storage,
|
|
155
|
+
refreshedPrior.memory,
|
|
156
|
+
lock,
|
|
157
|
+
principal,
|
|
158
|
+
namespace
|
|
159
|
+
);
|
|
160
|
+
const prior = this.projectRequiredCard(recoveredMemory, namespace, principal);
|
|
161
|
+
if (prior.card.status !== "active" && prior.card.status !== "pending_review") {
|
|
162
|
+
throw new SupportPassportError(
|
|
163
|
+
"invalid_card_status",
|
|
164
|
+
"Only a draft or approved support card can be edited.",
|
|
165
|
+
409
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const replacement = await this.createDraft(
|
|
169
|
+
storage,
|
|
170
|
+
{
|
|
171
|
+
title: parsed.data.title,
|
|
172
|
+
statement: parsed.data.statement,
|
|
173
|
+
category: parsed.data.category,
|
|
174
|
+
reviewBy: parsed.data.reviewBy,
|
|
175
|
+
sourceMemoryIds: prior.sourceMemoryIds,
|
|
176
|
+
supersedes: prior.card.status === "active" ? prior.card.cardId : prior.memory.frontmatter.supersedes,
|
|
177
|
+
replacesDraftId: prior.card.status === "pending_review" ? prior.card.cardId : undefined,
|
|
178
|
+
replacedRevision: prior.card.revision,
|
|
179
|
+
order: prior.order,
|
|
180
|
+
draftReplacementPrepared: prior.card.status === "pending_review",
|
|
181
|
+
},
|
|
182
|
+
lock,
|
|
183
|
+
principal,
|
|
184
|
+
namespace
|
|
185
|
+
);
|
|
186
|
+
if (prior.card.status === "active") return replacement;
|
|
187
|
+
|
|
188
|
+
const replacedAt = this.now().toISOString();
|
|
189
|
+
await this.requireOwnerLock(lock);
|
|
190
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
191
|
+
prior.memory,
|
|
192
|
+
{ status: "rejected", updated: replacedAt },
|
|
193
|
+
{ actor: principal, reasonCode: "owner-replaced-draft" }
|
|
194
|
+
);
|
|
195
|
+
if (rejected) {
|
|
196
|
+
return await this.finishPreparedDraftReplacement(storage, replacement.cardId, lock, principal, namespace);
|
|
197
|
+
}
|
|
198
|
+
const currentPrior = await storage.getMemoryById(prior.card.cardId);
|
|
199
|
+
const currentPriorCard = currentPrior ? this.projectOwnedCard(currentPrior, namespace, principal) : null;
|
|
200
|
+
if (currentPriorCard?.card.status === "rejected") {
|
|
201
|
+
return await this.finishPreparedDraftReplacement(storage, replacement.cardId, lock, principal, namespace);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await this.rejectCreatedDraft(
|
|
205
|
+
storage,
|
|
206
|
+
replacement.cardId,
|
|
207
|
+
"draft-replacement-failed",
|
|
208
|
+
lock,
|
|
209
|
+
principal,
|
|
210
|
+
namespace
|
|
211
|
+
);
|
|
212
|
+
throw new SupportPassportError("storage_conflict", "The support card changed before it was edited.", 409);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async approveCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
|
|
217
|
+
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
218
|
+
if (!parsed.success) throw invalidInput();
|
|
219
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
220
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
221
|
+
const loadedCard = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
222
|
+
this.requireRevision(loadedCard, parsed.data.expectedRevision);
|
|
223
|
+
this.requireStatus(loadedCard, "pending_review");
|
|
224
|
+
const recoveredMemory = await this.recoverReplacementTransition(
|
|
225
|
+
storage,
|
|
226
|
+
loadedCard.memory,
|
|
227
|
+
lock,
|
|
228
|
+
principal,
|
|
229
|
+
namespace
|
|
230
|
+
);
|
|
231
|
+
const card = this.projectRequiredCard(recoveredMemory, namespace, principal);
|
|
232
|
+
if (card.card.status === "active") return card.card;
|
|
233
|
+
if (card.card.status === "rejected") {
|
|
234
|
+
throw new SupportPassportError(
|
|
235
|
+
"storage_conflict",
|
|
236
|
+
"The support card was rejected while its replacement state was recovered.",
|
|
237
|
+
409
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
this.requireStatus(card, "pending_review");
|
|
241
|
+
await this.validatePriorForReplacement(storage, card, namespace, principal);
|
|
242
|
+
const updatedAt = this.now().toISOString();
|
|
243
|
+
await this.requireOwnerLock(lock);
|
|
244
|
+
let approved: boolean;
|
|
245
|
+
try {
|
|
246
|
+
approved = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
247
|
+
card.memory,
|
|
248
|
+
{ status: "active", updated: updatedAt },
|
|
249
|
+
{ actor: principal, reasonCode: "owner-approved" }
|
|
250
|
+
);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const current = await storage.getMemoryById(card.card.cardId);
|
|
253
|
+
const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
|
|
254
|
+
if (currentCard?.card.status === "active") {
|
|
255
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace);
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
if (!approved) {
|
|
260
|
+
const current = await storage.getMemoryById(card.card.cardId);
|
|
261
|
+
const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
|
|
262
|
+
if (currentCard?.card.status === "active") {
|
|
263
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace);
|
|
264
|
+
}
|
|
265
|
+
throw new SupportPassportError("storage_conflict", "The support card changed before approval.", 409);
|
|
266
|
+
}
|
|
267
|
+
const committedCard: SupportPassportCard = {
|
|
268
|
+
...card.card,
|
|
269
|
+
status: "active",
|
|
270
|
+
updatedAt,
|
|
271
|
+
revision: this.revisionFor(card.card, "active", updatedAt),
|
|
272
|
+
};
|
|
273
|
+
try {
|
|
274
|
+
const current = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
275
|
+
return await this.finishCommittedApproval(storage, current, lock, principal, namespace);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (isStorageConflict(error)) throw error;
|
|
278
|
+
log.warn(
|
|
279
|
+
`support passport could not finish replacement approval side effects: ${error instanceof Error ? error.message : String(error)}`
|
|
280
|
+
);
|
|
281
|
+
return committedCard;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async rejectCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
|
|
287
|
+
return await this.changeStatus(input, "pending_review", "rejected");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async withdrawCard(input: SupportPassportCardMutationInput): Promise<SupportPassportCard> {
|
|
291
|
+
return await this.changeStatus(input, "active", "archived");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private async createDraft(
|
|
295
|
+
storage: StorageManager,
|
|
296
|
+
input: {
|
|
297
|
+
title: string;
|
|
298
|
+
statement: string;
|
|
299
|
+
category: SupportPassportCardCategory;
|
|
300
|
+
reviewBy?: string;
|
|
301
|
+
sourceMemoryIds: string[];
|
|
302
|
+
supersedes?: string;
|
|
303
|
+
replacesDraftId?: string;
|
|
304
|
+
replacedRevision?: string;
|
|
305
|
+
order?: number;
|
|
306
|
+
draftReplacementPrepared?: boolean;
|
|
307
|
+
},
|
|
308
|
+
lock: HeldFileLockController,
|
|
309
|
+
principal: string,
|
|
310
|
+
namespace: string
|
|
311
|
+
): Promise<SupportPassportCard> {
|
|
312
|
+
const now = this.now();
|
|
313
|
+
const reviewBy = input.reviewBy ?? now.toISOString();
|
|
314
|
+
const storedCards = await this.readStoredCards(storage, lock, principal, namespace);
|
|
315
|
+
const visibleCards = this.ownerVisibleCards(storedCards);
|
|
316
|
+
const replacesVisibleCard = visibleCards.some(
|
|
317
|
+
(item) =>
|
|
318
|
+
(input.replacesDraftId === item.card.cardId && item.card.status === "pending_review") ||
|
|
319
|
+
(input.supersedes === item.card.cardId && item.card.status === "active")
|
|
320
|
+
);
|
|
321
|
+
if (visibleCards.length - (replacesVisibleCard ? 1 : 0) >= MAX_OWNER_VISIBLE_CARDS) {
|
|
322
|
+
throw new SupportPassportError("invalid_input", "A support passport can contain at most 100 visible cards.", 400);
|
|
323
|
+
}
|
|
324
|
+
const maximumOrder = storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1);
|
|
325
|
+
const order = input.order ?? maximumOrder + 1;
|
|
326
|
+
if (!Number.isSafeInteger(order)) {
|
|
327
|
+
throw new SupportPassportError("storage_conflict", "The support card order range is exhausted.", 409);
|
|
328
|
+
}
|
|
329
|
+
const envelope = composeMemoryEnvelope(
|
|
330
|
+
{
|
|
331
|
+
content: input.statement,
|
|
332
|
+
category: "preference",
|
|
333
|
+
tags: [SUPPORT_PASSPORT_CARD_TAG],
|
|
334
|
+
structuredAttributes: {
|
|
335
|
+
...encodeSupportPassportNamespaceAttributes(namespace),
|
|
336
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner]: computeSupportPassportOwnerKey(principal),
|
|
337
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.title]: input.title,
|
|
338
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.category]: input.category,
|
|
339
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.order]: String(order),
|
|
340
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.reviewBy]: reviewBy,
|
|
341
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.sourceMemoryIds]: input.sourceMemoryIds.join(","),
|
|
342
|
+
...(input.replacesDraftId
|
|
343
|
+
? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacesDraftId]: input.replacesDraftId }
|
|
344
|
+
: {}),
|
|
345
|
+
...(input.replacedRevision
|
|
346
|
+
? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacedRevision]: input.replacedRevision }
|
|
347
|
+
: {}),
|
|
348
|
+
...(input.draftReplacementPrepared
|
|
349
|
+
? { [SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared]: "true" }
|
|
350
|
+
: {}),
|
|
351
|
+
},
|
|
352
|
+
sourceReason: "support-passport",
|
|
353
|
+
},
|
|
354
|
+
{ source: "support-passport", now: this.now }
|
|
355
|
+
);
|
|
356
|
+
if (envelope.sanitizeViolations.length > 0) throw invalidInput();
|
|
357
|
+
await this.requireOwnerLock(lock);
|
|
358
|
+
const written = await storage.writeSealedMemory(envelope, {
|
|
359
|
+
actor: principal,
|
|
360
|
+
status: "pending_review",
|
|
361
|
+
lineage: input.sourceMemoryIds.length > 0 ? input.sourceMemoryIds : undefined,
|
|
362
|
+
supersedes: input.supersedes,
|
|
363
|
+
});
|
|
364
|
+
if (written.tombstoneBlocked) {
|
|
365
|
+
throw new SupportPassportError("storage_conflict", "The support card needs memory review before use.", 409);
|
|
366
|
+
}
|
|
367
|
+
return this.projectRequiredCard(written.memory, namespace, principal).card;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private async changeStatus(
|
|
371
|
+
input: SupportPassportCardMutationInput,
|
|
372
|
+
expectedStatus: "pending_review" | "active",
|
|
373
|
+
nextStatus: "rejected" | "archived"
|
|
374
|
+
): Promise<SupportPassportCard> {
|
|
375
|
+
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
376
|
+
if (!parsed.success) throw invalidInput();
|
|
377
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
378
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
379
|
+
let card = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
380
|
+
this.requireRevision(card, parsed.data.expectedRevision);
|
|
381
|
+
this.requireStatus(card, expectedStatus);
|
|
382
|
+
if (expectedStatus === "pending_review" && nextStatus === "rejected") {
|
|
383
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
|
|
384
|
+
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
385
|
+
this.requireRevision(card, parsed.data.expectedRevision);
|
|
386
|
+
this.requireStatus(card, expectedStatus);
|
|
387
|
+
}
|
|
388
|
+
if (
|
|
389
|
+
expectedStatus === "pending_review" ||
|
|
390
|
+
(expectedStatus === "active" && (card.replacesDraftId || card.memory.frontmatter.supersedes))
|
|
391
|
+
) {
|
|
392
|
+
card = this.projectRequiredCard(
|
|
393
|
+
await this.recoverReplacementTransition(storage, card.memory, lock, principal, namespace, {
|
|
394
|
+
rollbackConflictedApproval: expectedStatus !== "active",
|
|
395
|
+
}),
|
|
396
|
+
namespace,
|
|
397
|
+
principal
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (card.card.status === nextStatus) return card.card;
|
|
401
|
+
this.requireStatus(card, expectedStatus);
|
|
402
|
+
if (expectedStatus === "active" && nextStatus === "archived") {
|
|
403
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
|
|
404
|
+
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
405
|
+
this.requireRevision(card, parsed.data.expectedRevision);
|
|
406
|
+
this.requireStatus(card, expectedStatus);
|
|
407
|
+
}
|
|
408
|
+
const updatedAt = this.now().toISOString();
|
|
409
|
+
await this.requireOwnerLock(lock);
|
|
410
|
+
const changed = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
411
|
+
card.memory,
|
|
412
|
+
{
|
|
413
|
+
status: nextStatus,
|
|
414
|
+
updated: updatedAt,
|
|
415
|
+
...(nextStatus === "archived" ? { archivedAt: updatedAt } : {}),
|
|
416
|
+
},
|
|
417
|
+
{ actor: principal, reasonCode: nextStatus === "archived" ? "owner-withdrew" : "owner-rejected" }
|
|
418
|
+
);
|
|
419
|
+
if (!changed)
|
|
420
|
+
throw new SupportPassportError(
|
|
421
|
+
"storage_conflict",
|
|
422
|
+
"The support card changed before the request completed.",
|
|
423
|
+
409
|
|
424
|
+
);
|
|
425
|
+
return {
|
|
426
|
+
...card.card,
|
|
427
|
+
status: nextStatus,
|
|
428
|
+
updatedAt,
|
|
429
|
+
revision: this.revisionFor(card.card, nextStatus, updatedAt),
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private revisionFor(card: SupportPassportCard, status: SupportPassportCard["status"], updatedAt: string): string {
|
|
435
|
+
return computeSupportPassportCardRevision({
|
|
436
|
+
cardId: card.cardId,
|
|
437
|
+
title: card.title,
|
|
438
|
+
statement: card.statement,
|
|
439
|
+
category: card.category,
|
|
440
|
+
status,
|
|
441
|
+
updatedAt,
|
|
442
|
+
reviewBy: card.reviewBy,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private async resolveOwnerScope(principal: string): Promise<SupportPassportOwnerScope> {
|
|
447
|
+
const requestedPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal });
|
|
448
|
+
const owner = await this.resolveOwner(principal);
|
|
449
|
+
const namespace = SupportPassportNamespaceSchema.safeParse(owner.namespace);
|
|
450
|
+
const ownerPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal: owner.principal });
|
|
451
|
+
if (
|
|
452
|
+
!requestedPrincipal.success ||
|
|
453
|
+
!namespace.success ||
|
|
454
|
+
!ownerPrincipal.success ||
|
|
455
|
+
ownerPrincipal.data.principal !== requestedPrincipal.data.principal
|
|
456
|
+
) {
|
|
457
|
+
throw new SupportPassportError("card_data_invalid", "The support passport owner scope is invalid.", 500);
|
|
458
|
+
}
|
|
459
|
+
return { ...owner, principal: ownerPrincipal.data.principal, namespace: namespace.data };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private async rejectCreatedDraft(
|
|
463
|
+
storage: StorageManager,
|
|
464
|
+
cardId: string,
|
|
465
|
+
reasonCode: string,
|
|
466
|
+
lock: HeldFileLockController,
|
|
467
|
+
principal: string,
|
|
468
|
+
namespace: string
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
try {
|
|
471
|
+
const current = await this.requireCard(storage, cardId, namespace, principal);
|
|
472
|
+
await this.requireOwnerLock(lock);
|
|
473
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
474
|
+
current.memory,
|
|
475
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
476
|
+
{ actor: principal, reasonCode }
|
|
477
|
+
);
|
|
478
|
+
if (!rejected) log.warn("support passport could not roll back a replacement draft");
|
|
479
|
+
} catch (error) {
|
|
480
|
+
log.warn(
|
|
481
|
+
`support passport could not roll back a replacement draft: ${error instanceof Error ? error.message : String(error)}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private async requireCard(
|
|
487
|
+
storage: StorageManager,
|
|
488
|
+
cardId: string,
|
|
489
|
+
namespace: string,
|
|
490
|
+
principal: string
|
|
491
|
+
): Promise<StoredSupportPassportCard> {
|
|
492
|
+
const stored = await storage.getMemoryById(cardId);
|
|
493
|
+
const card = stored ? this.projectOwnedCard(stored, namespace, principal) : null;
|
|
494
|
+
if (!card) throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
|
|
495
|
+
return card;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private projectRequiredCard(memory: MemoryFile, namespace: string, principal: string): StoredSupportPassportCard {
|
|
499
|
+
const card = projectSupportPassportCard(memory);
|
|
500
|
+
if (!card) throw new SupportPassportError("card_data_invalid", "The support card data is invalid.", 500);
|
|
501
|
+
if (
|
|
502
|
+
card.namespace !== namespace ||
|
|
503
|
+
card.owner !== computeSupportPassportOwnerKey(principal)
|
|
504
|
+
) {
|
|
505
|
+
throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
|
|
506
|
+
}
|
|
507
|
+
return card;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private projectOwnedCard(memory: MemoryFile, namespace: string, principal: string): StoredSupportPassportCard | null {
|
|
511
|
+
const card = projectSupportPassportCard(memory);
|
|
512
|
+
return card?.namespace === namespace &&
|
|
513
|
+
card.owner === computeSupportPassportOwnerKey(principal)
|
|
514
|
+
? card
|
|
515
|
+
: null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private ownsMemory(memory: MemoryFile, namespace: string, principal: string): boolean {
|
|
519
|
+
const attributes = memory.frontmatter.structuredAttributes;
|
|
520
|
+
return (
|
|
521
|
+
attributes !== undefined &&
|
|
522
|
+
decodeSupportPassportNamespaceAttributes(attributes) === namespace &&
|
|
523
|
+
attributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner] === computeSupportPassportOwnerKey(principal)
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private async readProjectedCards(
|
|
528
|
+
storage: StorageManager,
|
|
529
|
+
namespace: string,
|
|
530
|
+
principal: string
|
|
531
|
+
): Promise<StoredSupportPassportCard[]> {
|
|
532
|
+
const owner = computeSupportPassportOwnerKey(principal);
|
|
533
|
+
const projected = (await storage.readAllMemories())
|
|
534
|
+
.map(projectSupportPassportCard)
|
|
535
|
+
.filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
|
|
536
|
+
const ids = new Set<string>();
|
|
537
|
+
for (const item of projected) {
|
|
538
|
+
if (ids.has(item.card.cardId)) {
|
|
539
|
+
throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
|
|
540
|
+
}
|
|
541
|
+
ids.add(item.card.cardId);
|
|
542
|
+
}
|
|
543
|
+
return projected;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private requireRevision(card: StoredSupportPassportCard, expectedRevision: string): void {
|
|
547
|
+
if (card.card.revision !== expectedRevision) {
|
|
548
|
+
throw new SupportPassportError("revision_conflict", "The support card changed after it was loaded.", 409);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private requireStatus(card: StoredSupportPassportCard, status: SupportPassportCard["status"]): void {
|
|
553
|
+
if (card.card.status !== status) {
|
|
554
|
+
throw new SupportPassportError("invalid_card_status", `The support card must have status ${status}.`, 409);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private async readStoredCards(
|
|
559
|
+
storage: StorageManager,
|
|
560
|
+
lock: HeldFileLockController,
|
|
561
|
+
principal: string,
|
|
562
|
+
namespace: string
|
|
563
|
+
): Promise<StoredSupportPassportCard[]> {
|
|
564
|
+
const initialVersion = storage.getCorpusScanVersion();
|
|
565
|
+
const initial = await storage.readAllMemories();
|
|
566
|
+
for (const memory of initial) {
|
|
567
|
+
const card = this.projectOwnedCard(memory, namespace, principal);
|
|
568
|
+
if (card) await this.recoverReplacementTransition(storage, memory, lock, principal, namespace);
|
|
569
|
+
}
|
|
570
|
+
const memories: MemoryFile[] =
|
|
571
|
+
storage.getCorpusScanVersion() === initialVersion ? initial : await storage.readAllMemories();
|
|
572
|
+
const owner = computeSupportPassportOwnerKey(principal);
|
|
573
|
+
const projected = memories
|
|
574
|
+
.map(projectSupportPassportCard)
|
|
575
|
+
.filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
|
|
576
|
+
const ids = new Set<string>();
|
|
577
|
+
for (const item of projected) {
|
|
578
|
+
if (ids.has(item.card.cardId)) {
|
|
579
|
+
throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
|
|
580
|
+
}
|
|
581
|
+
ids.add(item.card.cardId);
|
|
582
|
+
}
|
|
583
|
+
return projected;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private ownerVisibleCards(storedCards: StoredSupportPassportCard[]): StoredSupportPassportCard[] {
|
|
587
|
+
const visibleCards = storedCards.filter(
|
|
588
|
+
(item) => OWNER_VISIBLE_STATUSES.has(item.card.status) && !item.draftReplacementPrepared
|
|
589
|
+
);
|
|
590
|
+
if (visibleCards.length <= MAX_OWNER_VISIBLE_CARDS) return visibleCards;
|
|
591
|
+
const activeCardsWithPendingReplacements = new Set(
|
|
592
|
+
visibleCards
|
|
593
|
+
.filter((item) => item.card.status === "pending_review")
|
|
594
|
+
.map((item) => item.memory.frontmatter.supersedes)
|
|
595
|
+
.filter((cardId): cardId is string => typeof cardId === "string")
|
|
596
|
+
);
|
|
597
|
+
return visibleCards.filter(
|
|
598
|
+
(item) => item.card.status !== "active" || !activeCardsWithPendingReplacements.has(item.card.cardId)
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private async preparePriorForReplacement(
|
|
603
|
+
storage: StorageManager,
|
|
604
|
+
replacement: StoredSupportPassportCard,
|
|
605
|
+
lock: HeldFileLockController,
|
|
606
|
+
principal: string,
|
|
607
|
+
namespace: string
|
|
608
|
+
): Promise<string | null> {
|
|
609
|
+
const priorId = replacement.memory.frontmatter.supersedes;
|
|
610
|
+
if (!priorId) return null;
|
|
611
|
+
const priorMemory = await storage.getMemoryById(priorId);
|
|
612
|
+
const priorIsOwned = priorMemory ? this.ownsMemory(priorMemory, namespace, principal) : false;
|
|
613
|
+
const alreadyRetired =
|
|
614
|
+
priorIsOwned &&
|
|
615
|
+
priorMemory?.frontmatter.status === "superseded" &&
|
|
616
|
+
priorMemory.frontmatter.supersededBy === replacement.card.cardId;
|
|
617
|
+
const prior = priorMemory && priorIsOwned && !alreadyRetired ? projectSupportPassportCard(priorMemory) : null;
|
|
618
|
+
if (prior?.card.status === "rejected") return null;
|
|
619
|
+
if (!priorMemory || (!alreadyRetired && prior?.card.status !== "active")) {
|
|
620
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
621
|
+
}
|
|
622
|
+
if (alreadyRetired) return priorId;
|
|
623
|
+
const retiredAt = this.now().toISOString();
|
|
624
|
+
await this.requireOwnerLock(lock);
|
|
625
|
+
const retired = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
626
|
+
priorMemory,
|
|
627
|
+
{
|
|
628
|
+
status: "superseded",
|
|
629
|
+
supersededBy: replacement.card.cardId,
|
|
630
|
+
supersededAt: retiredAt,
|
|
631
|
+
supersessionCause: "direct",
|
|
632
|
+
updated: retiredAt,
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
actor: principal,
|
|
636
|
+
reasonCode: "support-passport-replacement-pending",
|
|
637
|
+
relatedMemoryIds: [replacement.card.cardId],
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
if (!retired) {
|
|
641
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
642
|
+
}
|
|
643
|
+
return priorId;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private async rejectPendingReplacementForPredecessor(
|
|
647
|
+
storage: StorageManager,
|
|
648
|
+
predecessor: StoredSupportPassportCard,
|
|
649
|
+
lock: HeldFileLockController,
|
|
650
|
+
principal: string,
|
|
651
|
+
namespace: string
|
|
652
|
+
): Promise<void> {
|
|
653
|
+
const storedCards =
|
|
654
|
+
predecessor.card.status === "active"
|
|
655
|
+
? await this.readStoredCards(storage, lock, principal, namespace)
|
|
656
|
+
: await this.readProjectedCards(storage, namespace, principal);
|
|
657
|
+
const replacements = storedCards.filter((item) => {
|
|
658
|
+
if (item.card.status !== "pending_review") return false;
|
|
659
|
+
return predecessor.card.status === "pending_review"
|
|
660
|
+
? item.replacesDraftId === predecessor.card.cardId
|
|
661
|
+
: item.memory.frontmatter.supersedes === predecessor.card.cardId;
|
|
662
|
+
});
|
|
663
|
+
if (replacements.length > 1) {
|
|
664
|
+
throw new SupportPassportError(
|
|
665
|
+
"storage_conflict",
|
|
666
|
+
"Multiple support card edits target the card being withdrawn.",
|
|
667
|
+
409
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
const [replacement] = replacements;
|
|
671
|
+
if (!replacement) return;
|
|
672
|
+
await this.requireOwnerLock(lock);
|
|
673
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
674
|
+
replacement.memory,
|
|
675
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
676
|
+
{
|
|
677
|
+
actor: principal,
|
|
678
|
+
reasonCode: predecessor.card.status === "pending_review" ? "source-draft-rejected" : "predecessor-withdrawn",
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
if (rejected) return;
|
|
682
|
+
const current = await storage.getMemoryById(replacement.card.cardId);
|
|
683
|
+
const currentCard = current ? this.projectOwnedCard(current, namespace, principal) : null;
|
|
684
|
+
if (currentCard?.card.status !== "rejected") {
|
|
685
|
+
throw new SupportPassportError("storage_conflict", "The pending support card edit could not be cancelled.", 409);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private async validatePriorForReplacement(
|
|
690
|
+
storage: StorageManager,
|
|
691
|
+
replacement: StoredSupportPassportCard,
|
|
692
|
+
namespace: string,
|
|
693
|
+
principal: string
|
|
694
|
+
): Promise<void> {
|
|
695
|
+
const priorId = replacement.memory.frontmatter.supersedes;
|
|
696
|
+
if (!priorId) return;
|
|
697
|
+
const priorMemory = await storage.getMemoryById(priorId);
|
|
698
|
+
const priorIsOwned = priorMemory ? this.ownsMemory(priorMemory, namespace, principal) : false;
|
|
699
|
+
const alreadyRetired =
|
|
700
|
+
priorIsOwned &&
|
|
701
|
+
priorMemory?.frontmatter.status === "superseded" &&
|
|
702
|
+
priorMemory.frontmatter.supersededBy === replacement.card.cardId;
|
|
703
|
+
const prior = priorMemory && priorIsOwned && !alreadyRetired ? projectSupportPassportCard(priorMemory) : null;
|
|
704
|
+
if (prior?.card.status === "rejected" || alreadyRetired) return;
|
|
705
|
+
if (!priorMemory || prior?.card.status !== "active") {
|
|
706
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private async recoverReplacementTransition(
|
|
711
|
+
storage: StorageManager,
|
|
712
|
+
memory: MemoryFile,
|
|
713
|
+
lock: HeldFileLockController,
|
|
714
|
+
principal: string,
|
|
715
|
+
namespace: string,
|
|
716
|
+
options: { rollbackConflictedApproval?: boolean } = {}
|
|
717
|
+
): Promise<MemoryFile> {
|
|
718
|
+
const replacement = this.projectOwnedCard(memory, namespace, principal);
|
|
719
|
+
if (replacement?.card.status !== "pending_review" && replacement?.card.status !== "active") return memory;
|
|
720
|
+
if (!replacement.replacesDraftId && !memory.frontmatter.supersedes) return memory;
|
|
721
|
+
if (
|
|
722
|
+
replacement.card.status === "active" &&
|
|
723
|
+
memory.frontmatter.structuredAttributes?.[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete] === "true"
|
|
724
|
+
) {
|
|
725
|
+
return memory;
|
|
726
|
+
}
|
|
727
|
+
await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace);
|
|
728
|
+
const currentMemory = (await storage.getMemoryById(replacement.card.cardId)) ?? memory;
|
|
729
|
+
const currentCard = this.projectOwnedCard(currentMemory, namespace, principal);
|
|
730
|
+
if (currentCard?.card.status !== "pending_review" && currentCard?.card.status !== "active") {
|
|
731
|
+
return currentMemory;
|
|
732
|
+
}
|
|
733
|
+
if (currentCard.card.status === "pending_review") {
|
|
734
|
+
const priorId = currentMemory.frontmatter.supersedes;
|
|
735
|
+
if (!priorId) return currentMemory;
|
|
736
|
+
const prior = await storage.getMemoryById(priorId);
|
|
737
|
+
if (
|
|
738
|
+
prior &&
|
|
739
|
+
this.ownsMemory(prior, namespace, principal) &&
|
|
740
|
+
prior.frontmatter.status === "superseded" &&
|
|
741
|
+
prior.frontmatter.supersededBy === replacement.card.cardId
|
|
742
|
+
) {
|
|
743
|
+
await this.restorePriorAfterApprovalFailure(
|
|
744
|
+
storage,
|
|
745
|
+
priorId,
|
|
746
|
+
replacement.card.cardId,
|
|
747
|
+
lock,
|
|
748
|
+
principal,
|
|
749
|
+
namespace
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (!isStorageConflict(error)) throw error;
|
|
758
|
+
if (options.rollbackConflictedApproval === false) throw error;
|
|
759
|
+
await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace);
|
|
760
|
+
}
|
|
761
|
+
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private async completeReplacementAfterActivation(
|
|
765
|
+
storage: StorageManager,
|
|
766
|
+
replacement: StoredSupportPassportCard,
|
|
767
|
+
lock: HeldFileLockController,
|
|
768
|
+
principal: string,
|
|
769
|
+
namespace: string
|
|
770
|
+
): Promise<void> {
|
|
771
|
+
const priorId = await this.preparePriorForReplacement(storage, replacement, lock, principal, namespace);
|
|
772
|
+
if (priorId && !(await this.completePriorRetirement(storage, priorId, replacement, lock, principal, namespace))) {
|
|
773
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
774
|
+
}
|
|
775
|
+
await this.markReplacementComplete(storage, replacement.card.cardId, lock, principal, namespace);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private async finishCommittedApproval(
|
|
779
|
+
storage: StorageManager,
|
|
780
|
+
replacement: StoredSupportPassportCard,
|
|
781
|
+
lock: HeldFileLockController,
|
|
782
|
+
principal: string,
|
|
783
|
+
namespace: string
|
|
784
|
+
): Promise<SupportPassportCard> {
|
|
785
|
+
try {
|
|
786
|
+
if (replacement.replacesDraftId || replacement.memory.frontmatter.supersedes) {
|
|
787
|
+
await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace);
|
|
788
|
+
}
|
|
789
|
+
} catch (error) {
|
|
790
|
+
if (isStorageConflict(error)) {
|
|
791
|
+
await this.rollbackConflictedApproval(storage, replacement.card.cardId, lock, principal, namespace);
|
|
792
|
+
throw error;
|
|
793
|
+
}
|
|
794
|
+
log.warn(
|
|
795
|
+
`support passport could not finish replacement approval side effects: ${error instanceof Error ? error.message : String(error)}`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
return replacement.card;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private async rollbackConflictedApproval(
|
|
802
|
+
storage: StorageManager,
|
|
803
|
+
replacementId: string,
|
|
804
|
+
lock: HeldFileLockController,
|
|
805
|
+
principal: string,
|
|
806
|
+
namespace: string
|
|
807
|
+
): Promise<void> {
|
|
808
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
809
|
+
if (replacement.card.status !== "active") return;
|
|
810
|
+
await this.requireOwnerLock(lock);
|
|
811
|
+
const rolledBack = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
812
|
+
replacement.memory,
|
|
813
|
+
{ status: "pending_review", updated: this.now().toISOString() },
|
|
814
|
+
{ actor: principal, reasonCode: "replacement-predecessor-conflict" }
|
|
815
|
+
);
|
|
816
|
+
if (!rolledBack) {
|
|
817
|
+
throw new SupportPassportError(
|
|
818
|
+
"storage_conflict",
|
|
819
|
+
"The replacement changed while its approval was rolled back.",
|
|
820
|
+
409
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
const priorId = replacement.memory.frontmatter.supersedes;
|
|
824
|
+
if (priorId) {
|
|
825
|
+
await this.restorePriorAfterApprovalFailure(storage, priorId, replacementId, lock, principal, namespace);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private async completePriorRetirement(
|
|
830
|
+
storage: StorageManager,
|
|
831
|
+
priorId: string,
|
|
832
|
+
replacement: StoredSupportPassportCard,
|
|
833
|
+
lock: HeldFileLockController,
|
|
834
|
+
principal: string,
|
|
835
|
+
namespace: string
|
|
836
|
+
): Promise<boolean> {
|
|
837
|
+
const replacementId = replacement.card.cardId;
|
|
838
|
+
const prior = await storage.getMemoryById(priorId);
|
|
839
|
+
if (
|
|
840
|
+
!prior ||
|
|
841
|
+
!this.ownsMemory(prior, namespace, principal) ||
|
|
842
|
+
prior.frontmatter.status !== "superseded" ||
|
|
843
|
+
prior.frontmatter.supersededBy !== replacementId
|
|
844
|
+
) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
await this.requireOwnerLock(lock);
|
|
848
|
+
const completed = await storage.supersedeMemory(
|
|
849
|
+
priorId,
|
|
850
|
+
replacementId,
|
|
851
|
+
"support-passport-replacement",
|
|
852
|
+
{ supersessionCause: "direct" },
|
|
853
|
+
{
|
|
854
|
+
actor: principal,
|
|
855
|
+
requireActive: true,
|
|
856
|
+
acceptExactReplay: true,
|
|
857
|
+
expectedSnapshot: prior,
|
|
858
|
+
audit: {
|
|
859
|
+
content: stripAttributesSuffix(prior.content),
|
|
860
|
+
tags: ["supersession", "auto-resolved", "support-passport-audit"],
|
|
861
|
+
structuredAttributes: {
|
|
862
|
+
...encodeSupportPassportNamespaceAttributes(namespace),
|
|
863
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner]: computeSupportPassportOwnerKey(principal),
|
|
864
|
+
},
|
|
865
|
+
source: "support-passport",
|
|
866
|
+
},
|
|
867
|
+
}
|
|
868
|
+
);
|
|
869
|
+
if (!completed) log.warn("support passport could not complete replacement retirement side effects");
|
|
870
|
+
return completed;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
private async recoverReplacedDraft(
|
|
874
|
+
storage: StorageManager,
|
|
875
|
+
replacement: StoredSupportPassportCard,
|
|
876
|
+
lock: HeldFileLockController,
|
|
877
|
+
principal: string,
|
|
878
|
+
namespace: string
|
|
879
|
+
): Promise<void> {
|
|
880
|
+
if (!replacement.replacesDraftId) return;
|
|
881
|
+
const replacedDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
882
|
+
const projectedDraft = replacedDraft ? this.projectOwnedCard(replacedDraft, namespace, principal) : null;
|
|
883
|
+
if (
|
|
884
|
+
replacedDraft &&
|
|
885
|
+
projectedDraft?.card.status === "pending_review" &&
|
|
886
|
+
projectedDraft.card.revision === replacement.replacedRevision
|
|
887
|
+
) {
|
|
888
|
+
await this.requireOwnerLock(lock);
|
|
889
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
890
|
+
replacedDraft,
|
|
891
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
892
|
+
{ actor: principal, reasonCode: "complete-draft-replacement" }
|
|
893
|
+
);
|
|
894
|
+
if (rejected) {
|
|
895
|
+
await this.finishPreparedDraftReplacement(storage, replacement.card.cardId, lock, principal, namespace);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const currentDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
900
|
+
if (
|
|
901
|
+
currentDraft &&
|
|
902
|
+
this.ownsMemory(currentDraft, namespace, principal) &&
|
|
903
|
+
currentDraft.frontmatter.status === "rejected"
|
|
904
|
+
) {
|
|
905
|
+
await this.finishPreparedDraftReplacement(storage, replacement.card.cardId, lock, principal, namespace);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (
|
|
909
|
+
currentDraft &&
|
|
910
|
+
this.ownsMemory(currentDraft, namespace, principal) &&
|
|
911
|
+
currentDraft.frontmatter.status === "pending_review"
|
|
912
|
+
) {
|
|
913
|
+
throw new SupportPassportError("storage_conflict", "The replaced draft changed during recovery.", 409);
|
|
914
|
+
}
|
|
915
|
+
await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
private async finishPreparedDraftReplacement(
|
|
919
|
+
storage: StorageManager,
|
|
920
|
+
replacementId: string,
|
|
921
|
+
lock: HeldFileLockController,
|
|
922
|
+
principal: string,
|
|
923
|
+
namespace: string
|
|
924
|
+
): Promise<SupportPassportCard> {
|
|
925
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
926
|
+
if (!replacement.draftReplacementPrepared) return replacement.card;
|
|
927
|
+
const structuredAttributes = { ...replacement.memory.frontmatter.structuredAttributes };
|
|
928
|
+
delete structuredAttributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared];
|
|
929
|
+
await this.requireOwnerLock(lock);
|
|
930
|
+
const finished = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
931
|
+
replacement.memory,
|
|
932
|
+
{ structuredAttributes },
|
|
933
|
+
{ actor: principal, reasonCode: "draft-replacement-complete" }
|
|
934
|
+
);
|
|
935
|
+
if (finished) {
|
|
936
|
+
return this.projectRequiredCard(
|
|
937
|
+
{ ...replacement.memory, frontmatter: { ...replacement.memory.frontmatter, structuredAttributes } },
|
|
938
|
+
namespace,
|
|
939
|
+
principal
|
|
940
|
+
).card;
|
|
941
|
+
}
|
|
942
|
+
const current = await this.requireCard(storage, replacementId, namespace, principal);
|
|
943
|
+
if (!current.draftReplacementPrepared) return current.card;
|
|
944
|
+
throw new SupportPassportError("storage_conflict", "The support card edit could not be completed.", 409);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private async rejectOrphanedReplacement(
|
|
948
|
+
storage: StorageManager,
|
|
949
|
+
replacementId: string,
|
|
950
|
+
lock: HeldFileLockController,
|
|
951
|
+
principal: string,
|
|
952
|
+
namespace: string
|
|
953
|
+
): Promise<void> {
|
|
954
|
+
const currentReplacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
955
|
+
if (currentReplacement.card.status === "rejected") return;
|
|
956
|
+
this.requireStatus(currentReplacement, "pending_review");
|
|
957
|
+
await this.requireOwnerLock(lock);
|
|
958
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
959
|
+
currentReplacement.memory,
|
|
960
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
961
|
+
{ actor: principal, reasonCode: "replaced-draft-approved" }
|
|
962
|
+
);
|
|
963
|
+
if (rejected) return;
|
|
964
|
+
const latest = await storage.getMemoryById(replacementId);
|
|
965
|
+
if (latest?.frontmatter.status !== "rejected") {
|
|
966
|
+
throw new SupportPassportError("storage_conflict", "The orphaned replacement could not be rejected.", 409);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
private async restorePriorAfterApprovalFailure(
|
|
971
|
+
storage: StorageManager,
|
|
972
|
+
priorId: string,
|
|
973
|
+
replacementId: string,
|
|
974
|
+
lock: HeldFileLockController,
|
|
975
|
+
principal: string,
|
|
976
|
+
namespace: string
|
|
977
|
+
): Promise<void> {
|
|
978
|
+
const replacement = await storage.getMemoryById(replacementId);
|
|
979
|
+
if (replacement && !this.ownsMemory(replacement, namespace, principal)) {
|
|
980
|
+
throw new SupportPassportError("storage_conflict", "The replacement support card changed ownership.", 409);
|
|
981
|
+
}
|
|
982
|
+
if (replacement?.frontmatter.status === "active") return;
|
|
983
|
+
const prior = await storage.getMemoryById(priorId);
|
|
984
|
+
if (prior && !this.ownsMemory(prior, namespace, principal)) {
|
|
985
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed ownership.", 409);
|
|
986
|
+
}
|
|
987
|
+
if (prior?.frontmatter.status === "active" && prior.frontmatter.supersededBy === undefined) return;
|
|
988
|
+
if (prior?.frontmatter.status !== "superseded" || prior.frontmatter.supersededBy !== replacementId) {
|
|
989
|
+
throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
|
|
990
|
+
}
|
|
991
|
+
await this.requireOwnerLock(lock);
|
|
992
|
+
const restored = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
993
|
+
prior,
|
|
994
|
+
{
|
|
995
|
+
status: "active",
|
|
996
|
+
supersededBy: undefined,
|
|
997
|
+
supersededAt: undefined,
|
|
998
|
+
supersessionCause: undefined,
|
|
999
|
+
invalidatedBy: undefined,
|
|
1000
|
+
updated: this.now().toISOString(),
|
|
1001
|
+
},
|
|
1002
|
+
{ actor: principal, reasonCode: "replacement-activation-failed" }
|
|
1003
|
+
);
|
|
1004
|
+
if (restored) return;
|
|
1005
|
+
const currentPrior = await storage.getMemoryById(priorId);
|
|
1006
|
+
if (currentPrior?.frontmatter.status === "active" && currentPrior.frontmatter.supersededBy === undefined) return;
|
|
1007
|
+
throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private async markReplacementComplete(
|
|
1011
|
+
storage: StorageManager,
|
|
1012
|
+
replacementId: string,
|
|
1013
|
+
lock: HeldFileLockController,
|
|
1014
|
+
principal: string,
|
|
1015
|
+
namespace: string
|
|
1016
|
+
): Promise<void> {
|
|
1017
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1018
|
+
if (
|
|
1019
|
+
replacement.memory.frontmatter.structuredAttributes?.[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete] ===
|
|
1020
|
+
"true"
|
|
1021
|
+
) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
await this.requireOwnerLock(lock);
|
|
1025
|
+
const marked = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
1026
|
+
replacement.memory,
|
|
1027
|
+
{
|
|
1028
|
+
structuredAttributes: {
|
|
1029
|
+
...replacement.memory.frontmatter.structuredAttributes,
|
|
1030
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete]: "true",
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
1033
|
+
{ actor: principal, reasonCode: "replacement-side-effects-complete" }
|
|
1034
|
+
);
|
|
1035
|
+
if (!marked) log.warn("support passport could not mark replacement side effects complete");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
private async requireOwnerLock(lock: HeldFileLockController): Promise<void> {
|
|
1039
|
+
if (await lock.refresh()) return;
|
|
1040
|
+
throw new SupportPassportError("storage_conflict", "The support passport lock changed. Try again.", 409);
|
|
1041
|
+
}
|
|
1042
|
+
}
|