@remnic/core 9.54.4 → 9.54.6
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 +24 -23
- package/dist/access-admin-ops-surface.js +55 -50
- package/dist/access-authorization-probe.d.ts +25 -22
- package/dist/access-authorization-probe.js +58 -51
- package/dist/access-boundary.d.ts +26 -23
- package/dist/access-boundary.js +59 -52
- package/dist/access-cli.js +115 -107
- 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 +19 -18
- package/dist/access-extraction-force-flush.js +55 -50
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +22 -21
- package/dist/access-http-lifecycle-flush.d.ts +22 -21
- package/dist/access-http-offline-stream.d.ts +19 -18
- package/dist/access-http-query.js +55 -50
- package/dist/access-http.d.ts +55 -36
- package/dist/access-http.js +76 -65
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +55 -50
- package/dist/access-lcm-surface.d.ts +21 -20
- package/dist/access-lcm-surface.js +55 -50
- package/dist/access-mcp.d.ts +22 -21
- package/dist/access-mcp.js +63 -57
- 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-CCNn_6NE.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +55 -50
- package/dist/access-observe-write-surface.d.ts +22 -21
- package/dist/access-observe-write-surface.js +55 -50
- package/dist/access-offline-manifest.d.ts +10 -9
- package/dist/access-offline-manifest.js +12 -10
- package/dist/access-operations-batch.js +57 -52
- package/dist/access-operations.d.ts +23 -22
- package/dist/access-operations.js +61 -55
- package/dist/access-recall-concurrency.d.ts +21 -20
- package/dist/access-recall-concurrency.js +55 -50
- package/dist/access-recall-response.d.ts +19 -18
- package/dist/access-recall-response.js +55 -50
- package/dist/access-recall-surface.d.ts +19 -18
- package/dist/access-recall-surface.js +55 -50
- package/dist/access-schema.d.ts +2 -2
- package/dist/access-schema.js +5 -4
- package/dist/{access-service-CXvbxLK-.d.ts → access-service-DkSdXK4S.d.ts} +1314 -83
- package/dist/access-service-helpers.d.ts +22 -21
- package/dist/access-service.d.ts +31 -30
- package/dist/access-service.js +55 -50
- package/dist/access-surface-catalog.d.ts +22 -21
- package/dist/access-surface-catalog.js +209 -2
- package/dist/access-surface-catalog.js.map +1 -1
- 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 +14 -1
- package/dist/active-memory-bridge.js +6 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/admin-console/public/what-helps-me/app.js +1594 -0
- package/dist/admin-console/public/what-helps-me/index.html +339 -0
- package/dist/admin-console/public/what-helps-me/model.js +685 -0
- package/dist/admin-console/public/what-helps-me/what-helps-me.css +1296 -0
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/{auto-sync-TDHLZPVP.js → auto-sync-NR3GPCOA.js} +4 -4
- 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 +33 -12
- 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/calibration.js +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-BOOxl-I2.d.ts → catalog-DGFf8mD9.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 +34 -13
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-ZYLC7NFO.js → chunk-26ZXBTYN.js} +4 -4
- package/dist/chunk-26ZXBTYN.js.map +1 -0
- package/dist/{chunk-LLJMAULE.js → chunk-2FKEIPIT.js} +5 -5
- package/dist/{chunk-3CSX3ETE.js → chunk-2LWQSV6A.js} +23 -11
- package/dist/chunk-2LWQSV6A.js.map +1 -0
- package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
- package/dist/chunk-37DZZFFM.js +502 -0
- package/dist/chunk-37DZZFFM.js.map +1 -0
- package/dist/chunk-3HSRWJTC.js +9 -0
- package/dist/chunk-3HSRWJTC.js.map +1 -0
- package/dist/{chunk-XUAMCLY4.js → chunk-3TIZPSJC.js} +17 -13
- package/dist/chunk-3TIZPSJC.js.map +1 -0
- package/dist/{chunk-PDDTI7IC.js → chunk-43SEBY2M.js} +1 -1
- package/dist/{chunk-HWRD23DW.js → chunk-46SZBVDS.js} +3 -3
- package/dist/{chunk-6HPHEFXT.js → chunk-5FD7JMTX.js} +2 -2
- package/dist/{chunk-7BZOBA5J.js → chunk-5NB726GV.js} +70 -16
- package/dist/chunk-5NB726GV.js.map +1 -0
- package/dist/chunk-62OKGIJA.js +39 -0
- package/dist/chunk-62OKGIJA.js.map +1 -0
- package/dist/{chunk-IBKR2TFR.js → chunk-6CYHUWCO.js} +2 -2
- package/dist/{chunk-YN6ASZUG.js → chunk-6QPI2ITJ.js} +5 -5
- package/dist/{chunk-UU3G6GUP.js → chunk-76JJSXK7.js} +1 -1
- package/dist/chunk-76JJSXK7.js.map +1 -0
- package/dist/{chunk-PX2LSHYM.js → chunk-7BD6O7ZV.js} +189 -4
- package/dist/chunk-7BD6O7ZV.js.map +1 -0
- package/dist/chunk-7TBXNYLO.js +346 -0
- package/dist/chunk-7TBXNYLO.js.map +1 -0
- package/dist/{chunk-NH5IR2WM.js → chunk-BRR5EUTF.js} +6 -2
- package/dist/chunk-BRR5EUTF.js.map +1 -0
- package/dist/{chunk-NIS256NW.js → chunk-CIAI24TQ.js} +3 -3
- package/dist/{chunk-T2JHZWH6.js → chunk-D3LBIUWV.js} +11703 -7573
- package/dist/chunk-D3LBIUWV.js.map +1 -0
- package/dist/{chunk-7FGL6CGA.js → chunk-DVYRD4HY.js} +2 -2
- package/dist/{chunk-G7Z6EENO.js → chunk-EKTP7MNS.js} +4 -4
- package/dist/{chunk-S75M5ZRK.js → chunk-F2JRDPON.js} +16 -1
- package/dist/chunk-F2JRDPON.js.map +1 -0
- package/dist/{chunk-RZ4XGKOJ.js → chunk-FFRTNF3L.js} +8 -8
- package/dist/{chunk-VKXWK44Q.js → chunk-FONWBCVY.js} +3 -3
- package/dist/{chunk-DZD576ZU.js → chunk-FPLM2TKD.js} +2 -2
- package/dist/{chunk-ZPJ4QSGP.js → chunk-FTPONF6B.js} +2 -2
- package/dist/{chunk-LJS7KUPV.js → chunk-FWX7W5PM.js} +2 -2
- package/dist/{chunk-UTRO5UJU.js → chunk-G6DFSS6A.js} +5 -1
- package/dist/chunk-G6DFSS6A.js.map +1 -0
- package/dist/{chunk-6LAPLZOS.js → chunk-GJ776BC2.js} +8 -3
- package/dist/chunk-GJ776BC2.js.map +1 -0
- package/dist/{chunk-G4T7L557.js → chunk-GN3WRFJY.js} +6 -6
- package/dist/{chunk-QT2FRYI4.js → chunk-H7NAZ3UB.js} +2 -2
- package/dist/{chunk-APPFNGX6.js → chunk-HBUFQRIM.js} +2 -2
- package/dist/{chunk-UU7TRFNE.js → chunk-HGJ6NJ4E.js} +2 -2
- package/dist/{chunk-WB7JSQCP.js → chunk-HIEWDJ2O.js} +5 -2
- package/dist/{chunk-WB7JSQCP.js.map → chunk-HIEWDJ2O.js.map} +1 -1
- package/dist/{chunk-UES4BHA2.js → chunk-I3MSGFGD.js} +17 -6
- package/dist/chunk-I3MSGFGD.js.map +1 -0
- package/dist/{chunk-C4ZNGVK3.js → chunk-IKP5OYQQ.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
- package/dist/{chunk-RDJRFDJY.js → chunk-IQ4NVWEF.js} +2 -2
- package/dist/{chunk-CGVHPCP2.js → chunk-JYG4OLWH.js} +2 -2
- package/dist/{chunk-WOPA3SGI.js → chunk-K26NWEOC.js} +8 -8
- package/dist/{chunk-K4QS7LXU.js → chunk-K2UGPTYY.js} +485 -44
- package/dist/chunk-K2UGPTYY.js.map +1 -0
- package/dist/{chunk-L6CQ7WIW.js → chunk-KFBOPMF6.js} +11 -11
- package/dist/chunk-KFBOPMF6.js.map +1 -0
- 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-HOIRB77A.js → chunk-L6Z5OAS4.js} +2 -2
- package/dist/chunk-LC2BDILZ.js +57 -0
- package/dist/chunk-LC2BDILZ.js.map +1 -0
- package/dist/{chunk-227UFSEQ.js → chunk-LEPOP46W.js} +4 -4
- package/dist/{chunk-6R4G7N4S.js → chunk-M4YHAODQ.js} +388 -748
- package/dist/chunk-M4YHAODQ.js.map +1 -0
- package/dist/{chunk-3JLPGJ43.js → chunk-MIH3CJPN.js} +5 -5
- package/dist/{chunk-QHFV6VNU.js → chunk-MK5VEWCR.js} +2 -2
- package/dist/chunk-NISZQW72.js +140 -0
- package/dist/chunk-NISZQW72.js.map +1 -0
- package/dist/{chunk-GUKBW4HL.js → chunk-NQMSEB5D.js} +6 -6
- package/dist/{chunk-G2LUMAUF.js → chunk-OCIP6YMD.js} +12 -5
- package/dist/chunk-OCIP6YMD.js.map +1 -0
- package/dist/{chunk-47P3RYZP.js → chunk-PG4QBPVG.js} +21 -11
- package/dist/chunk-PG4QBPVG.js.map +1 -0
- package/dist/{chunk-WRJVB3C2.js → chunk-PO7NXBG2.js} +2 -2
- package/dist/{chunk-QWGE2PUH.js → chunk-PT54WISF.js} +190 -56
- package/dist/chunk-PT54WISF.js.map +1 -0
- package/dist/{chunk-AZM4CCAC.js → chunk-QZLDRQ4I.js} +8 -8
- package/dist/{chunk-THPPCISA.js → chunk-RBII7637.js} +5 -5
- package/dist/{chunk-JDZPNCMA.js → chunk-RUNORG2P.js} +6 -3
- package/dist/chunk-RUNORG2P.js.map +1 -0
- package/dist/{chunk-JV5YUBIZ.js → chunk-RZQ6W2W4.js} +50 -44
- package/dist/chunk-RZQ6W2W4.js.map +1 -0
- package/dist/{chunk-RUMNY5DT.js → chunk-SMNYAMJD.js} +6 -2
- package/dist/chunk-SMNYAMJD.js.map +1 -0
- package/dist/{chunk-MKQBXQKU.js → chunk-TB6CBZ5V.js} +22 -15
- package/dist/chunk-TB6CBZ5V.js.map +1 -0
- package/dist/{chunk-VMMQDEN5.js → chunk-TO7ZTWJW.js} +2 -2
- package/dist/{chunk-K2TDK7GG.js → chunk-TWTUGCTY.js} +2 -2
- package/dist/{chunk-HLYR2TQL.js → chunk-UCTS3DDH.js} +2 -2
- package/dist/{chunk-JYIJNNN3.js → chunk-UG6VXIOK.js} +2 -2
- package/dist/{chunk-WM65LU67.js → chunk-UKYS3NU5.js} +2 -2
- package/dist/{chunk-WDBX6VER.js → chunk-UPLZNJKN.js} +5 -5
- package/dist/{chunk-C23BYTJV.js → chunk-UW5NPZJ2.js} +40 -12
- package/dist/chunk-UW5NPZJ2.js.map +1 -0
- package/dist/{chunk-2GFK22OZ.js → chunk-V6ERBR7U.js} +2 -2
- package/dist/{chunk-CYF6WVE7.js → chunk-W5ZKPI34.js} +139 -158
- package/dist/chunk-W5ZKPI34.js.map +1 -0
- package/dist/chunk-XX4HPFFM.js +370 -0
- package/dist/chunk-XX4HPFFM.js.map +1 -0
- package/dist/{chunk-ZMYDYNI4.js → chunk-XZHE2DEN.js} +4 -4
- package/dist/chunk-YXQEKCZM.js +223 -0
- package/dist/chunk-YXQEKCZM.js.map +1 -0
- package/dist/{chunk-GYPQL6XN.js → chunk-Z4N7SVVF.js} +35 -47
- package/dist/chunk-Z4N7SVVF.js.map +1 -0
- package/dist/{chunk-MAXVH44E.js → chunk-Z54EM5YZ.js} +7 -7
- package/dist/{chunk-YGLY6QFV.js → chunk-ZRZVLB7O.js} +2 -2
- package/dist/{chunk-4IIFL7GC.js → chunk-ZUKIUXHQ.js} +10 -10
- package/dist/{cli-DVUFIa5E.d.ts → cli-Bze8S0Qs.d.ts} +5 -5
- package/dist/cli.d.ts +22 -21
- package/dist/cli.js +90 -80
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +33 -12
- 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 +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +33 -12
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +32 -12
- 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-JDTBXRGW.js +20 -0
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +35 -14
- 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 +25 -24
- 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-CzEfB31P.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 +33 -12
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/explicit-capture.js +7 -4
- package/dist/external-wiki-access.d.ts +22 -21
- package/dist/external-wiki-access.js +56 -51
- 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 +22 -21
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- 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-judge.js +2 -2
- 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 +35 -19
- package/dist/fallback-llm.d.ts +14 -1
- package/dist/fallback-llm.js +1 -1
- package/dist/{first-start-migration-626C5554.js → first-start-migration-CRZCZAJN.js} +11 -3
- package/dist/first-start-migration-CRZCZAJN.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 +314 -308
- package/dist/index.js +304 -133
- package/dist/inline-script.d.ts +3 -0
- package/dist/inline-script.js +8 -0
- package/dist/inline-script.js.map +1 -0
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +6 -6
- 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 +2 -1
- package/dist/local-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +33 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +33 -12
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +34 -13
- package/dist/{maintenance-BJzNIKBE.d.ts → maintenance-C8aRWfhq.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +22 -21
- package/dist/mcp-read-only-tools.js +1 -1
- 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 +24 -23
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +8 -8
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +33 -12
- 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 +26 -25
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +39 -36
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +38 -18
- package/dist/{orchestrator-BiXgBJBQ.d.ts → orchestrator-D85CzDDY.d.ts} +11 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +117 -109
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-sz_dcuLg.d.ts → pipeline-DPcUifCO.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/public-http-D-SyWNj6.d.ts +25 -0
- package/dist/{purge-533FZLS4.js → purge-GB7PZHT2.js} +2 -2
- package/dist/{qmd-eAWObK4M.d.ts → qmd-dFCHp1r0.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-planner-llm.js +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 +5 -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 +38 -38
- package/dist/{scope-profiles-BBQsXuyj.d.ts → scope-profiles-DICK7GtN.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 +6 -6
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +12 -12
- 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-BP7NAX0N.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +34 -13
- package/dist/semantic-rule-promotion.js +33 -12
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +33 -12
- package/dist/{service-DIrtHHEg.d.ts → service-DT-pJyda.d.ts} +660 -660
- 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 +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +31 -15
- package/dist/{storage-DqCzXi56.d.ts → storage-qOnaJ1Lw.d.ts} +49 -13
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +33 -12
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +5 -5
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +518 -0
- package/dist/support-passport/index.js +357 -0
- package/dist/support-passport/index.js.map +1 -0
- 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-EXYSKSVN.js} +8 -2
- package/dist/{tier-stats-2JCZYEF7.js.map → tier-stats-EXYSKSVN.js.map} +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +36 -36
- 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-ed-SvS2t.d.ts} +8 -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 +33 -12
- package/dist/write-envelope.d.ts +1 -1
- package/dist/write-rate-limiter.d.ts +11 -5
- package/dist/write-rate-limiter.js +1 -1
- package/package.json +12 -2
- package/scripts/build-with-heap.mjs +11 -1
- package/src/access-authorization-probe.ts +20 -2
- package/src/access-boundary.ts +32 -0
- package/src/access-http.test.ts +1971 -643
- package/src/access-http.ts +81 -74
- package/src/access-mcp.ts +3 -1
- package/src/access-memory-search-fanout.ts +24 -1
- package/src/access-offline-manifest.ts +5 -0
- package/src/access-operations.ts +2 -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 +628 -884
- package/src/access-surface-catalog.test.ts +40 -10
- package/src/access-surface-catalog.ts +15 -0
- package/src/active-memory-bridge.test.ts +151 -0
- package/src/active-memory-bridge.ts +95 -21
- package/src/briefing.test.ts +38 -0
- package/src/briefing.ts +3 -3
- package/src/cli.ts +6 -1
- package/src/config.ts +18 -9
- 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/fallback-llm.test.ts +54 -0
- package/src/fallback-llm.ts +54 -9
- package/src/index.ts +3 -0
- package/src/inline-script.ts +6 -0
- package/src/local-llm-headers-timeout.test.ts +42 -0
- package/src/local-llm.ts +8 -8
- 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/mcp-read-only-tools.ts +3 -0
- 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/relay/mission-access.test.ts +11 -11
- package/src/relay/mission-ui.test.ts +3 -1
- package/src/storage/supersession-audit.ts +7 -1
- package/src/storage/supersession-side-effects.ts +17 -5
- package/src/storage/tombstone-blocked-capture-sync.ts +31 -0
- package/src/storage-contract/failure-semantics.test.ts +102 -0
- package/src/storage.ts +18 -19
- package/src/support-passport/access-http-base.ts +143 -0
- package/src/support-passport/access-http.ts +172 -0
- package/src/support-passport/access-operations.ts +272 -0
- package/src/support-passport/access-service-base.test.ts +193 -0
- package/src/support-passport/access-service-base.ts +176 -0
- package/src/support-passport/access-surface.test.ts +283 -0
- package/src/support-passport/access-surface.ts +286 -0
- package/src/support-passport/card-projection.test.ts +137 -0
- package/src/support-passport/card-projection.ts +348 -0
- package/src/support-passport/card-replacement.ts +126 -0
- package/src/support-passport/card-service-commit-forwarding.test.ts +160 -0
- package/src/support-passport/card-service.test.ts +2964 -0
- package/src/support-passport/card-service.ts +1184 -0
- package/src/support-passport/card-state.ts +158 -0
- package/src/support-passport/config-model-routing.test.ts +37 -0
- package/src/support-passport/config.test.ts +34 -0
- package/src/support-passport/config.ts +41 -0
- package/src/support-passport/contracts.test.ts +156 -0
- package/src/support-passport/contracts.ts +149 -0
- package/src/support-passport/errors.ts +30 -0
- package/src/support-passport/generated-batch.test.ts +185 -0
- package/src/support-passport/generated-batch.ts +338 -0
- package/src/support-passport/grant-contracts.ts +183 -0
- package/src/support-passport/grant-generated-batch-scope.test.ts +201 -0
- package/src/support-passport/grant-service-cache.test.ts +148 -0
- package/src/support-passport/grant-service-cancellation.test.ts +83 -0
- package/src/support-passport/grant-service-marker-cache.test.ts +62 -0
- package/src/support-passport/grant-service.test.ts +3496 -0
- package/src/support-passport/grant-service.ts +513 -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 +906 -0
- package/src/support-passport/index.ts +17 -0
- package/src/support-passport/mcp-tools.test.ts +222 -0
- package/src/support-passport/mcp-tools.ts +187 -0
- package/src/support-passport/model-adapter.test.ts +840 -0
- package/src/support-passport/model-adapter.ts +602 -0
- package/src/support-passport/model-audit-pinned-write.test.ts +59 -0
- package/src/support-passport/model-audit.ts +152 -0
- package/src/support-passport/model-bridge.test.ts +456 -0
- package/src/support-passport/model-bridge.ts +439 -0
- package/src/support-passport/model-contracts.ts +124 -0
- package/src/support-passport/model-service.test.ts +1731 -0
- package/src/support-passport/model-service.ts +399 -0
- package/src/support-passport/offline-sync-guard.ts +230 -0
- package/src/support-passport/owner-http.test.ts +671 -0
- package/src/support-passport/owner-lock.ts +70 -0
- package/src/support-passport/private-file.test.ts +99 -0
- package/src/support-passport/private-file.ts +498 -0
- package/src/support-passport/public-http.test.ts +476 -0
- package/src/support-passport/public-http.ts +601 -0
- package/src/support-passport/ui.test.ts +181 -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 +8 -1
- package/src/wearables/service.test.ts +13 -0
- package/src/wearables/service.ts +2 -0
- package/src/write-rate-limiter.test.ts +30 -6
- package/src/write-rate-limiter.ts +29 -18
- package/dist/chunk-3CSX3ETE.js.map +0 -1
- package/dist/chunk-47P3RYZP.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-C23BYTJV.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-K4QS7LXU.js.map +0 -1
- package/dist/chunk-L6CQ7WIW.js.map +0 -1
- package/dist/chunk-MKQBXQKU.js.map +0 -1
- package/dist/chunk-NH5IR2WM.js.map +0 -1
- package/dist/chunk-PX2LSHYM.js.map +0 -1
- package/dist/chunk-QWGE2PUH.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-UES4BHA2.js.map +0 -1
- package/dist/chunk-UTRO5UJU.js.map +0 -1
- package/dist/chunk-UU3G6GUP.js.map +0 -1
- package/dist/chunk-XUAMCLY4.js.map +0 -1
- package/dist/chunk-ZYLC7NFO.js.map +0 -1
- package/dist/contradiction-scan-HYLIRMJ2.js +0 -14
- package/dist/first-start-migration-626C5554.js.map +0 -1
- /package/dist/{auto-sync-TDHLZPVP.js.map → auto-sync-NR3GPCOA.js.map} +0 -0
- /package/dist/{chunk-LLJMAULE.js.map → chunk-2FKEIPIT.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-PDDTI7IC.js.map → chunk-43SEBY2M.js.map} +0 -0
- /package/dist/{chunk-HWRD23DW.js.map → chunk-46SZBVDS.js.map} +0 -0
- /package/dist/{chunk-6HPHEFXT.js.map → chunk-5FD7JMTX.js.map} +0 -0
- /package/dist/{chunk-IBKR2TFR.js.map → chunk-6CYHUWCO.js.map} +0 -0
- /package/dist/{chunk-YN6ASZUG.js.map → chunk-6QPI2ITJ.js.map} +0 -0
- /package/dist/{chunk-NIS256NW.js.map → chunk-CIAI24TQ.js.map} +0 -0
- /package/dist/{chunk-7FGL6CGA.js.map → chunk-DVYRD4HY.js.map} +0 -0
- /package/dist/{chunk-G7Z6EENO.js.map → chunk-EKTP7MNS.js.map} +0 -0
- /package/dist/{chunk-RZ4XGKOJ.js.map → chunk-FFRTNF3L.js.map} +0 -0
- /package/dist/{chunk-VKXWK44Q.js.map → chunk-FONWBCVY.js.map} +0 -0
- /package/dist/{chunk-DZD576ZU.js.map → chunk-FPLM2TKD.js.map} +0 -0
- /package/dist/{chunk-ZPJ4QSGP.js.map → chunk-FTPONF6B.js.map} +0 -0
- /package/dist/{chunk-LJS7KUPV.js.map → chunk-FWX7W5PM.js.map} +0 -0
- /package/dist/{chunk-G4T7L557.js.map → chunk-GN3WRFJY.js.map} +0 -0
- /package/dist/{chunk-QT2FRYI4.js.map → chunk-H7NAZ3UB.js.map} +0 -0
- /package/dist/{chunk-APPFNGX6.js.map → chunk-HBUFQRIM.js.map} +0 -0
- /package/dist/{chunk-UU7TRFNE.js.map → chunk-HGJ6NJ4E.js.map} +0 -0
- /package/dist/{chunk-C4ZNGVK3.js.map → chunk-IKP5OYQQ.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-RDJRFDJY.js.map → chunk-IQ4NVWEF.js.map} +0 -0
- /package/dist/{chunk-CGVHPCP2.js.map → chunk-JYG4OLWH.js.map} +0 -0
- /package/dist/{chunk-WOPA3SGI.js.map → chunk-K26NWEOC.js.map} +0 -0
- /package/dist/{chunk-G7A5HLUS.js.map → chunk-KJH6W2I7.js.map} +0 -0
- /package/dist/{chunk-HOIRB77A.js.map → chunk-L6Z5OAS4.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-3JLPGJ43.js.map → chunk-MIH3CJPN.js.map} +0 -0
- /package/dist/{chunk-QHFV6VNU.js.map → chunk-MK5VEWCR.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-AZM4CCAC.js.map → chunk-QZLDRQ4I.js.map} +0 -0
- /package/dist/{chunk-THPPCISA.js.map → chunk-RBII7637.js.map} +0 -0
- /package/dist/{chunk-VMMQDEN5.js.map → chunk-TO7ZTWJW.js.map} +0 -0
- /package/dist/{chunk-K2TDK7GG.js.map → chunk-TWTUGCTY.js.map} +0 -0
- /package/dist/{chunk-HLYR2TQL.js.map → chunk-UCTS3DDH.js.map} +0 -0
- /package/dist/{chunk-JYIJNNN3.js.map → chunk-UG6VXIOK.js.map} +0 -0
- /package/dist/{chunk-WM65LU67.js.map → chunk-UKYS3NU5.js.map} +0 -0
- /package/dist/{chunk-WDBX6VER.js.map → chunk-UPLZNJKN.js.map} +0 -0
- /package/dist/{chunk-2GFK22OZ.js.map → chunk-V6ERBR7U.js.map} +0 -0
- /package/dist/{chunk-ZMYDYNI4.js.map → chunk-XZHE2DEN.js.map} +0 -0
- /package/dist/{chunk-MAXVH44E.js.map → chunk-Z54EM5YZ.js.map} +0 -0
- /package/dist/{chunk-YGLY6QFV.js.map → chunk-ZRZVLB7O.js.map} +0 -0
- /package/dist/{chunk-4IIFL7GC.js.map → chunk-ZUKIUXHQ.js.map} +0 -0
- /package/dist/{contradiction-scan-HYLIRMJ2.js.map → contradiction-scan-JDTBXRGW.js.map} +0 -0
- /package/dist/{purge-533FZLS4.js.map → purge-GB7PZHT2.js.map} +0 -0
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { StorageManager } from "../index.js";
|
|
4
|
+
import { log } from "../logger.js";
|
|
5
|
+
import type { MemoryFile } from "../types.js";
|
|
6
|
+
import type { HeldFileLockController } from "../utils/serialize-mutations.js";
|
|
7
|
+
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
8
|
+
import {
|
|
9
|
+
SUPPORT_PASSPORT_ATTRIBUTE_KEYS,
|
|
10
|
+
SUPPORT_PASSPORT_CARD_TAG,
|
|
11
|
+
type StoredSupportPassportCard,
|
|
12
|
+
computeSupportPassportOwnerKey,
|
|
13
|
+
encodeSupportPassportNamespaceAttributes,
|
|
14
|
+
} from "./card-projection.js";
|
|
15
|
+
import {
|
|
16
|
+
completeSupportPassportReplacementPrior,
|
|
17
|
+
prepareSupportPassportReplacementPrior,
|
|
18
|
+
validateSupportPassportReplacementPrior,
|
|
19
|
+
} from "./card-replacement.js";
|
|
20
|
+
import {
|
|
21
|
+
MAX_OWNER_VISIBLE_CARDS,
|
|
22
|
+
type SupportPassportCardServiceDependencies,
|
|
23
|
+
type SupportPassportOwnerScope,
|
|
24
|
+
invalidInput,
|
|
25
|
+
isStorageConflict,
|
|
26
|
+
once,
|
|
27
|
+
ownerListCards,
|
|
28
|
+
ownerVisibleCards,
|
|
29
|
+
ownsMemory,
|
|
30
|
+
projectOwnedCard,
|
|
31
|
+
projectRequiredCard,
|
|
32
|
+
requireRevision,
|
|
33
|
+
requireStatus,
|
|
34
|
+
revisionFor,
|
|
35
|
+
validateOwnerScope,
|
|
36
|
+
} from "./card-state.js";
|
|
37
|
+
import {
|
|
38
|
+
type SupportPassportCard,
|
|
39
|
+
type SupportPassportCardCategory,
|
|
40
|
+
SupportPassportCardListSchema,
|
|
41
|
+
type SupportPassportCardMutationInput,
|
|
42
|
+
SupportPassportCardMutationInputSchema,
|
|
43
|
+
SupportPassportListCardsInputSchema,
|
|
44
|
+
type SupportPassportManualDraftInput,
|
|
45
|
+
SupportPassportManualDraftInputSchema,
|
|
46
|
+
type SupportPassportReplaceCardInput,
|
|
47
|
+
SupportPassportReplaceCardInputSchema,
|
|
48
|
+
} from "./contracts.js";
|
|
49
|
+
import { SupportPassportError } from "./errors.js";
|
|
50
|
+
import {
|
|
51
|
+
type GeneratedBatchMarker,
|
|
52
|
+
commitSupportPassportGeneratedBatch,
|
|
53
|
+
isCommittedGeneratedCard,
|
|
54
|
+
persistSupportPassportGeneratedBatchMarker,
|
|
55
|
+
projectCommittedSupportPassportCards,
|
|
56
|
+
readCommittedSupportPassportCards,
|
|
57
|
+
recoverSupportPassportGeneratedBatches,
|
|
58
|
+
rollbackSupportPassportGeneratedBatch,
|
|
59
|
+
} from "./generated-batch.js";
|
|
60
|
+
import { type SupportPassportDraftCard, SupportPassportDraftOutputSchema } from "./model-contracts.js";
|
|
61
|
+
import { withSupportPassportOwnerLock } from "./owner-lock.js";
|
|
62
|
+
export type { SupportPassportCardServiceDependencies, SupportPassportOwnerScope } from "./card-state.js";
|
|
63
|
+
export class SupportPassportCardService {
|
|
64
|
+
private readonly resolveOwner: SupportPassportCardServiceDependencies["resolveOwner"];
|
|
65
|
+
private readonly now: () => Date;
|
|
66
|
+
constructor(dependencies: SupportPassportCardServiceDependencies) {
|
|
67
|
+
this.resolveOwner = dependencies.resolveOwner;
|
|
68
|
+
this.now = dependencies.now ?? (() => new Date());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async listCards(input: {
|
|
72
|
+
principal: string;
|
|
73
|
+
}): Promise<SupportPassportCard[]> {
|
|
74
|
+
const parsed = SupportPassportListCardsInputSchema.safeParse(input);
|
|
75
|
+
if (!parsed.success) throw invalidInput();
|
|
76
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
77
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async () => {
|
|
78
|
+
const memories = await storage.readAllMemories();
|
|
79
|
+
const stored = await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
|
|
80
|
+
const cards = ownerListCards(stored)
|
|
81
|
+
.sort((a, b) => a.order - b.order || a.card.cardId.localeCompare(b.card.cardId))
|
|
82
|
+
.map((item) => item.card);
|
|
83
|
+
const output = SupportPassportCardListSchema.safeParse(cards);
|
|
84
|
+
if (!output.success) {
|
|
85
|
+
throw new SupportPassportError("card_data_invalid", "The support card data is invalid.", 500);
|
|
86
|
+
}
|
|
87
|
+
return output.data;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async createManualDraft(
|
|
92
|
+
input: SupportPassportManualDraftInput,
|
|
93
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
94
|
+
): Promise<SupportPassportCard> {
|
|
95
|
+
const parsed = SupportPassportManualDraftInputSchema.safeParse(input);
|
|
96
|
+
if (!parsed.success) throw invalidInput();
|
|
97
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
98
|
+
const onCommitted = once(options.onCommitted);
|
|
99
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
100
|
+
options.signal?.throwIfAborted();
|
|
101
|
+
const created = await this.createDraft(
|
|
102
|
+
storage,
|
|
103
|
+
{
|
|
104
|
+
title: parsed.data.title,
|
|
105
|
+
statement: parsed.data.statement,
|
|
106
|
+
category: parsed.data.category,
|
|
107
|
+
reviewBy: parsed.data.reviewBy,
|
|
108
|
+
sourceMemoryIds: [],
|
|
109
|
+
onCommitted,
|
|
110
|
+
},
|
|
111
|
+
lock,
|
|
112
|
+
principal,
|
|
113
|
+
namespace
|
|
114
|
+
);
|
|
115
|
+
if (!options.signal?.aborted) return created;
|
|
116
|
+
if (!(await this.rejectGeneratedDraft(storage, created.cardId, lock, principal, namespace))) {
|
|
117
|
+
throw new SupportPassportError("storage_conflict", "A cancelled support card could not be rolled back.", 500);
|
|
118
|
+
}
|
|
119
|
+
options.signal.throwIfAborted();
|
|
120
|
+
return created;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async createGeneratedDrafts(input: {
|
|
125
|
+
principal: string;
|
|
126
|
+
cards: SupportPassportDraftCard[];
|
|
127
|
+
}): Promise<SupportPassportCard[]> {
|
|
128
|
+
const principal = SupportPassportListCardsInputSchema.safeParse({
|
|
129
|
+
principal: input.principal,
|
|
130
|
+
});
|
|
131
|
+
if (!principal.success) throw invalidInput();
|
|
132
|
+
const owner = await this.resolveOwnerScope(principal.data.principal);
|
|
133
|
+
return await this.createGeneratedDraftsForOwner({
|
|
134
|
+
authenticatedPrincipal: principal.data.principal,
|
|
135
|
+
owner,
|
|
136
|
+
cards: input.cards,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async createGeneratedDraftsForOwner(input: {
|
|
141
|
+
authenticatedPrincipal: string;
|
|
142
|
+
owner: SupportPassportOwnerScope;
|
|
143
|
+
cards: SupportPassportDraftCard[];
|
|
144
|
+
signal?: AbortSignal;
|
|
145
|
+
onCommitted?: () => void;
|
|
146
|
+
commitWithValidatedSources?: (commit: () => Promise<void>) => Promise<void>;
|
|
147
|
+
}): Promise<SupportPassportCard[]> {
|
|
148
|
+
const output = SupportPassportDraftOutputSchema.safeParse({
|
|
149
|
+
cards: input.cards,
|
|
150
|
+
});
|
|
151
|
+
if (!output.success) throw invalidInput();
|
|
152
|
+
const owner = validateOwnerScope(input.owner, input.authenticatedPrincipal);
|
|
153
|
+
const { principal, namespace, storage } = owner;
|
|
154
|
+
const onCommitted = once(input.onCommitted);
|
|
155
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
156
|
+
const batchContext = this.generatedBatchContext(storage, lock, principal, namespace, onCommitted);
|
|
157
|
+
const batchId = randomUUID();
|
|
158
|
+
const created: SupportPassportCard[] = [];
|
|
159
|
+
const createdIds: string[] = [];
|
|
160
|
+
const createdRecords: StoredSupportPassportCard[] = [];
|
|
161
|
+
let marker: GeneratedBatchMarker | null = null;
|
|
162
|
+
try {
|
|
163
|
+
input.signal?.throwIfAborted();
|
|
164
|
+
input.signal?.throwIfAborted();
|
|
165
|
+
const storedCards = await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
|
|
166
|
+
if (ownerVisibleCards(storedCards).length + output.data.cards.length > MAX_OWNER_VISIBLE_CARDS) {
|
|
167
|
+
throw new SupportPassportError(
|
|
168
|
+
"invalid_input",
|
|
169
|
+
"A support passport can contain at most 100 visible cards.",
|
|
170
|
+
400
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
let nextOrder = storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
|
|
174
|
+
marker = await persistSupportPassportGeneratedBatchMarker(batchContext, batchId, output.data.cards.length);
|
|
175
|
+
for (const card of output.data.cards) {
|
|
176
|
+
input.signal?.throwIfAborted();
|
|
177
|
+
created.push(
|
|
178
|
+
await this.persistDraft(
|
|
179
|
+
storage,
|
|
180
|
+
{
|
|
181
|
+
title: card.title,
|
|
182
|
+
statement: card.statement,
|
|
183
|
+
category: card.category,
|
|
184
|
+
sourceMemoryIds: card.sourceMemoryIds,
|
|
185
|
+
order: nextOrder,
|
|
186
|
+
generatedBatch: { batchId, size: output.data.cards.length },
|
|
187
|
+
onPersisted: (record) => {
|
|
188
|
+
createdIds.push(record.card.cardId);
|
|
189
|
+
createdRecords.push(record);
|
|
190
|
+
onCommitted();
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
lock,
|
|
194
|
+
principal,
|
|
195
|
+
namespace
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
nextOrder += 1;
|
|
199
|
+
}
|
|
200
|
+
input.signal?.throwIfAborted();
|
|
201
|
+
const commit = async () =>
|
|
202
|
+
await commitSupportPassportGeneratedBatch(batchContext, marker!, createdRecords);
|
|
203
|
+
if (input.commitWithValidatedSources) await input.commitWithValidatedSources(commit);
|
|
204
|
+
else await commit();
|
|
205
|
+
return created;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!marker) throw error;
|
|
208
|
+
if (
|
|
209
|
+
!(await rollbackSupportPassportGeneratedBatch(batchContext, batchId, createdIds))
|
|
210
|
+
) {
|
|
211
|
+
throw new SupportPassportError("storage_conflict", "A generated draft batch could not be rolled back.", 500);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async replaceCard(
|
|
219
|
+
input: SupportPassportReplaceCardInput,
|
|
220
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
221
|
+
): Promise<SupportPassportCard> {
|
|
222
|
+
const parsed = SupportPassportReplaceCardInputSchema.safeParse(input);
|
|
223
|
+
if (!parsed.success) throw invalidInput();
|
|
224
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
225
|
+
const onCommitted = once(options.onCommitted);
|
|
226
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
227
|
+
options.signal?.throwIfAborted();
|
|
228
|
+
const loadedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
229
|
+
const storedCards = await readCommittedSupportPassportCards(storage, namespace, principal);
|
|
230
|
+
const interruptedReplacements = storedCards.filter(
|
|
231
|
+
(item) =>
|
|
232
|
+
item.card.status === "pending_review" &&
|
|
233
|
+
(item.replacesDraftId === loadedPrior.card.cardId ||
|
|
234
|
+
item.memory.frontmatter.supersedes === loadedPrior.card.cardId)
|
|
235
|
+
);
|
|
236
|
+
if (interruptedReplacements.length > 1) {
|
|
237
|
+
throw new SupportPassportError(
|
|
238
|
+
"storage_conflict",
|
|
239
|
+
"Multiple support card edits already completed. Reload the support passport.",
|
|
240
|
+
409
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
const [interruptedReplacement] = interruptedReplacements;
|
|
244
|
+
if (interruptedReplacement) {
|
|
245
|
+
if (interruptedReplacement.replacedRevision !== parsed.data.expectedRevision) {
|
|
246
|
+
throw new SupportPassportError("revision_conflict", "The support card changed after it was loaded.", 409);
|
|
247
|
+
}
|
|
248
|
+
const matchesRetry =
|
|
249
|
+
interruptedReplacement.card.title === parsed.data.title &&
|
|
250
|
+
interruptedReplacement.card.statement === parsed.data.statement &&
|
|
251
|
+
interruptedReplacement.card.category === parsed.data.category &&
|
|
252
|
+
interruptedReplacement.card.reviewBy === parsed.data.reviewBy;
|
|
253
|
+
if (!matchesRetry) {
|
|
254
|
+
throw new SupportPassportError(
|
|
255
|
+
"storage_conflict",
|
|
256
|
+
"A different support card edit already completed. Reload the support passport.",
|
|
257
|
+
409
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (!interruptedReplacement.draftReplacementPrepared) return interruptedReplacement.card;
|
|
261
|
+
const recovered = await this.recoverReplacementTransition(
|
|
262
|
+
storage,
|
|
263
|
+
interruptedReplacement.memory,
|
|
264
|
+
lock,
|
|
265
|
+
principal,
|
|
266
|
+
namespace,
|
|
267
|
+
{},
|
|
268
|
+
onCommitted
|
|
269
|
+
);
|
|
270
|
+
return projectRequiredCard(recovered, namespace, principal).card;
|
|
271
|
+
}
|
|
272
|
+
requireRevision(loadedPrior, parsed.data.expectedRevision);
|
|
273
|
+
await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
|
|
274
|
+
const refreshedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
275
|
+
requireRevision(refreshedPrior, parsed.data.expectedRevision);
|
|
276
|
+
const recoveredMemory = await this.recoverReplacementTransition(
|
|
277
|
+
storage,
|
|
278
|
+
refreshedPrior.memory,
|
|
279
|
+
lock,
|
|
280
|
+
principal,
|
|
281
|
+
namespace,
|
|
282
|
+
{},
|
|
283
|
+
onCommitted
|
|
284
|
+
);
|
|
285
|
+
const prior = projectRequiredCard(recoveredMemory, namespace, principal);
|
|
286
|
+
if (prior.card.status !== "active" && prior.card.status !== "pending_review") {
|
|
287
|
+
throw new SupportPassportError(
|
|
288
|
+
"invalid_card_status",
|
|
289
|
+
"Only a draft or approved support card can be edited.",
|
|
290
|
+
409
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const replacement = await this.createDraft(
|
|
294
|
+
storage,
|
|
295
|
+
{
|
|
296
|
+
title: parsed.data.title,
|
|
297
|
+
statement: parsed.data.statement,
|
|
298
|
+
category: parsed.data.category,
|
|
299
|
+
reviewBy: parsed.data.reviewBy,
|
|
300
|
+
sourceMemoryIds: prior.sourceMemoryIds,
|
|
301
|
+
supersedes: prior.card.status === "active" ? prior.card.cardId : prior.memory.frontmatter.supersedes,
|
|
302
|
+
replacesDraftId: prior.card.status === "pending_review" ? prior.card.cardId : undefined,
|
|
303
|
+
replacedRevision: prior.card.revision,
|
|
304
|
+
order: prior.order,
|
|
305
|
+
draftReplacementPrepared: prior.card.status === "pending_review",
|
|
306
|
+
onCommitted,
|
|
307
|
+
},
|
|
308
|
+
lock,
|
|
309
|
+
principal,
|
|
310
|
+
namespace
|
|
311
|
+
);
|
|
312
|
+
if (options.signal?.aborted) {
|
|
313
|
+
if (!(await this.rejectGeneratedDraft(storage, replacement.cardId, lock, principal, namespace))) {
|
|
314
|
+
throw new SupportPassportError(
|
|
315
|
+
"storage_conflict",
|
|
316
|
+
"A cancelled support card edit could not be rolled back.",
|
|
317
|
+
500
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
options.signal.throwIfAborted();
|
|
321
|
+
}
|
|
322
|
+
if (prior.card.status === "active") return replacement;
|
|
323
|
+
|
|
324
|
+
const replacedAt = this.now().toISOString();
|
|
325
|
+
await this.requireOwnerLock(lock);
|
|
326
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
327
|
+
prior.memory,
|
|
328
|
+
{ status: "rejected", updated: replacedAt },
|
|
329
|
+
{ actor: principal, reasonCode: "owner-replaced-draft" }
|
|
330
|
+
);
|
|
331
|
+
if (rejected) {
|
|
332
|
+
onCommitted();
|
|
333
|
+
return await this.finishPreparedDraftReplacement(
|
|
334
|
+
storage,
|
|
335
|
+
replacement.cardId,
|
|
336
|
+
lock,
|
|
337
|
+
principal,
|
|
338
|
+
namespace,
|
|
339
|
+
onCommitted
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const currentPrior = await storage.getMemoryById(prior.card.cardId);
|
|
343
|
+
const currentPriorCard = currentPrior ? projectOwnedCard(currentPrior, namespace, principal) : null;
|
|
344
|
+
if (currentPriorCard?.card.status === "rejected") {
|
|
345
|
+
return await this.finishPreparedDraftReplacement(
|
|
346
|
+
storage,
|
|
347
|
+
replacement.cardId,
|
|
348
|
+
lock,
|
|
349
|
+
principal,
|
|
350
|
+
namespace,
|
|
351
|
+
onCommitted
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
await this.rejectCreatedDraft(
|
|
356
|
+
storage,
|
|
357
|
+
replacement.cardId,
|
|
358
|
+
"draft-replacement-failed",
|
|
359
|
+
lock,
|
|
360
|
+
principal,
|
|
361
|
+
namespace,
|
|
362
|
+
onCommitted
|
|
363
|
+
);
|
|
364
|
+
throw new SupportPassportError("storage_conflict", "The support card changed before it was edited.", 409);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async approveCard(
|
|
369
|
+
input: SupportPassportCardMutationInput,
|
|
370
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
371
|
+
): Promise<SupportPassportCard> {
|
|
372
|
+
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
373
|
+
if (!parsed.success) throw invalidInput();
|
|
374
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
375
|
+
const onCommitted = once(options.onCommitted);
|
|
376
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
377
|
+
options.signal?.throwIfAborted();
|
|
378
|
+
const loadedCard = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
379
|
+
requireRevision(loadedCard, parsed.data.expectedRevision);
|
|
380
|
+
requireStatus(loadedCard, "pending_review");
|
|
381
|
+
const recoveredMemory = await this.recoverReplacementTransition(
|
|
382
|
+
storage,
|
|
383
|
+
loadedCard.memory,
|
|
384
|
+
lock,
|
|
385
|
+
principal,
|
|
386
|
+
namespace,
|
|
387
|
+
{},
|
|
388
|
+
onCommitted
|
|
389
|
+
);
|
|
390
|
+
const card = projectRequiredCard(recoveredMemory, namespace, principal);
|
|
391
|
+
if (card.card.status === "active") return card.card;
|
|
392
|
+
if (card.card.status === "rejected") {
|
|
393
|
+
throw new SupportPassportError(
|
|
394
|
+
"storage_conflict",
|
|
395
|
+
"The support card was rejected while its replacement state was recovered.",
|
|
396
|
+
409
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
requireStatus(card, "pending_review");
|
|
400
|
+
await validateSupportPassportReplacementPrior(storage, card, namespace, principal);
|
|
401
|
+
const updatedAt = this.now().toISOString();
|
|
402
|
+
await this.requireOwnerLock(lock);
|
|
403
|
+
let approved: boolean;
|
|
404
|
+
try {
|
|
405
|
+
approved = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
406
|
+
card.memory,
|
|
407
|
+
{ status: "active", updated: updatedAt },
|
|
408
|
+
{
|
|
409
|
+
actor: principal,
|
|
410
|
+
reasonCode: parsed.data.reasonCode ?? "owner-approved",
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
const current = await storage.getMemoryById(card.card.cardId);
|
|
415
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
416
|
+
if (currentCard?.card.status === "active") {
|
|
417
|
+
onCommitted();
|
|
418
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
419
|
+
}
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
if (!approved) {
|
|
423
|
+
const current = await storage.getMemoryById(card.card.cardId);
|
|
424
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
425
|
+
if (currentCard?.card.status === "active") {
|
|
426
|
+
onCommitted();
|
|
427
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
428
|
+
}
|
|
429
|
+
throw new SupportPassportError("storage_conflict", "The support card changed before approval.", 409);
|
|
430
|
+
}
|
|
431
|
+
const committedCard: SupportPassportCard = {
|
|
432
|
+
...card.card,
|
|
433
|
+
status: "active",
|
|
434
|
+
updatedAt,
|
|
435
|
+
revision: revisionFor(card.card, "active", updatedAt),
|
|
436
|
+
};
|
|
437
|
+
onCommitted();
|
|
438
|
+
try {
|
|
439
|
+
const current = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
440
|
+
return await this.finishCommittedApproval(storage, current, lock, principal, namespace, onCommitted);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
if (isStorageConflict(error)) throw error;
|
|
443
|
+
log.warn(
|
|
444
|
+
`support passport could not finish replacement approval side effects: ${error instanceof Error ? error.message : String(error)}`
|
|
445
|
+
);
|
|
446
|
+
return committedCard;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async rejectCard(
|
|
452
|
+
input: SupportPassportCardMutationInput,
|
|
453
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
454
|
+
): Promise<SupportPassportCard> {
|
|
455
|
+
return await this.changeStatus(input, "pending_review", "rejected", "owner-rejected", options);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async withdrawCard(
|
|
459
|
+
input: SupportPassportCardMutationInput,
|
|
460
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
461
|
+
): Promise<SupportPassportCard> {
|
|
462
|
+
return await this.changeStatus(input, "active", "archived", "owner-withdrew", options);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private async createDraft(
|
|
466
|
+
storage: StorageManager,
|
|
467
|
+
input: {
|
|
468
|
+
title: string;
|
|
469
|
+
statement: string;
|
|
470
|
+
category: SupportPassportCardCategory;
|
|
471
|
+
reviewBy?: string;
|
|
472
|
+
sourceMemoryIds: string[];
|
|
473
|
+
supersedes?: string;
|
|
474
|
+
replacesDraftId?: string;
|
|
475
|
+
replacedRevision?: string;
|
|
476
|
+
order?: number;
|
|
477
|
+
draftReplacementPrepared?: boolean;
|
|
478
|
+
generatedBatch?: { batchId: string; size: number };
|
|
479
|
+
onPersisted?: (card: StoredSupportPassportCard) => void;
|
|
480
|
+
onCommitted?: () => void;
|
|
481
|
+
},
|
|
482
|
+
lock: HeldFileLockController,
|
|
483
|
+
principal: string,
|
|
484
|
+
namespace: string
|
|
485
|
+
): Promise<SupportPassportCard> {
|
|
486
|
+
const now = this.now();
|
|
487
|
+
const reviewBy = input.reviewBy ?? now.toISOString();
|
|
488
|
+
const storedCards = await this.readStoredCards(storage, lock, principal, namespace, input.onCommitted);
|
|
489
|
+
const visibleCards = ownerListCards(storedCards);
|
|
490
|
+
const replacesVisibleCard = visibleCards.some(
|
|
491
|
+
(item) =>
|
|
492
|
+
(input.replacesDraftId === item.card.cardId && item.card.status === "pending_review") ||
|
|
493
|
+
(input.supersedes === item.card.cardId && item.card.status === "active")
|
|
494
|
+
);
|
|
495
|
+
if (visibleCards.length - (replacesVisibleCard ? 1 : 0) >= MAX_OWNER_VISIBLE_CARDS) {
|
|
496
|
+
throw new SupportPassportError("invalid_input", "A support passport can contain at most 100 visible cards.", 400);
|
|
497
|
+
}
|
|
498
|
+
const order = input.order ?? storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
|
|
499
|
+
return await this.persistDraft(storage, { ...input, order }, lock, principal, namespace);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private async persistDraft(
|
|
503
|
+
storage: StorageManager,
|
|
504
|
+
input: {
|
|
505
|
+
title: string;
|
|
506
|
+
statement: string;
|
|
507
|
+
category: SupportPassportCardCategory;
|
|
508
|
+
reviewBy?: string;
|
|
509
|
+
sourceMemoryIds: string[];
|
|
510
|
+
supersedes?: string;
|
|
511
|
+
replacesDraftId?: string;
|
|
512
|
+
replacedRevision?: string;
|
|
513
|
+
order: number;
|
|
514
|
+
draftReplacementPrepared?: boolean;
|
|
515
|
+
generatedBatch?: { batchId: string; size: number };
|
|
516
|
+
onPersisted?: (card: StoredSupportPassportCard) => void;
|
|
517
|
+
onCommitted?: () => void;
|
|
518
|
+
},
|
|
519
|
+
lock: HeldFileLockController,
|
|
520
|
+
principal: string,
|
|
521
|
+
namespace: string
|
|
522
|
+
): Promise<SupportPassportCard> {
|
|
523
|
+
const now = this.now();
|
|
524
|
+
const reviewBy = input.reviewBy ?? now.toISOString();
|
|
525
|
+
const order = input.order;
|
|
526
|
+
if (!Number.isSafeInteger(order)) {
|
|
527
|
+
throw new SupportPassportError("storage_conflict", "The support card order range is exhausted.", 409);
|
|
528
|
+
}
|
|
529
|
+
const envelope = composeMemoryEnvelope(
|
|
530
|
+
{
|
|
531
|
+
content: input.statement,
|
|
532
|
+
category: "preference",
|
|
533
|
+
tags: [SUPPORT_PASSPORT_CARD_TAG],
|
|
534
|
+
structuredAttributes: {
|
|
535
|
+
...encodeSupportPassportNamespaceAttributes(namespace),
|
|
536
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner]: computeSupportPassportOwnerKey(principal),
|
|
537
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.title]: input.title,
|
|
538
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.category]: input.category,
|
|
539
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.order]: String(order),
|
|
540
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.reviewBy]: reviewBy,
|
|
541
|
+
...(input.replacesDraftId
|
|
542
|
+
? {
|
|
543
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacesDraftId]: input.replacesDraftId,
|
|
544
|
+
}
|
|
545
|
+
: {}),
|
|
546
|
+
...(input.replacedRevision
|
|
547
|
+
? {
|
|
548
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacedRevision]: input.replacedRevision,
|
|
549
|
+
}
|
|
550
|
+
: {}),
|
|
551
|
+
...(input.draftReplacementPrepared
|
|
552
|
+
? {
|
|
553
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared]: "true",
|
|
554
|
+
}
|
|
555
|
+
: {}),
|
|
556
|
+
...(input.generatedBatch
|
|
557
|
+
? {
|
|
558
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchId]: input.generatedBatch.batchId,
|
|
559
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchSize]: String(input.generatedBatch.size),
|
|
560
|
+
}
|
|
561
|
+
: {}),
|
|
562
|
+
},
|
|
563
|
+
sourceReason: "support-passport",
|
|
564
|
+
},
|
|
565
|
+
{ source: "support-passport", now: this.now }
|
|
566
|
+
);
|
|
567
|
+
if (envelope.sanitizeViolations.length > 0) throw invalidInput();
|
|
568
|
+
await this.requireOwnerLock(lock);
|
|
569
|
+
const written = await storage.writeSealedMemory(envelope, {
|
|
570
|
+
actor: principal,
|
|
571
|
+
status: "pending_review",
|
|
572
|
+
lineage: input.sourceMemoryIds.length > 0 ? input.sourceMemoryIds : undefined,
|
|
573
|
+
supersedes: input.supersedes,
|
|
574
|
+
});
|
|
575
|
+
if (written.tombstoneBlocked) {
|
|
576
|
+
throw new SupportPassportError("storage_conflict", "The support card needs memory review before use.", 409);
|
|
577
|
+
}
|
|
578
|
+
const stored = projectRequiredCard(written.memory, namespace, principal);
|
|
579
|
+
input.onPersisted?.(stored);
|
|
580
|
+
if (!input.generatedBatch) input.onCommitted?.();
|
|
581
|
+
return stored.card;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private async changeStatus(
|
|
585
|
+
input: SupportPassportCardMutationInput,
|
|
586
|
+
expectedStatus: "pending_review" | "active",
|
|
587
|
+
nextStatus: "rejected" | "archived",
|
|
588
|
+
defaultReasonCode: string,
|
|
589
|
+
options: { signal?: AbortSignal; onCommitted?: () => void }
|
|
590
|
+
): Promise<SupportPassportCard> {
|
|
591
|
+
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
592
|
+
if (!parsed.success) throw invalidInput();
|
|
593
|
+
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
594
|
+
const onCommitted = once(options.onCommitted);
|
|
595
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
596
|
+
options.signal?.throwIfAborted();
|
|
597
|
+
let card = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
598
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
599
|
+
requireStatus(card, expectedStatus);
|
|
600
|
+
if (expectedStatus === "pending_review" && nextStatus === "rejected") {
|
|
601
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
|
|
602
|
+
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
603
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
604
|
+
requireStatus(card, expectedStatus);
|
|
605
|
+
}
|
|
606
|
+
if (
|
|
607
|
+
expectedStatus === "pending_review" ||
|
|
608
|
+
(expectedStatus === "active" && (card.replacesDraftId || card.memory.frontmatter.supersedes))
|
|
609
|
+
) {
|
|
610
|
+
card = projectRequiredCard(
|
|
611
|
+
await this.recoverReplacementTransition(
|
|
612
|
+
storage,
|
|
613
|
+
card.memory,
|
|
614
|
+
lock,
|
|
615
|
+
principal,
|
|
616
|
+
namespace,
|
|
617
|
+
{
|
|
618
|
+
rollbackConflictedApproval: expectedStatus !== "active",
|
|
619
|
+
},
|
|
620
|
+
onCommitted
|
|
621
|
+
),
|
|
622
|
+
namespace,
|
|
623
|
+
principal
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
if (card.card.status === nextStatus) return card.card;
|
|
627
|
+
requireStatus(card, expectedStatus);
|
|
628
|
+
if (expectedStatus === "active" && nextStatus === "archived") {
|
|
629
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
|
|
630
|
+
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
631
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
632
|
+
requireStatus(card, expectedStatus);
|
|
633
|
+
}
|
|
634
|
+
const updatedAt = this.now().toISOString();
|
|
635
|
+
await this.requireOwnerLock(lock);
|
|
636
|
+
const changed = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
637
|
+
card.memory,
|
|
638
|
+
{
|
|
639
|
+
status: nextStatus,
|
|
640
|
+
updated: updatedAt,
|
|
641
|
+
...(nextStatus === "archived" ? { archivedAt: updatedAt } : {}),
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
actor: principal,
|
|
645
|
+
reasonCode: parsed.data.reasonCode ?? defaultReasonCode,
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
if (!changed)
|
|
649
|
+
throw new SupportPassportError(
|
|
650
|
+
"storage_conflict",
|
|
651
|
+
"The support card changed before the request completed.",
|
|
652
|
+
409
|
|
653
|
+
);
|
|
654
|
+
onCommitted();
|
|
655
|
+
return {
|
|
656
|
+
...card.card,
|
|
657
|
+
status: nextStatus,
|
|
658
|
+
updatedAt,
|
|
659
|
+
revision: revisionFor(card.card, nextStatus, updatedAt),
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private async resolveOwnerScope(principal: string): Promise<SupportPassportOwnerScope> {
|
|
665
|
+
const owner = await this.resolveOwner(principal);
|
|
666
|
+
return validateOwnerScope(owner, principal);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private async rejectCreatedDraft(
|
|
670
|
+
storage: StorageManager,
|
|
671
|
+
cardId: string,
|
|
672
|
+
reasonCode: string,
|
|
673
|
+
lock: HeldFileLockController,
|
|
674
|
+
principal: string,
|
|
675
|
+
namespace: string,
|
|
676
|
+
onCommitted?: () => void
|
|
677
|
+
): Promise<void> {
|
|
678
|
+
try {
|
|
679
|
+
const current = await this.requireCard(storage, cardId, namespace, principal);
|
|
680
|
+
await this.requireOwnerLock(lock);
|
|
681
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
682
|
+
current.memory,
|
|
683
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
684
|
+
{ actor: principal, reasonCode }
|
|
685
|
+
);
|
|
686
|
+
if (rejected) onCommitted?.();
|
|
687
|
+
else log.warn("support passport could not roll back a replacement draft");
|
|
688
|
+
} catch (error) {
|
|
689
|
+
log.warn(
|
|
690
|
+
`support passport could not roll back a replacement draft: ${error instanceof Error ? error.message : String(error)}`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private async rejectGeneratedDraft(
|
|
696
|
+
storage: StorageManager,
|
|
697
|
+
cardId: string,
|
|
698
|
+
lock: HeldFileLockController,
|
|
699
|
+
principal: string,
|
|
700
|
+
namespace: string
|
|
701
|
+
): Promise<boolean> {
|
|
702
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
703
|
+
const memory = await storage.getMemoryById(cardId);
|
|
704
|
+
if (!memory) return true;
|
|
705
|
+
const stored = projectOwnedCard(memory, namespace, principal);
|
|
706
|
+
if (!stored) return false;
|
|
707
|
+
if (stored.card.status === "rejected") return true;
|
|
708
|
+
if (stored.card.status !== "pending_review") return false;
|
|
709
|
+
await this.requireOwnerLock(lock);
|
|
710
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
711
|
+
memory,
|
|
712
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
713
|
+
{ actor: principal, reasonCode: "draft-batch-failed" }
|
|
714
|
+
);
|
|
715
|
+
if (rejected) return true;
|
|
716
|
+
}
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
private async readStoredCards(
|
|
721
|
+
storage: StorageManager,
|
|
722
|
+
lock: HeldFileLockController,
|
|
723
|
+
principal: string,
|
|
724
|
+
namespace: string,
|
|
725
|
+
onCommitted?: () => void
|
|
726
|
+
): Promise<StoredSupportPassportCard[]> {
|
|
727
|
+
const initialVersion = storage.getCorpusScanVersion();
|
|
728
|
+
const initial = await storage.readAllMemories();
|
|
729
|
+
await recoverSupportPassportGeneratedBatches(
|
|
730
|
+
this.generatedBatchContext(storage, lock, principal, namespace, onCommitted),
|
|
731
|
+
initial
|
|
732
|
+
);
|
|
733
|
+
for (const memory of initial) {
|
|
734
|
+
const card = projectOwnedCard(memory, namespace, principal);
|
|
735
|
+
if (card) {
|
|
736
|
+
await this.recoverReplacementTransition(storage, memory, lock, principal, namespace, {}, onCommitted);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const memories: MemoryFile[] =
|
|
740
|
+
storage.getCorpusScanVersion() === initialVersion ? initial : await storage.readAllMemories();
|
|
741
|
+
return await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private generatedBatchContext(
|
|
745
|
+
storage: StorageManager,
|
|
746
|
+
lock: HeldFileLockController,
|
|
747
|
+
principal: string,
|
|
748
|
+
namespace: string,
|
|
749
|
+
onCommitted?: () => void
|
|
750
|
+
) {
|
|
751
|
+
return {
|
|
752
|
+
storage,
|
|
753
|
+
principal,
|
|
754
|
+
namespace,
|
|
755
|
+
now: this.now,
|
|
756
|
+
requireOwnerLock: async () => await this.requireOwnerLock(lock),
|
|
757
|
+
...(onCommitted ? { onCommitted } : {}),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
private async rejectPendingReplacementForPredecessor(
|
|
762
|
+
storage: StorageManager,
|
|
763
|
+
predecessor: StoredSupportPassportCard,
|
|
764
|
+
lock: HeldFileLockController,
|
|
765
|
+
principal: string,
|
|
766
|
+
namespace: string,
|
|
767
|
+
onCommitted?: () => void
|
|
768
|
+
): Promise<void> {
|
|
769
|
+
const storedCards =
|
|
770
|
+
predecessor.card.status === "active"
|
|
771
|
+
? await this.readStoredCards(storage, lock, principal, namespace, onCommitted)
|
|
772
|
+
: await readCommittedSupportPassportCards(storage, namespace, principal);
|
|
773
|
+
const replacements = storedCards.filter((item) => {
|
|
774
|
+
if (item.card.status !== "pending_review") return false;
|
|
775
|
+
return predecessor.card.status === "pending_review"
|
|
776
|
+
? item.replacesDraftId === predecessor.card.cardId
|
|
777
|
+
: item.memory.frontmatter.supersedes === predecessor.card.cardId;
|
|
778
|
+
});
|
|
779
|
+
if (replacements.length > 1) {
|
|
780
|
+
throw new SupportPassportError(
|
|
781
|
+
"storage_conflict",
|
|
782
|
+
"Multiple support card edits target the card being withdrawn.",
|
|
783
|
+
409
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
const [replacement] = replacements;
|
|
787
|
+
if (!replacement) return;
|
|
788
|
+
await this.requireOwnerLock(lock);
|
|
789
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
790
|
+
replacement.memory,
|
|
791
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
792
|
+
{
|
|
793
|
+
actor: principal,
|
|
794
|
+
reasonCode: predecessor.card.status === "pending_review" ? "source-draft-rejected" : "predecessor-withdrawn",
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
if (rejected) {
|
|
798
|
+
onCommitted?.();
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const current = await storage.getMemoryById(replacement.card.cardId);
|
|
802
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
803
|
+
if (currentCard?.card.status !== "rejected") {
|
|
804
|
+
throw new SupportPassportError("storage_conflict", "The pending support card edit could not be cancelled.", 409);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
private async recoverReplacementTransition(
|
|
809
|
+
storage: StorageManager,
|
|
810
|
+
memory: MemoryFile,
|
|
811
|
+
lock: HeldFileLockController,
|
|
812
|
+
principal: string,
|
|
813
|
+
namespace: string,
|
|
814
|
+
options: { rollbackConflictedApproval?: boolean } = {},
|
|
815
|
+
onCommitted?: () => void
|
|
816
|
+
): Promise<MemoryFile> {
|
|
817
|
+
const replacement = projectOwnedCard(memory, namespace, principal);
|
|
818
|
+
if (replacement?.card.status !== "pending_review" && replacement?.card.status !== "active") return memory;
|
|
819
|
+
if (!replacement.replacesDraftId && !memory.frontmatter.supersedes) return memory;
|
|
820
|
+
if (
|
|
821
|
+
replacement.card.status === "active" &&
|
|
822
|
+
memory.frontmatter.structuredAttributes?.[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete] === "true"
|
|
823
|
+
) {
|
|
824
|
+
return memory;
|
|
825
|
+
}
|
|
826
|
+
await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace, onCommitted);
|
|
827
|
+
const currentMemory = (await storage.getMemoryById(replacement.card.cardId)) ?? memory;
|
|
828
|
+
const currentCard = projectOwnedCard(currentMemory, namespace, principal);
|
|
829
|
+
if (currentCard?.card.status !== "pending_review" && currentCard?.card.status !== "active") {
|
|
830
|
+
return currentMemory;
|
|
831
|
+
}
|
|
832
|
+
if (currentCard.card.status === "pending_review") {
|
|
833
|
+
const priorId = currentMemory.frontmatter.supersedes;
|
|
834
|
+
if (!priorId) return currentMemory;
|
|
835
|
+
const prior = await storage.getMemoryById(priorId);
|
|
836
|
+
if (
|
|
837
|
+
prior &&
|
|
838
|
+
ownsMemory(prior, namespace, principal) &&
|
|
839
|
+
prior.frontmatter.status === "superseded" &&
|
|
840
|
+
prior.frontmatter.supersededBy === replacement.card.cardId
|
|
841
|
+
) {
|
|
842
|
+
await this.restorePriorAfterApprovalFailure(
|
|
843
|
+
storage,
|
|
844
|
+
priorId,
|
|
845
|
+
replacement.card.cardId,
|
|
846
|
+
lock,
|
|
847
|
+
principal,
|
|
848
|
+
namespace,
|
|
849
|
+
onCommitted
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
856
|
+
} catch (error) {
|
|
857
|
+
if (!isStorageConflict(error)) throw error;
|
|
858
|
+
if (options.rollbackConflictedApproval === false) throw error;
|
|
859
|
+
await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace, onCommitted);
|
|
860
|
+
}
|
|
861
|
+
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private async completeReplacementAfterActivation(
|
|
865
|
+
storage: StorageManager,
|
|
866
|
+
replacement: StoredSupportPassportCard,
|
|
867
|
+
lock: HeldFileLockController,
|
|
868
|
+
principal: string,
|
|
869
|
+
namespace: string,
|
|
870
|
+
onCommitted?: () => void
|
|
871
|
+
): Promise<void> {
|
|
872
|
+
const priorId = await prepareSupportPassportReplacementPrior({
|
|
873
|
+
storage,
|
|
874
|
+
replacement,
|
|
875
|
+
principal,
|
|
876
|
+
namespace,
|
|
877
|
+
now: this.now,
|
|
878
|
+
requireOwnerLock: () => this.requireOwnerLock(lock),
|
|
879
|
+
onCommitted,
|
|
880
|
+
});
|
|
881
|
+
if (
|
|
882
|
+
priorId &&
|
|
883
|
+
!(await completeSupportPassportReplacementPrior({
|
|
884
|
+
storage,
|
|
885
|
+
priorId,
|
|
886
|
+
replacement,
|
|
887
|
+
principal,
|
|
888
|
+
namespace,
|
|
889
|
+
requireOwnerLock: () => this.requireOwnerLock(lock),
|
|
890
|
+
}))
|
|
891
|
+
) {
|
|
892
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
893
|
+
}
|
|
894
|
+
if (priorId) onCommitted?.();
|
|
895
|
+
await this.markReplacementComplete(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private async finishCommittedApproval(
|
|
899
|
+
storage: StorageManager,
|
|
900
|
+
replacement: StoredSupportPassportCard,
|
|
901
|
+
lock: HeldFileLockController,
|
|
902
|
+
principal: string,
|
|
903
|
+
namespace: string,
|
|
904
|
+
onCommitted?: () => void
|
|
905
|
+
): Promise<SupportPassportCard> {
|
|
906
|
+
try {
|
|
907
|
+
if (replacement.replacesDraftId || replacement.memory.frontmatter.supersedes) {
|
|
908
|
+
await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace, onCommitted);
|
|
909
|
+
}
|
|
910
|
+
} catch (error) {
|
|
911
|
+
if (isStorageConflict(error)) {
|
|
912
|
+
await this.rollbackConflictedApproval(
|
|
913
|
+
storage,
|
|
914
|
+
replacement.card.cardId,
|
|
915
|
+
lock,
|
|
916
|
+
principal,
|
|
917
|
+
namespace,
|
|
918
|
+
onCommitted
|
|
919
|
+
);
|
|
920
|
+
throw error;
|
|
921
|
+
}
|
|
922
|
+
log.warn(
|
|
923
|
+
`support passport could not finish replacement approval side effects: ${error instanceof Error ? error.message : String(error)}`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return replacement.card;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private async rollbackConflictedApproval(
|
|
930
|
+
storage: StorageManager,
|
|
931
|
+
replacementId: string,
|
|
932
|
+
lock: HeldFileLockController,
|
|
933
|
+
principal: string,
|
|
934
|
+
namespace: string,
|
|
935
|
+
onCommitted?: () => void
|
|
936
|
+
): Promise<void> {
|
|
937
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
938
|
+
if (replacement.card.status !== "active") return;
|
|
939
|
+
await this.requireOwnerLock(lock);
|
|
940
|
+
const rolledBack = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
941
|
+
replacement.memory,
|
|
942
|
+
{ status: "pending_review", updated: this.now().toISOString() },
|
|
943
|
+
{ actor: principal, reasonCode: "replacement-predecessor-conflict" }
|
|
944
|
+
);
|
|
945
|
+
if (!rolledBack) {
|
|
946
|
+
throw new SupportPassportError(
|
|
947
|
+
"storage_conflict",
|
|
948
|
+
"The replacement changed while its approval was rolled back.",
|
|
949
|
+
409
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
onCommitted?.();
|
|
953
|
+
const priorId = replacement.memory.frontmatter.supersedes;
|
|
954
|
+
if (priorId) {
|
|
955
|
+
await this.restorePriorAfterApprovalFailure(
|
|
956
|
+
storage,
|
|
957
|
+
priorId,
|
|
958
|
+
replacementId,
|
|
959
|
+
lock,
|
|
960
|
+
principal,
|
|
961
|
+
namespace,
|
|
962
|
+
onCommitted
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private async recoverReplacedDraft(
|
|
968
|
+
storage: StorageManager,
|
|
969
|
+
replacement: StoredSupportPassportCard,
|
|
970
|
+
lock: HeldFileLockController,
|
|
971
|
+
principal: string,
|
|
972
|
+
namespace: string,
|
|
973
|
+
onCommitted?: () => void
|
|
974
|
+
): Promise<void> {
|
|
975
|
+
if (!replacement.replacesDraftId) return;
|
|
976
|
+
const replacedDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
977
|
+
const projectedDraft = replacedDraft ? projectOwnedCard(replacedDraft, namespace, principal) : null;
|
|
978
|
+
if (
|
|
979
|
+
replacedDraft &&
|
|
980
|
+
projectedDraft?.card.status === "pending_review" &&
|
|
981
|
+
projectedDraft.card.revision === replacement.replacedRevision
|
|
982
|
+
) {
|
|
983
|
+
await this.requireOwnerLock(lock);
|
|
984
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
985
|
+
replacedDraft,
|
|
986
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
987
|
+
{ actor: principal, reasonCode: "complete-draft-replacement" }
|
|
988
|
+
);
|
|
989
|
+
if (rejected) {
|
|
990
|
+
onCommitted?.();
|
|
991
|
+
await this.finishPreparedDraftReplacement(
|
|
992
|
+
storage,
|
|
993
|
+
replacement.card.cardId,
|
|
994
|
+
lock,
|
|
995
|
+
principal,
|
|
996
|
+
namespace,
|
|
997
|
+
onCommitted
|
|
998
|
+
);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const currentDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
1003
|
+
if (
|
|
1004
|
+
currentDraft &&
|
|
1005
|
+
ownsMemory(currentDraft, namespace, principal) &&
|
|
1006
|
+
currentDraft.frontmatter.status === "rejected"
|
|
1007
|
+
) {
|
|
1008
|
+
await this.finishPreparedDraftReplacement(
|
|
1009
|
+
storage,
|
|
1010
|
+
replacement.card.cardId,
|
|
1011
|
+
lock,
|
|
1012
|
+
principal,
|
|
1013
|
+
namespace,
|
|
1014
|
+
onCommitted
|
|
1015
|
+
);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (
|
|
1019
|
+
currentDraft &&
|
|
1020
|
+
ownsMemory(currentDraft, namespace, principal) &&
|
|
1021
|
+
currentDraft.frontmatter.status === "pending_review"
|
|
1022
|
+
) {
|
|
1023
|
+
throw new SupportPassportError("storage_conflict", "The replaced draft changed during recovery.", 409);
|
|
1024
|
+
}
|
|
1025
|
+
await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
private async finishPreparedDraftReplacement(
|
|
1029
|
+
storage: StorageManager,
|
|
1030
|
+
replacementId: string,
|
|
1031
|
+
lock: HeldFileLockController,
|
|
1032
|
+
principal: string,
|
|
1033
|
+
namespace: string,
|
|
1034
|
+
onCommitted?: () => void
|
|
1035
|
+
): Promise<SupportPassportCard> {
|
|
1036
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1037
|
+
if (!replacement.draftReplacementPrepared) return replacement.card;
|
|
1038
|
+
const structuredAttributes = {
|
|
1039
|
+
...replacement.memory.frontmatter.structuredAttributes,
|
|
1040
|
+
};
|
|
1041
|
+
delete structuredAttributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared];
|
|
1042
|
+
await this.requireOwnerLock(lock);
|
|
1043
|
+
const finished = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
1044
|
+
replacement.memory,
|
|
1045
|
+
{ structuredAttributes },
|
|
1046
|
+
{ actor: principal, reasonCode: "draft-replacement-complete" }
|
|
1047
|
+
);
|
|
1048
|
+
if (finished) {
|
|
1049
|
+
onCommitted?.();
|
|
1050
|
+
return projectRequiredCard(
|
|
1051
|
+
{
|
|
1052
|
+
...replacement.memory,
|
|
1053
|
+
frontmatter: {
|
|
1054
|
+
...replacement.memory.frontmatter,
|
|
1055
|
+
structuredAttributes,
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
namespace,
|
|
1059
|
+
principal
|
|
1060
|
+
).card;
|
|
1061
|
+
}
|
|
1062
|
+
const current = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1063
|
+
if (!current.draftReplacementPrepared) return current.card;
|
|
1064
|
+
throw new SupportPassportError("storage_conflict", "The support card edit could not be completed.", 409);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
private async rejectOrphanedReplacement(
|
|
1068
|
+
storage: StorageManager,
|
|
1069
|
+
replacementId: string,
|
|
1070
|
+
lock: HeldFileLockController,
|
|
1071
|
+
principal: string,
|
|
1072
|
+
namespace: string,
|
|
1073
|
+
onCommitted?: () => void
|
|
1074
|
+
): Promise<void> {
|
|
1075
|
+
const currentReplacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1076
|
+
if (currentReplacement.card.status === "rejected") return;
|
|
1077
|
+
requireStatus(currentReplacement, "pending_review");
|
|
1078
|
+
await this.requireOwnerLock(lock);
|
|
1079
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
1080
|
+
currentReplacement.memory,
|
|
1081
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
1082
|
+
{ actor: principal, reasonCode: "replaced-draft-approved" }
|
|
1083
|
+
);
|
|
1084
|
+
if (rejected) {
|
|
1085
|
+
onCommitted?.();
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const latest = await storage.getMemoryById(replacementId);
|
|
1089
|
+
if (latest?.frontmatter.status !== "rejected") {
|
|
1090
|
+
throw new SupportPassportError("storage_conflict", "The orphaned replacement could not be rejected.", 409);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
private async restorePriorAfterApprovalFailure(
|
|
1095
|
+
storage: StorageManager,
|
|
1096
|
+
priorId: string,
|
|
1097
|
+
replacementId: string,
|
|
1098
|
+
lock: HeldFileLockController,
|
|
1099
|
+
principal: string,
|
|
1100
|
+
namespace: string,
|
|
1101
|
+
onCommitted?: () => void
|
|
1102
|
+
): Promise<void> {
|
|
1103
|
+
const replacement = await storage.getMemoryById(replacementId);
|
|
1104
|
+
if (replacement && !ownsMemory(replacement, namespace, principal)) {
|
|
1105
|
+
throw new SupportPassportError("storage_conflict", "The replacement support card changed ownership.", 409);
|
|
1106
|
+
}
|
|
1107
|
+
if (replacement?.frontmatter.status === "active") return;
|
|
1108
|
+
const prior = await storage.getMemoryById(priorId);
|
|
1109
|
+
if (prior && !ownsMemory(prior, namespace, principal)) {
|
|
1110
|
+
throw new SupportPassportError("storage_conflict", "The prior support card changed ownership.", 409);
|
|
1111
|
+
}
|
|
1112
|
+
if (prior?.frontmatter.status === "active" && prior.frontmatter.supersededBy === undefined) return;
|
|
1113
|
+
if (prior?.frontmatter.status !== "superseded" || prior.frontmatter.supersededBy !== replacementId) {
|
|
1114
|
+
throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
|
|
1115
|
+
}
|
|
1116
|
+
await this.requireOwnerLock(lock);
|
|
1117
|
+
const restored = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
1118
|
+
prior,
|
|
1119
|
+
{
|
|
1120
|
+
status: "active",
|
|
1121
|
+
supersededBy: undefined,
|
|
1122
|
+
supersededAt: undefined,
|
|
1123
|
+
supersessionCause: undefined,
|
|
1124
|
+
invalidatedBy: undefined,
|
|
1125
|
+
updated: this.now().toISOString(),
|
|
1126
|
+
},
|
|
1127
|
+
{ actor: principal, reasonCode: "replacement-activation-failed" }
|
|
1128
|
+
);
|
|
1129
|
+
if (restored) {
|
|
1130
|
+
onCommitted?.();
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const currentPrior = await storage.getMemoryById(priorId);
|
|
1134
|
+
if (currentPrior?.frontmatter.status === "active" && currentPrior.frontmatter.supersededBy === undefined) return;
|
|
1135
|
+
throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private async markReplacementComplete(
|
|
1139
|
+
storage: StorageManager,
|
|
1140
|
+
replacementId: string,
|
|
1141
|
+
lock: HeldFileLockController,
|
|
1142
|
+
principal: string,
|
|
1143
|
+
namespace: string,
|
|
1144
|
+
onCommitted?: () => void
|
|
1145
|
+
): Promise<void> {
|
|
1146
|
+
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1147
|
+
if (
|
|
1148
|
+
replacement.memory.frontmatter.structuredAttributes?.[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete] ===
|
|
1149
|
+
"true"
|
|
1150
|
+
) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
await this.requireOwnerLock(lock);
|
|
1154
|
+
const marked = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
1155
|
+
replacement.memory,
|
|
1156
|
+
{
|
|
1157
|
+
structuredAttributes: {
|
|
1158
|
+
...replacement.memory.frontmatter.structuredAttributes,
|
|
1159
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacementComplete]: "true",
|
|
1160
|
+
},
|
|
1161
|
+
},
|
|
1162
|
+
{ actor: principal, reasonCode: "replacement-side-effects-complete" }
|
|
1163
|
+
);
|
|
1164
|
+
if (marked) onCommitted?.();
|
|
1165
|
+
else log.warn("support passport could not mark replacement side effects complete");
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private async requireOwnerLock(lock: HeldFileLockController): Promise<void> {
|
|
1169
|
+
if (await lock.refresh()) return;
|
|
1170
|
+
throw new SupportPassportError("storage_conflict", "The support passport lock changed. Try again.", 409);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private async requireCard(
|
|
1174
|
+
storage: StorageManager,
|
|
1175
|
+
cardId: string,
|
|
1176
|
+
namespace: string,
|
|
1177
|
+
principal: string
|
|
1178
|
+
): Promise<StoredSupportPassportCard> {
|
|
1179
|
+
const memory = await storage.getMemoryById(cardId);
|
|
1180
|
+
const card = memory ? projectOwnedCard(memory, namespace, principal) : null;
|
|
1181
|
+
if (card && (await isCommittedGeneratedCard(storage, card))) return card;
|
|
1182
|
+
throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
|
|
1183
|
+
}
|
|
1184
|
+
}
|