@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,906 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { type FileHandle, lstat, open, readdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { log } from "../logger.js";
|
|
6
|
+
import { expandTildePath } from "../utils/path.js";
|
|
7
|
+
import { type HeldFileLockController, serializeMutations, withHeldFileLock } from "../utils/serialize-mutations.js";
|
|
8
|
+
import { computeSupportPassportOwnerKey } from "./card-projection.js";
|
|
9
|
+
import { SupportPassportNamespaceSchema } from "./contracts.js";
|
|
10
|
+
import { SupportPassportError } from "./errors.js";
|
|
11
|
+
import {
|
|
12
|
+
SupportPassportCreateGrantInputSchema,
|
|
13
|
+
type SupportPassportGrantCardRef,
|
|
14
|
+
type SupportPassportGrantState,
|
|
15
|
+
SupportPassportGrantStateSchema,
|
|
16
|
+
} from "./grant-contracts.js";
|
|
17
|
+
import { computeSupportPassportOwnerLockKey } from "./owner-lock.js";
|
|
18
|
+
import {
|
|
19
|
+
ensurePrivateDirectoryNoFollow,
|
|
20
|
+
ensurePrivateDirectoryTreeNoFollow,
|
|
21
|
+
readPrivateFileNoFollow,
|
|
22
|
+
removePrivateFilesNoFollow,
|
|
23
|
+
withPrivateDirectoryNoFollow,
|
|
24
|
+
writePrivateFileAtomicallyNoFollow,
|
|
25
|
+
} from "./private-file.js";
|
|
26
|
+
|
|
27
|
+
const GRANT_LOCK_STALE_MS = 30_000;
|
|
28
|
+
const GRANT_LOCK_WAIT_MS = 5_000;
|
|
29
|
+
const GRANT_LOCK_HEARTBEAT_MS = 10_000;
|
|
30
|
+
const SAFE_GRANT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
31
|
+
const UUID_INPUT = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
32
|
+
const SAFE_OWNER_INDEX_HASH = /^[0-9a-f]{64}$/;
|
|
33
|
+
const UNSUPPORTED_DIRECTORY_SYNC_ERRORS = new Set(["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"]);
|
|
34
|
+
const MAX_OWNER_GRANT_HISTORY = 100;
|
|
35
|
+
const MAX_OWNER_INDEX_RECOVERY_ATTEMPTS = 3;
|
|
36
|
+
|
|
37
|
+
class OwnerIndexLockLostError extends Error {
|
|
38
|
+
constructor(cause: unknown) {
|
|
39
|
+
super("support passport owner index lock was lost", { cause });
|
|
40
|
+
this.name = "OwnerIndexLockLostError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class MalformedGrantStateError extends Error {
|
|
45
|
+
constructor(message = "support passport grant state is invalid", cause?: unknown) {
|
|
46
|
+
super(message, { cause });
|
|
47
|
+
this.name = "MalformedGrantStateError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SupportPassportGrantStoreOptions {
|
|
52
|
+
memoryDir: string;
|
|
53
|
+
now?: () => Date;
|
|
54
|
+
makeSecret?: () => Buffer;
|
|
55
|
+
makeGrantId?: () => string;
|
|
56
|
+
withHeldFileLock?: typeof withHeldFileLock;
|
|
57
|
+
syncDirectory?: typeof syncDirectoryForDurability;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CreateStoredGrantInput {
|
|
61
|
+
namespace: string;
|
|
62
|
+
principal: string;
|
|
63
|
+
cards: SupportPassportGrantCardRef[];
|
|
64
|
+
expiresAt: string;
|
|
65
|
+
requestedAt?: Date;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SupportPassportGrantMutationHooks {
|
|
69
|
+
beforeCommit?: () => Promise<void>;
|
|
70
|
+
onCommitted?: () => void | Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type SupportPassportGrantCreateHooks = SupportPassportGrantMutationHooks | (() => Promise<void>);
|
|
74
|
+
type SupportPassportGrantRevokeHooks = SupportPassportGrantMutationHooks | (() => Promise<void>);
|
|
75
|
+
|
|
76
|
+
function sha256(domain: string, value: string): string {
|
|
77
|
+
return createHash("sha256").update(domain).update("\0").update(value).digest("hex");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hashesMatch(left: string, right: string): boolean {
|
|
81
|
+
const leftBytes = Buffer.from(left, "hex");
|
|
82
|
+
const rightBytes = Buffer.from(right, "hex");
|
|
83
|
+
return leftBytes.length === 32 && rightBytes.length === 32 && timingSafeEqual(leftBytes, rightBytes);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sameGrantState(left: SupportPassportGrantState, right: SupportPassportGrantState): boolean {
|
|
87
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function newestGrantFirst(left: SupportPassportGrantState, right: SupportPassportGrantState): number {
|
|
91
|
+
return Date.parse(right.createdAt) - Date.parse(left.createdAt) || left.grantId.localeCompare(right.grantId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function grantNotFound(): SupportPassportError {
|
|
95
|
+
return new SupportPassportError("grant_not_found", "The share link was not found.", 404);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function warnCommitNotificationFailure(error: unknown): void {
|
|
99
|
+
log.warn(`support passport commit notification failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function notifySupportPassportCommitted(callback: (() => void | Promise<void>) | undefined): void {
|
|
103
|
+
try {
|
|
104
|
+
const completion = callback?.();
|
|
105
|
+
if (completion) void Promise.resolve(completion).catch(warnCommitNotificationFailure);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
warnCommitNotificationFailure(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeGrantId(grantId: unknown): string {
|
|
112
|
+
if (typeof grantId !== "string" || !UUID_INPUT.test(grantId)) throw grantNotFound();
|
|
113
|
+
return grantId.toLowerCase();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeNamespace(namespace: unknown): string {
|
|
117
|
+
const parsed = SupportPassportNamespaceSchema.safeParse(namespace);
|
|
118
|
+
if (!parsed.success) {
|
|
119
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
120
|
+
}
|
|
121
|
+
return parsed.data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizePrincipal(principal: unknown): string {
|
|
125
|
+
const normalized = typeof principal === "string" ? principal.trim() : "";
|
|
126
|
+
if (normalized.length < 1 || normalized.length > 512) {
|
|
127
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
128
|
+
}
|
|
129
|
+
return normalized;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function syncDirectoryForDurability(
|
|
133
|
+
directory: string,
|
|
134
|
+
openDirectory: (directory: string) => Promise<Pick<FileHandle, "sync" | "close">> = async (target) =>
|
|
135
|
+
await open(target, "r")
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
let handle: Pick<FileHandle, "sync" | "close"> | undefined;
|
|
138
|
+
try {
|
|
139
|
+
handle = await openDirectory(directory);
|
|
140
|
+
await handle.sync();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
143
|
+
if (!UNSUPPORTED_DIRECTORY_SYNC_ERRORS.has(code ?? "")) throw error;
|
|
144
|
+
} finally {
|
|
145
|
+
await handle?.close().catch(() => undefined);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class SupportPassportGrantStore {
|
|
150
|
+
private memoryDir: string;
|
|
151
|
+
private grantsDir: string;
|
|
152
|
+
private ownerIndexesDir: string;
|
|
153
|
+
private memoryRootReady?: Promise<void>;
|
|
154
|
+
private safeDirectoriesReady?: Promise<void>;
|
|
155
|
+
private readonly now: () => Date;
|
|
156
|
+
private readonly makeSecret: () => Buffer;
|
|
157
|
+
private readonly makeGrantId: () => string;
|
|
158
|
+
private readonly runWithHeldFileLock: typeof withHeldFileLock;
|
|
159
|
+
private readonly syncDirectory: typeof syncDirectoryForDurability;
|
|
160
|
+
|
|
161
|
+
constructor(options: SupportPassportGrantStoreOptions) {
|
|
162
|
+
this.memoryDir = path.resolve(expandTildePath(options.memoryDir));
|
|
163
|
+
this.grantsDir = path.join(this.memoryDir, "state", "support-passport", "grants");
|
|
164
|
+
this.ownerIndexesDir = path.join(this.grantsDir, "owners");
|
|
165
|
+
this.now = options.now ?? (() => new Date());
|
|
166
|
+
this.makeSecret = options.makeSecret ?? (() => randomBytes(32));
|
|
167
|
+
this.makeGrantId = options.makeGrantId ?? randomUUID;
|
|
168
|
+
this.runWithHeldFileLock = options.withHeldFileLock ?? withHeldFileLock;
|
|
169
|
+
this.syncDirectory = options.syncDirectory ?? syncDirectoryForDurability;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async create(
|
|
173
|
+
input: CreateStoredGrantInput,
|
|
174
|
+
hooks: SupportPassportGrantCreateHooks = {}
|
|
175
|
+
): Promise<{ state: SupportPassportGrantState; secret: string }> {
|
|
176
|
+
const mutationHooks = typeof hooks === "function" ? { beforeCommit: hooks } : hooks;
|
|
177
|
+
const namespace = normalizeNamespace(input.namespace);
|
|
178
|
+
const parsed = SupportPassportCreateGrantInputSchema.safeParse({
|
|
179
|
+
principal: input.principal,
|
|
180
|
+
cards: input.cards,
|
|
181
|
+
expiresAt: input.expiresAt,
|
|
182
|
+
});
|
|
183
|
+
if (!parsed.success) {
|
|
184
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
185
|
+
}
|
|
186
|
+
const expiresAt = parsed.data.expiresAt;
|
|
187
|
+
if (expiresAt === undefined) {
|
|
188
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
189
|
+
}
|
|
190
|
+
const requestedAt = input.requestedAt ?? this.now();
|
|
191
|
+
const durationMs = Date.parse(expiresAt) - requestedAt.getTime();
|
|
192
|
+
if (
|
|
193
|
+
!Number.isFinite(requestedAt.getTime()) ||
|
|
194
|
+
!Number.isFinite(durationMs) ||
|
|
195
|
+
durationMs < 300_000 ||
|
|
196
|
+
durationMs > 604_800_000
|
|
197
|
+
) {
|
|
198
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
199
|
+
}
|
|
200
|
+
const principalHash = computeSupportPassportOwnerKey(parsed.data.principal);
|
|
201
|
+
const ownerHash = this.ownerHash(namespace, principalHash);
|
|
202
|
+
let committed: { state: SupportPassportGrantState; secret: string } | undefined;
|
|
203
|
+
try {
|
|
204
|
+
let finalized: { state: SupportPassportGrantState; secret: string };
|
|
205
|
+
try {
|
|
206
|
+
finalized = await this.withOwnerIndexLock(ownerHash, async (lock) => {
|
|
207
|
+
await this.requireAvailableOwnerGrantSlot(namespace, principalHash);
|
|
208
|
+
committed = await this.withMutationLock(async (mutationLock) => {
|
|
209
|
+
const secretBytes = this.makeSecret();
|
|
210
|
+
if (!Buffer.isBuffer(secretBytes) || secretBytes.length !== 32) {
|
|
211
|
+
throw new Error("SupportPassportGrantStore.makeSecret must return 32 bytes");
|
|
212
|
+
}
|
|
213
|
+
const rawGrantId = this.makeGrantId();
|
|
214
|
+
if (!UUID_INPUT.test(rawGrantId)) {
|
|
215
|
+
throw new Error("SupportPassportGrantStore.makeGrantId must return a UUID");
|
|
216
|
+
}
|
|
217
|
+
const grantId = rawGrantId.toLowerCase();
|
|
218
|
+
const secret = secretBytes.toString("base64url");
|
|
219
|
+
const createdAt = this.now();
|
|
220
|
+
const createdAtMs = createdAt.getTime();
|
|
221
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
222
|
+
if (
|
|
223
|
+
!Number.isFinite(createdAtMs) ||
|
|
224
|
+
requestedAt.getTime() > createdAtMs ||
|
|
225
|
+
expiresAtMs <= createdAtMs ||
|
|
226
|
+
expiresAtMs - createdAtMs > 604_800_000
|
|
227
|
+
) {
|
|
228
|
+
throw new SupportPassportError("invalid_input", "The share link request is invalid.", 400);
|
|
229
|
+
}
|
|
230
|
+
const state = SupportPassportGrantStateSchema.parse({
|
|
231
|
+
schemaVersion: 1,
|
|
232
|
+
stateVersion: 1,
|
|
233
|
+
grantId,
|
|
234
|
+
namespace,
|
|
235
|
+
principalHash,
|
|
236
|
+
ownerLockKey: computeSupportPassportOwnerLockKey(namespace, parsed.data.principal),
|
|
237
|
+
secretHash: sha256("support-passport-secret:v1", secret),
|
|
238
|
+
cards: parsed.data.cards,
|
|
239
|
+
createdAt: createdAt.toISOString(),
|
|
240
|
+
expiresAt,
|
|
241
|
+
});
|
|
242
|
+
await this.requireMutationLock(mutationLock);
|
|
243
|
+
await mutationHooks.beforeCommit?.();
|
|
244
|
+
await this.requireMutationLock(mutationLock);
|
|
245
|
+
try {
|
|
246
|
+
await this.writeState(state, true);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const persisted = await this.readState(state.grantId).catch(() => undefined);
|
|
249
|
+
if (!persisted || !sameGrantState(persisted, state)) throw error;
|
|
250
|
+
try {
|
|
251
|
+
await this.syncDirectory(this.grantsDir);
|
|
252
|
+
} catch {
|
|
253
|
+
await this.removeGrantStates([state.grantId]).catch(() => undefined);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
await this.writeOwnerMembership(state);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
await this.removeStoredGrant(state).catch(() => undefined);
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
const persisted = { state, secret };
|
|
264
|
+
committed = persisted;
|
|
265
|
+
return persisted;
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
await this.addToOwnerIndex(committed.state, lock);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof OwnerIndexLockLostError) throw error;
|
|
271
|
+
const indexed = await this.readOwnerIndexByHash(ownerHash).catch(() => undefined);
|
|
272
|
+
if (!indexed?.includes(committed.state.grantId)) throw error;
|
|
273
|
+
try {
|
|
274
|
+
await this.syncDirectory(this.ownerIndexesDir);
|
|
275
|
+
} catch {
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
await this.requireOwnerIndexLock(lock);
|
|
279
|
+
}
|
|
280
|
+
return committed;
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (!(error instanceof OwnerIndexLockLostError) || !committed) throw error;
|
|
284
|
+
finalized = await this.reconcileCreateAfterOwnerIndexLockLoss(committed);
|
|
285
|
+
}
|
|
286
|
+
notifySupportPassportCommitted(mutationHooks.onCommitted);
|
|
287
|
+
return finalized;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (committed) {
|
|
290
|
+
await this.withGrantLock(committed.state.grantId, async (lock) => {
|
|
291
|
+
await this.requireMutationLock(lock);
|
|
292
|
+
await this.removeStoredGrant(committed!.state);
|
|
293
|
+
}).catch(() => undefined);
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async authenticate(grantId: string, secret: string): Promise<SupportPassportGrantState> {
|
|
300
|
+
const normalizedGrantId = normalizeGrantId(grantId);
|
|
301
|
+
if (typeof secret !== "string" || secret.length > 512) throw grantNotFound();
|
|
302
|
+
let state: SupportPassportGrantState;
|
|
303
|
+
try {
|
|
304
|
+
state = await this.readState(normalizedGrantId);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") throw grantNotFound();
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
const candidate = sha256("support-passport-secret:v1", secret);
|
|
310
|
+
if (!hashesMatch(candidate, state.secretHash)) throw grantNotFound();
|
|
311
|
+
if (state.revokedAt) {
|
|
312
|
+
throw new SupportPassportError("grant_gone", "The share link is no longer active.", 410);
|
|
313
|
+
}
|
|
314
|
+
if (Date.parse(state.expiresAt) <= this.now().getTime()) {
|
|
315
|
+
throw new SupportPassportError("grant_expired", "The share link has expired.", 410);
|
|
316
|
+
}
|
|
317
|
+
return state;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async withAuthenticatedGrant<T>(
|
|
321
|
+
grantId: string,
|
|
322
|
+
secret: string,
|
|
323
|
+
task: (state: SupportPassportGrantState) => Promise<T>,
|
|
324
|
+
beforeReturn?: (state: SupportPassportGrantState) => Promise<void>
|
|
325
|
+
): Promise<T> {
|
|
326
|
+
return await this.withGrantLock(grantId, async (lock) => {
|
|
327
|
+
const state = await this.authenticate(grantId, secret);
|
|
328
|
+
const result = await task(state);
|
|
329
|
+
await this.requireMutationLock(lock);
|
|
330
|
+
const finalState = await this.authenticate(grantId, secret);
|
|
331
|
+
await this.requireMutationLock(lock);
|
|
332
|
+
if (finalState.stateVersion !== state.stateVersion) {
|
|
333
|
+
throw new SupportPassportError("grant_stale", "The shared support guide has changed.", 410);
|
|
334
|
+
}
|
|
335
|
+
this.requireActiveState(finalState);
|
|
336
|
+
await beforeReturn?.(finalState);
|
|
337
|
+
await this.requireMutationLock(lock);
|
|
338
|
+
this.requireActiveState(finalState);
|
|
339
|
+
return result;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async listForOwner(namespace: string, principal: string): Promise<SupportPassportGrantState[]> {
|
|
344
|
+
const normalizedNamespace = normalizeNamespace(namespace);
|
|
345
|
+
const principalHash = computeSupportPassportOwnerKey(normalizePrincipal(principal));
|
|
346
|
+
const activeCutoff = this.now().getTime();
|
|
347
|
+
const states = this.listedOwnerStates(
|
|
348
|
+
await this.readOwnerMembershipStates(normalizedNamespace, principalHash),
|
|
349
|
+
activeCutoff
|
|
350
|
+
);
|
|
351
|
+
return states.sort((a, b) => {
|
|
352
|
+
const aActive = !a.revokedAt && Date.parse(a.expiresAt) > activeCutoff;
|
|
353
|
+
const bActive = !b.revokedAt && Date.parse(b.expiresAt) > activeCutoff;
|
|
354
|
+
if (aActive !== bActive) return aActive ? -1 : 1;
|
|
355
|
+
return newestGrantFirst(a, b);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async revoke(
|
|
360
|
+
input: {
|
|
361
|
+
grantId: string;
|
|
362
|
+
namespace: string;
|
|
363
|
+
principal: string;
|
|
364
|
+
expectedStateVersion?: number;
|
|
365
|
+
},
|
|
366
|
+
hooks: SupportPassportGrantRevokeHooks = {}
|
|
367
|
+
): Promise<SupportPassportGrantState> {
|
|
368
|
+
const mutationHooks = typeof hooks === "function" ? { beforeCommit: hooks } : hooks;
|
|
369
|
+
const normalizedInput = { ...input, grantId: normalizeGrantId(input.grantId) };
|
|
370
|
+
const namespace = normalizeNamespace(input.namespace);
|
|
371
|
+
return await this.withGrantLock(normalizedInput.grantId, async (lock) => {
|
|
372
|
+
let state: SupportPassportGrantState;
|
|
373
|
+
try {
|
|
374
|
+
state = await this.readState(normalizedInput.grantId);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") throw grantNotFound();
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
const principalHash = computeSupportPassportOwnerKey(normalizePrincipal(input.principal));
|
|
380
|
+
if (state.namespace !== namespace || !hashesMatch(state.principalHash, principalHash)) throw grantNotFound();
|
|
381
|
+
if (state.revokedAt) {
|
|
382
|
+
return state;
|
|
383
|
+
}
|
|
384
|
+
if (
|
|
385
|
+
normalizedInput.expectedStateVersion !== undefined &&
|
|
386
|
+
normalizedInput.expectedStateVersion !== state.stateVersion
|
|
387
|
+
) {
|
|
388
|
+
throw new SupportPassportError("state_conflict", "The share link changed after it was loaded.", 409);
|
|
389
|
+
}
|
|
390
|
+
const revoked = SupportPassportGrantStateSchema.parse({
|
|
391
|
+
...state,
|
|
392
|
+
stateVersion: state.stateVersion + 1,
|
|
393
|
+
revokedAt: this.now().toISOString(),
|
|
394
|
+
});
|
|
395
|
+
await this.requireMutationLock(lock);
|
|
396
|
+
await mutationHooks.beforeCommit?.();
|
|
397
|
+
await this.requireMutationLock(lock);
|
|
398
|
+
try {
|
|
399
|
+
await this.writeState(revoked);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const persisted = await this.readState(revoked.grantId).catch(() => undefined);
|
|
402
|
+
if (!persisted || !sameGrantState(persisted, revoked)) throw error;
|
|
403
|
+
await this.syncDirectory(this.grantsDir);
|
|
404
|
+
}
|
|
405
|
+
notifySupportPassportCommitted(mutationHooks.onCommitted);
|
|
406
|
+
return revoked;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private filePath(grantId: string): string {
|
|
411
|
+
const normalizedGrantId = normalizeGrantId(grantId);
|
|
412
|
+
return path.join(this.grantsDir, `${normalizedGrantId}.json`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private ownerIndexPath(ownerHash: string): string {
|
|
416
|
+
if (!SAFE_OWNER_INDEX_HASH.test(ownerHash)) throw new Error("support passport owner index hash is invalid");
|
|
417
|
+
return path.join(this.ownerIndexesDir, `${ownerHash}.json`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private ownerHash(namespace: string, principalHash: string): string {
|
|
421
|
+
return sha256("support-passport-owner-index:v1", `${namespace}\0${principalHash}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private async addToOwnerIndex(state: SupportPassportGrantState, lock: HeldFileLockController): Promise<void> {
|
|
425
|
+
const ownerHash = this.ownerHash(state.namespace, state.principalHash);
|
|
426
|
+
const ownerStates = (await this.readOwnerMembershipStates(state.namespace, state.principalHash))
|
|
427
|
+
.filter((item) => item.grantId !== state.grantId)
|
|
428
|
+
.sort(newestGrantFirst);
|
|
429
|
+
const current = ownerStates.map((item) => item.grantId);
|
|
430
|
+
let retained = current;
|
|
431
|
+
const indexedStates: SupportPassportGrantState[] = [...ownerStates];
|
|
432
|
+
if (current.length >= MAX_OWNER_GRANT_HISTORY) {
|
|
433
|
+
const expiryCutoff = this.now().getTime();
|
|
434
|
+
const active = indexedStates.filter((item) => !item.revokedAt && Date.parse(item.expiresAt) > expiryCutoff);
|
|
435
|
+
if (active.length >= MAX_OWNER_GRANT_HISTORY) {
|
|
436
|
+
throw new SupportPassportError(
|
|
437
|
+
"invalid_input",
|
|
438
|
+
"A support passport can contain at most 100 active share links.",
|
|
439
|
+
400
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const inactive = indexedStates
|
|
443
|
+
.filter((item) => item.revokedAt || Date.parse(item.expiresAt) <= expiryCutoff)
|
|
444
|
+
.sort(newestGrantFirst);
|
|
445
|
+
retained = [
|
|
446
|
+
...active.map((item) => item.grantId),
|
|
447
|
+
...inactive.slice(0, MAX_OWNER_GRANT_HISTORY - active.length - 1).map((item) => item.grantId),
|
|
448
|
+
];
|
|
449
|
+
}
|
|
450
|
+
const grantIds = [...retained, state.grantId];
|
|
451
|
+
const retainedIds = new Set(grantIds);
|
|
452
|
+
const indexedStateById = new Map(indexedStates.map((indexedState) => [indexedState.grantId, indexedState]));
|
|
453
|
+
const evictedGrantIds = current.filter((grantId) => !retainedIds.has(grantId));
|
|
454
|
+
await this.writeOwnerIndexWhileLocked(ownerHash, grantIds, lock);
|
|
455
|
+
try {
|
|
456
|
+
for (const grantId of evictedGrantIds) {
|
|
457
|
+
await this.withGrantLock(grantId, async (grantLock) => {
|
|
458
|
+
await this.requireMutationLock(grantLock);
|
|
459
|
+
const evictedState = indexedStateById.get(grantId);
|
|
460
|
+
if (evictedState) await this.removeStoredGrant(evictedState);
|
|
461
|
+
else await this.removeGrantStates([grantId]);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
log.warn(
|
|
466
|
+
`support passport could not remove inactive grant state: ${error instanceof Error ? error.message : String(error)}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private async removeGrantStates(grantIds: string[]): Promise<void> {
|
|
472
|
+
if (grantIds.length === 0) return;
|
|
473
|
+
for (const grantId of grantIds) {
|
|
474
|
+
if (!SAFE_GRANT_ID.test(grantId)) throw grantNotFound();
|
|
475
|
+
}
|
|
476
|
+
await removePrivateFilesNoFollow(
|
|
477
|
+
this.grantsDir,
|
|
478
|
+
grantIds.map((grantId) => `${grantId}.json`),
|
|
479
|
+
"support passport grant files must be regular files in a stable directory",
|
|
480
|
+
path.parse(this.memoryDir).root
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private ownerMembershipDirectory(ownerHash: string): string {
|
|
485
|
+
if (!SAFE_OWNER_INDEX_HASH.test(ownerHash)) throw new Error("support passport owner index hash is invalid");
|
|
486
|
+
return path.join(this.ownerIndexesDir, ownerHash);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private async ensureOwnerMembershipDirectory(ownerHash: string): Promise<string> {
|
|
490
|
+
const directory = this.ownerMembershipDirectory(ownerHash);
|
|
491
|
+
await ensurePrivateDirectoryTreeNoFollow(
|
|
492
|
+
directory,
|
|
493
|
+
"support passport owner membership must remain inside the memory directory"
|
|
494
|
+
);
|
|
495
|
+
return directory;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private async writeOwnerMembership(state: SupportPassportGrantState): Promise<void> {
|
|
499
|
+
const ownerHash = this.ownerHash(state.namespace, state.principalHash);
|
|
500
|
+
const directory = await this.ensureOwnerMembershipDirectory(ownerHash);
|
|
501
|
+
await writePrivateFileAtomicallyNoFollow(
|
|
502
|
+
directory,
|
|
503
|
+
path.join(directory, `${state.grantId}.json`),
|
|
504
|
+
`${JSON.stringify({ schemaVersion: 1, grantId: state.grantId })}\n`,
|
|
505
|
+
"support passport owner membership must be regular files in a stable directory",
|
|
506
|
+
path.parse(this.memoryDir).root
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private async removeOwnerMembership(state: SupportPassportGrantState): Promise<void> {
|
|
511
|
+
const ownerHash = this.ownerHash(state.namespace, state.principalHash);
|
|
512
|
+
const directory = await this.ensureOwnerMembershipDirectory(ownerHash);
|
|
513
|
+
await removePrivateFilesNoFollow(
|
|
514
|
+
directory,
|
|
515
|
+
[`${state.grantId}.json`],
|
|
516
|
+
"support passport owner membership must be regular files in a stable directory",
|
|
517
|
+
path.parse(this.memoryDir).root
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private async removeStoredGrant(state: SupportPassportGrantState): Promise<void> {
|
|
522
|
+
await this.removeGrantStates([state.grantId]);
|
|
523
|
+
await this.removeOwnerMembership(state);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private async writeOwnerIndexWhileLocked(
|
|
527
|
+
ownerHash: string,
|
|
528
|
+
grantIds: string[],
|
|
529
|
+
lock: HeldFileLockController
|
|
530
|
+
): Promise<void> {
|
|
531
|
+
await this.requireOwnerIndexLock(lock);
|
|
532
|
+
await this.writeOwnerIndex(ownerHash, grantIds);
|
|
533
|
+
await this.requireOwnerIndexLock(lock);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private async reconcileCreateAfterOwnerIndexLockLoss(committed: {
|
|
537
|
+
state: SupportPassportGrantState;
|
|
538
|
+
secret: string;
|
|
539
|
+
}): Promise<{ state: SupportPassportGrantState; secret: string }> {
|
|
540
|
+
const ownerHash = this.ownerHash(committed.state.namespace, committed.state.principalHash);
|
|
541
|
+
for (let attempt = 1; attempt <= MAX_OWNER_INDEX_RECOVERY_ATTEMPTS; attempt += 1) {
|
|
542
|
+
try {
|
|
543
|
+
return await this.withOwnerIndexLock(
|
|
544
|
+
ownerHash,
|
|
545
|
+
async (lock) => await this.reconcileCommittedGrant(committed, ownerHash, lock)
|
|
546
|
+
);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (!(error instanceof OwnerIndexLockLostError)) throw error;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
throw new SupportPassportError("storage_conflict", "The share link store changed during the request.", 409);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private async reconcileCommittedGrant(
|
|
555
|
+
committed: { state: SupportPassportGrantState; secret: string },
|
|
556
|
+
ownerHash: string,
|
|
557
|
+
lock: HeldFileLockController
|
|
558
|
+
): Promise<{ state: SupportPassportGrantState; secret: string }> {
|
|
559
|
+
const persisted = await this.readState(committed.state.grantId).catch(() => undefined);
|
|
560
|
+
if (!persisted || !sameGrantState(persisted, committed.state)) {
|
|
561
|
+
throw new SupportPassportError("storage_conflict", "The share link store changed during the request.", 409);
|
|
562
|
+
}
|
|
563
|
+
const ownerStates = await this.readOwnerMembershipStates(committed.state.namespace, committed.state.principalHash);
|
|
564
|
+
const committedIndex = ownerStates.findIndex((state) => state.grantId === committed.state.grantId);
|
|
565
|
+
if (committedIndex === -1) ownerStates.push(committed.state);
|
|
566
|
+
else ownerStates[committedIndex] = committed.state;
|
|
567
|
+
const activeCutoff = this.now().getTime();
|
|
568
|
+
const active = ownerStates.filter((state) => !state.revokedAt && Date.parse(state.expiresAt) > activeCutoff);
|
|
569
|
+
if (active.length > MAX_OWNER_GRANT_HISTORY) {
|
|
570
|
+
await this.withGrantLock(committed.state.grantId, async (grantLock) => {
|
|
571
|
+
await this.requireMutationLock(grantLock);
|
|
572
|
+
await this.removeStoredGrant(committed.state);
|
|
573
|
+
});
|
|
574
|
+
const committedIndex = ownerStates.findIndex((state) => state.grantId === committed.state.grantId);
|
|
575
|
+
if (committedIndex !== -1) ownerStates.splice(committedIndex, 1);
|
|
576
|
+
}
|
|
577
|
+
const retained = this.retainedOwnerStates(ownerStates, activeCutoff);
|
|
578
|
+
await this.writeOwnerIndexWhileLocked(
|
|
579
|
+
ownerHash,
|
|
580
|
+
retained.map((state) => state.grantId),
|
|
581
|
+
lock
|
|
582
|
+
);
|
|
583
|
+
const retainedIds = new Set(retained.map((state) => state.grantId));
|
|
584
|
+
for (const state of ownerStates) {
|
|
585
|
+
if (retainedIds.has(state.grantId)) continue;
|
|
586
|
+
try {
|
|
587
|
+
await this.withGrantLock(state.grantId, async (grantLock) => {
|
|
588
|
+
await this.requireMutationLock(grantLock);
|
|
589
|
+
await this.removeStoredGrant(state);
|
|
590
|
+
});
|
|
591
|
+
} catch (error) {
|
|
592
|
+
log.warn(
|
|
593
|
+
`support passport could not remove inactive grant state: ${error instanceof Error ? error.message : String(error)}`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (active.length > MAX_OWNER_GRANT_HISTORY) {
|
|
598
|
+
throw new SupportPassportError("storage_conflict", "The share link store changed during the request.", 409);
|
|
599
|
+
}
|
|
600
|
+
return committed;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private retainedOwnerStates(states: SupportPassportGrantState[], activeCutoff: number): SupportPassportGrantState[] {
|
|
604
|
+
const active = states.filter((state) => !state.revokedAt && Date.parse(state.expiresAt) > activeCutoff);
|
|
605
|
+
if (active.length > MAX_OWNER_GRANT_HISTORY) {
|
|
606
|
+
throw new SupportPassportError("storage_conflict", "The share link store contains too many active links.", 409);
|
|
607
|
+
}
|
|
608
|
+
const inactive = states
|
|
609
|
+
.filter((state) => state.revokedAt || Date.parse(state.expiresAt) <= activeCutoff)
|
|
610
|
+
.sort(newestGrantFirst);
|
|
611
|
+
return [...active, ...inactive.slice(0, MAX_OWNER_GRANT_HISTORY - active.length)];
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private listedOwnerStates(states: SupportPassportGrantState[], activeCutoff: number): SupportPassportGrantState[] {
|
|
615
|
+
const active = states.filter((state) => !state.revokedAt && Date.parse(state.expiresAt) > activeCutoff);
|
|
616
|
+
const inactive = states
|
|
617
|
+
.filter((state) => state.revokedAt || Date.parse(state.expiresAt) <= activeCutoff)
|
|
618
|
+
.sort(newestGrantFirst);
|
|
619
|
+
return [...active, ...inactive.slice(0, Math.max(0, MAX_OWNER_GRANT_HISTORY - active.length))];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private async requireAvailableOwnerGrantSlot(namespace: string, principalHash: string): Promise<void> {
|
|
623
|
+
const activeCutoff = this.now().getTime();
|
|
624
|
+
const active = (await this.readOwnerMembershipStates(namespace, principalHash)).filter(
|
|
625
|
+
(state) => !state.revokedAt && Date.parse(state.expiresAt) > activeCutoff
|
|
626
|
+
);
|
|
627
|
+
if (active.length >= MAX_OWNER_GRANT_HISTORY) {
|
|
628
|
+
throw new SupportPassportError(
|
|
629
|
+
"invalid_input",
|
|
630
|
+
"A support passport can contain at most 100 active share links.",
|
|
631
|
+
400
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private async readOwnerMembershipStates(
|
|
637
|
+
namespace: string,
|
|
638
|
+
principalHash: string
|
|
639
|
+
): Promise<SupportPassportGrantState[]> {
|
|
640
|
+
await this.ensureSafeDirectories();
|
|
641
|
+
const ownerHash = this.ownerHash(namespace, principalHash);
|
|
642
|
+
const directory = await this.ensureOwnerMembershipDirectory(ownerHash);
|
|
643
|
+
const grantIds = await withPrivateDirectoryNoFollow(
|
|
644
|
+
path.parse(this.memoryDir).root,
|
|
645
|
+
directory,
|
|
646
|
+
"support passport owner membership must be regular files in a stable directory",
|
|
647
|
+
async (pinnedDirectory) => {
|
|
648
|
+
const entries = await readdir(pinnedDirectory, { withFileTypes: true });
|
|
649
|
+
return entries
|
|
650
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
651
|
+
.map((entry) => entry.name.slice(0, -5))
|
|
652
|
+
.filter((grantId) => SAFE_GRANT_ID.test(grantId));
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
const ownerStates: SupportPassportGrantState[] = [];
|
|
656
|
+
for (const grantId of grantIds) {
|
|
657
|
+
try {
|
|
658
|
+
const state = await this.readState(grantId);
|
|
659
|
+
if (state.namespace === namespace && hashesMatch(state.principalHash, principalHash)) {
|
|
660
|
+
ownerStates.push(state);
|
|
661
|
+
}
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT" || error instanceof MalformedGrantStateError) continue;
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return ownerStates;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private async readOwnerIndexByHash(ownerHash: string): Promise<string[]> {
|
|
671
|
+
await this.ensureSafeDirectories();
|
|
672
|
+
const filePath = this.ownerIndexPath(ownerHash);
|
|
673
|
+
let content: string;
|
|
674
|
+
try {
|
|
675
|
+
content = await readPrivateFileNoFollow(
|
|
676
|
+
this.ownerIndexesDir,
|
|
677
|
+
filePath,
|
|
678
|
+
"support passport owner indexes must be regular files in a stable directory",
|
|
679
|
+
path.parse(this.memoryDir).root
|
|
680
|
+
);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
const parsed: unknown = JSON.parse(content);
|
|
686
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
687
|
+
throw new Error("support passport owner index is invalid");
|
|
688
|
+
}
|
|
689
|
+
const record = parsed as Record<string, unknown>;
|
|
690
|
+
const grantIds = record.grantIds;
|
|
691
|
+
if (record.schemaVersion !== 1 || record.ownerHash !== ownerHash || !Array.isArray(grantIds)) {
|
|
692
|
+
throw new Error("support passport owner index is invalid");
|
|
693
|
+
}
|
|
694
|
+
if (grantIds.some((grantId) => typeof grantId !== "string" || !SAFE_GRANT_ID.test(grantId))) {
|
|
695
|
+
throw new Error("support passport owner index is invalid");
|
|
696
|
+
}
|
|
697
|
+
const uniqueGrantIds = new Set(grantIds as string[]);
|
|
698
|
+
if (uniqueGrantIds.size !== grantIds.length || uniqueGrantIds.size > MAX_OWNER_GRANT_HISTORY) {
|
|
699
|
+
throw new Error("support passport owner index is invalid");
|
|
700
|
+
}
|
|
701
|
+
return [...uniqueGrantIds];
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
private async writeOwnerIndex(ownerHash: string, grantIds: string[]): Promise<void> {
|
|
705
|
+
const filePath = this.ownerIndexPath(ownerHash);
|
|
706
|
+
try {
|
|
707
|
+
const metadata = await lstat(filePath);
|
|
708
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
|
709
|
+
throw new Error("support passport owner indexes must be regular files");
|
|
710
|
+
}
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
713
|
+
}
|
|
714
|
+
await writePrivateFileAtomicallyNoFollow(
|
|
715
|
+
this.ownerIndexesDir,
|
|
716
|
+
filePath,
|
|
717
|
+
`${JSON.stringify({ schemaVersion: 1, ownerHash, grantIds }, null, 2)}\n`,
|
|
718
|
+
"support passport owner indexes must be regular files in a stable directory",
|
|
719
|
+
path.parse(this.memoryDir).root
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private async readState(grantId: string): Promise<SupportPassportGrantState> {
|
|
724
|
+
await this.ensureSafeDirectories();
|
|
725
|
+
const filePath = this.filePath(grantId);
|
|
726
|
+
const content = await readPrivateFileNoFollow(
|
|
727
|
+
this.grantsDir,
|
|
728
|
+
filePath,
|
|
729
|
+
"support passport grant files must be regular files in a stable directory",
|
|
730
|
+
path.parse(this.memoryDir).root
|
|
731
|
+
);
|
|
732
|
+
try {
|
|
733
|
+
const state = SupportPassportGrantStateSchema.parse(JSON.parse(content));
|
|
734
|
+
if (state.grantId !== grantId) {
|
|
735
|
+
throw new MalformedGrantStateError("support passport grant ID must match its file name");
|
|
736
|
+
}
|
|
737
|
+
return state;
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (error instanceof MalformedGrantStateError) throw error;
|
|
740
|
+
if (error instanceof SyntaxError || (error as Error).name === "ZodError") {
|
|
741
|
+
throw new MalformedGrantStateError(undefined, error);
|
|
742
|
+
}
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private async writeState(state: SupportPassportGrantState, requireAbsent = false): Promise<void> {
|
|
748
|
+
const filePath = this.filePath(state.grantId);
|
|
749
|
+
try {
|
|
750
|
+
const metadata = await lstat(filePath);
|
|
751
|
+
if (metadata.isSymbolicLink() || !metadata.isFile())
|
|
752
|
+
throw new Error("support passport grant files must be regular files");
|
|
753
|
+
if (requireAbsent) {
|
|
754
|
+
throw new SupportPassportError("storage_conflict", "A new share link could not be allocated.", 409);
|
|
755
|
+
}
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
758
|
+
}
|
|
759
|
+
await writePrivateFileAtomicallyNoFollow(
|
|
760
|
+
this.grantsDir,
|
|
761
|
+
filePath,
|
|
762
|
+
`${JSON.stringify(state, null, 2)}\n`,
|
|
763
|
+
"support passport grant files must be regular files in a stable directory",
|
|
764
|
+
path.parse(this.memoryDir).root
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
private async ensureSafeDirectories(): Promise<void> {
|
|
769
|
+
if (!this.safeDirectoriesReady) {
|
|
770
|
+
this.safeDirectoriesReady = (async () => {
|
|
771
|
+
await this.ensureMemoryRoot();
|
|
772
|
+
await ensurePrivateDirectoryNoFollow(
|
|
773
|
+
this.memoryDir,
|
|
774
|
+
this.ownerIndexesDir,
|
|
775
|
+
"support passport grant directories must remain inside the memory directory",
|
|
776
|
+
undefined,
|
|
777
|
+
true
|
|
778
|
+
);
|
|
779
|
+
})();
|
|
780
|
+
}
|
|
781
|
+
const currentAttempt = this.safeDirectoriesReady;
|
|
782
|
+
try {
|
|
783
|
+
await currentAttempt;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
if (this.safeDirectoriesReady === currentAttempt) this.safeDirectoriesReady = undefined;
|
|
786
|
+
throw error;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private async ensureMemoryRoot(): Promise<void> {
|
|
791
|
+
const configuredMemoryDir = this.memoryDir;
|
|
792
|
+
if (!this.memoryRootReady) {
|
|
793
|
+
this.memoryRootReady = (async () => {
|
|
794
|
+
await ensurePrivateDirectoryTreeNoFollow(
|
|
795
|
+
configuredMemoryDir,
|
|
796
|
+
"support passport memory directory must be a stable directory",
|
|
797
|
+
undefined
|
|
798
|
+
);
|
|
799
|
+
this.memoryDir = configuredMemoryDir;
|
|
800
|
+
this.grantsDir = path.join(configuredMemoryDir, "state", "support-passport", "grants");
|
|
801
|
+
this.ownerIndexesDir = path.join(this.grantsDir, "owners");
|
|
802
|
+
})();
|
|
803
|
+
}
|
|
804
|
+
const currentAttempt = this.memoryRootReady;
|
|
805
|
+
try {
|
|
806
|
+
await currentAttempt;
|
|
807
|
+
} catch (error) {
|
|
808
|
+
if (this.memoryRootReady === currentAttempt) this.memoryRootReady = undefined;
|
|
809
|
+
throw error;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private async withMutationLock<T>(task: (lock: HeldFileLockController) => Promise<T>): Promise<T> {
|
|
814
|
+
await this.ensureSafeDirectories();
|
|
815
|
+
return await withPrivateDirectoryNoFollow(
|
|
816
|
+
path.parse(this.memoryDir).root,
|
|
817
|
+
this.grantsDir,
|
|
818
|
+
"support passport grant lock directory must remain inside the memory directory",
|
|
819
|
+
async (pinnedDirectory) =>
|
|
820
|
+
await this.withExclusiveLock(
|
|
821
|
+
`support-passport-grants:${this.grantsDir}`,
|
|
822
|
+
path.join(pinnedDirectory, ".grants.lock"),
|
|
823
|
+
task
|
|
824
|
+
)
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private async withGrantLock<T>(grantId: string, task: (lock: HeldFileLockController) => Promise<T>): Promise<T> {
|
|
829
|
+
const normalizedGrantId = normalizeGrantId(grantId);
|
|
830
|
+
await this.ensureSafeDirectories();
|
|
831
|
+
return await withPrivateDirectoryNoFollow(
|
|
832
|
+
path.parse(this.memoryDir).root,
|
|
833
|
+
this.grantsDir,
|
|
834
|
+
"support passport grant lock directory must remain inside the memory directory",
|
|
835
|
+
async (pinnedDirectory) =>
|
|
836
|
+
await this.withExclusiveLock(
|
|
837
|
+
`support-passport-grant:${this.grantsDir}:${normalizedGrantId}`,
|
|
838
|
+
path.join(pinnedDirectory, `.${normalizedGrantId}.lock`),
|
|
839
|
+
task
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private async withOwnerIndexLock<T>(
|
|
845
|
+
ownerHash: string,
|
|
846
|
+
task: (lock: HeldFileLockController) => Promise<T>
|
|
847
|
+
): Promise<T> {
|
|
848
|
+
await this.ensureSafeDirectories();
|
|
849
|
+
return await withPrivateDirectoryNoFollow(
|
|
850
|
+
path.parse(this.memoryDir).root,
|
|
851
|
+
this.ownerIndexesDir,
|
|
852
|
+
"support passport owner lock directory must remain inside the memory directory",
|
|
853
|
+
async (pinnedDirectory) =>
|
|
854
|
+
await this.withExclusiveLock(
|
|
855
|
+
`support-passport-owner:${this.ownerIndexesDir}:${ownerHash}`,
|
|
856
|
+
path.join(pinnedDirectory, `.${ownerHash}.lock`),
|
|
857
|
+
task
|
|
858
|
+
)
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private async withExclusiveLock<T>(
|
|
863
|
+
serializationKey: string,
|
|
864
|
+
lockPath: string,
|
|
865
|
+
task: (lock: HeldFileLockController) => Promise<T>
|
|
866
|
+
): Promise<T> {
|
|
867
|
+
return await serializeMutations(serializationKey, () =>
|
|
868
|
+
this.runWithHeldFileLock(
|
|
869
|
+
lockPath,
|
|
870
|
+
{
|
|
871
|
+
staleMs: GRANT_LOCK_STALE_MS,
|
|
872
|
+
maxWaitMs: GRANT_LOCK_WAIT_MS,
|
|
873
|
+
heartbeatMs: GRANT_LOCK_HEARTBEAT_MS,
|
|
874
|
+
},
|
|
875
|
+
async (acquired, lock) => {
|
|
876
|
+
if (!acquired) {
|
|
877
|
+
throw new SupportPassportError("storage_conflict", "The share link store is busy. Try again.", 409);
|
|
878
|
+
}
|
|
879
|
+
return await task(lock);
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
private async requireMutationLock(lock: HeldFileLockController): Promise<void> {
|
|
886
|
+
if (await lock.refresh()) return;
|
|
887
|
+
throw new SupportPassportError("storage_conflict", "The share link store changed during the request.", 409);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private async requireOwnerIndexLock(lock: HeldFileLockController): Promise<void> {
|
|
891
|
+
try {
|
|
892
|
+
await this.requireMutationLock(lock);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
throw new OwnerIndexLockLostError(error);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private requireActiveState(state: SupportPassportGrantState): void {
|
|
899
|
+
if (state.revokedAt) {
|
|
900
|
+
throw new SupportPassportError("grant_gone", "The share link is no longer active.", 410);
|
|
901
|
+
}
|
|
902
|
+
if (Date.parse(state.expiresAt) <= this.now().getTime()) {
|
|
903
|
+
throw new SupportPassportError("grant_expired", "The share link has expired.", 410);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|