@remnic/core 9.54.5 → 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 -24
- package/dist/access-admin-ops-surface.js +32 -29
- package/dist/access-authorization-probe.d.ts +25 -23
- package/dist/access-authorization-probe.js +35 -30
- package/dist/access-boundary.d.ts +26 -24
- package/dist/access-boundary.js +36 -31
- package/dist/access-cli.js +86 -80
- 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 -19
- package/dist/access-extraction-force-flush.js +32 -29
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +22 -22
- package/dist/access-http-lifecycle-flush.d.ts +22 -22
- package/dist/access-http-offline-stream.d.ts +18 -18
- package/dist/access-http-query.js +32 -29
- package/dist/access-http.d.ts +55 -37
- package/dist/access-http.js +52 -43
- package/dist/access-identity-continuity-surface.d.ts +9 -9
- package/dist/access-identity-continuity-surface.js +32 -29
- package/dist/access-lcm-surface.d.ts +21 -21
- package/dist/access-lcm-surface.js +32 -29
- package/dist/access-mcp.d.ts +22 -22
- package/dist/access-mcp.js +40 -36
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-BdHlGd_T.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 +32 -29
- package/dist/access-observe-write-surface.d.ts +22 -22
- package/dist/access-observe-write-surface.js +32 -29
- package/dist/access-offline-manifest.d.ts +9 -9
- package/dist/access-offline-manifest.js +2 -2
- package/dist/access-operations-batch.js +34 -31
- package/dist/access-operations.d.ts +26 -26
- package/dist/access-operations.js +38 -34
- package/dist/access-recall-concurrency.d.ts +21 -21
- package/dist/access-recall-concurrency.js +32 -29
- package/dist/access-recall-response.d.ts +19 -19
- package/dist/access-recall-response.js +32 -29
- package/dist/access-recall-surface.d.ts +19 -19
- package/dist/access-recall-surface.js +32 -29
- package/dist/access-schema.d.ts +88 -88
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DL7X9hrg.d.ts → access-service-DkSdXK4S.d.ts} +1314 -83
- package/dist/access-service-helpers.d.ts +22 -22
- package/dist/access-service.d.ts +31 -31
- package/dist/access-service.js +32 -29
- package/dist/access-surface-catalog.d.ts +22 -22
- 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 +6 -5
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- 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 +14 -14
- 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 +2 -2
- 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-B90is4d8.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 +15 -15
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-CSY44OKU.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-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
- 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-TKFGFDPJ.js → chunk-3TIZPSJC.js} +4 -4
- package/dist/{chunk-B4T3FQMT.js → chunk-43SEBY2M.js} +1 -1
- package/dist/{chunk-LVKYLMSM.js → chunk-46SZBVDS.js} +2 -2
- package/dist/{chunk-YKZLOPTC.js → chunk-5FD7JMTX.js} +2 -2
- package/dist/{chunk-2PNXOQ4P.js → chunk-5NB726GV.js} +3 -7
- 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-WM5LD6AT.js → chunk-6CYHUWCO.js} +2 -2
- package/dist/{chunk-BIF4SHMD.js → chunk-76JJSXK7.js} +1 -1
- package/dist/chunk-76JJSXK7.js.map +1 -0
- package/dist/{chunk-3SXLD5T3.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-YR65W7BV.js → chunk-BRR5EUTF.js} +3 -3
- package/dist/{chunk-JKW663ZN.js → chunk-CIAI24TQ.js} +2 -2
- package/dist/{chunk-3WGI7F4V.js → chunk-D3LBIUWV.js} +9219 -5233
- package/dist/chunk-D3LBIUWV.js.map +1 -0
- package/dist/{chunk-YDWPYDML.js → chunk-EKTP7MNS.js} +4 -4
- package/dist/{chunk-6LEIWWAF.js → chunk-F2JRDPON.js} +2 -2
- package/dist/{chunk-VYQ4URQB.js → chunk-FFRTNF3L.js} +2 -2
- package/dist/{chunk-TY6MKIWP.js → chunk-FONWBCVY.js} +3 -3
- package/dist/{chunk-XSV2YDCJ.js → chunk-FPLM2TKD.js} +2 -2
- package/dist/{chunk-VTU5IEKY.js → chunk-FTPONF6B.js} +2 -2
- package/dist/{chunk-6PZGKGAO.js → chunk-FWX7W5PM.js} +2 -2
- package/dist/{chunk-QPW3PAIH.js → chunk-G6DFSS6A.js} +2 -2
- package/dist/{chunk-2DD6ZHOM.js → chunk-GJ776BC2.js} +3 -3
- package/dist/{chunk-XKAR4RF6.js → chunk-GN3WRFJY.js} +6 -6
- package/dist/{chunk-RG426DJO.js → chunk-H7NAZ3UB.js} +2 -2
- package/dist/{chunk-BSXGTDVJ.js → chunk-HBUFQRIM.js} +2 -2
- package/dist/{chunk-FFEBSFVT.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-QTO6BMQZ.js → chunk-I3MSGFGD.js} +17 -6
- package/dist/chunk-I3MSGFGD.js.map +1 -0
- package/dist/{chunk-5JKUSBMX.js → chunk-IKP5OYQQ.js} +2 -2
- package/dist/{chunk-KQAFEZQX.js → chunk-IPLYGWQF.js} +5 -5
- package/dist/{chunk-DBY26JJJ.js → chunk-IQ4NVWEF.js} +2 -2
- package/dist/{chunk-7Q7JWLQ4.js → chunk-JYG4OLWH.js} +2 -2
- package/dist/{chunk-3OTSVCPK.js → chunk-K26NWEOC.js} +4 -4
- package/dist/{chunk-WZWZRZ2V.js → chunk-K2UGPTYY.js} +484 -43
- 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-HOIRB77A.js → chunk-L6Z5OAS4.js} +2 -2
- package/dist/{chunk-7RZJWZ46.js → chunk-LEPOP46W.js} +5 -5
- package/dist/{chunk-QQXWFDRD.js → chunk-M4YHAODQ.js} +270 -684
- package/dist/chunk-M4YHAODQ.js.map +1 -0
- package/dist/{chunk-NGT35IGQ.js → chunk-MIH3CJPN.js} +3 -3
- package/dist/{chunk-QPAQSG3F.js → chunk-MK5VEWCR.js} +2 -2
- package/dist/chunk-NISZQW72.js +140 -0
- package/dist/chunk-NISZQW72.js.map +1 -0
- package/dist/{chunk-L7RTUEDG.js → chunk-OCIP6YMD.js} +2 -2
- package/dist/{chunk-2INWPC6E.js → chunk-PG4QBPVG.js} +20 -10
- package/dist/chunk-PG4QBPVG.js.map +1 -0
- package/dist/{chunk-DDA55IVB.js → chunk-PT54WISF.js} +189 -55
- package/dist/chunk-PT54WISF.js.map +1 -0
- package/dist/{chunk-UWJNKGBA.js → chunk-QZLDRQ4I.js} +5 -5
- package/dist/{chunk-UYOXFFEY.js → chunk-RUNORG2P.js} +3 -3
- package/dist/{chunk-LC3FD4BV.js → chunk-RZQ6W2W4.js} +39 -39
- package/dist/{chunk-MRGJZ75M.js → chunk-SMNYAMJD.js} +3 -3
- package/dist/{chunk-MKQBXQKU.js → chunk-TB6CBZ5V.js} +22 -15
- package/dist/chunk-TB6CBZ5V.js.map +1 -0
- package/dist/{chunk-DN2IDKI5.js → chunk-TO7ZTWJW.js} +2 -2
- package/dist/{chunk-K2TDK7GG.js → chunk-TWTUGCTY.js} +2 -2
- package/dist/{chunk-CUTFSLSD.js → chunk-UCTS3DDH.js} +2 -2
- package/dist/{chunk-JYIJNNN3.js → chunk-UG6VXIOK.js} +2 -2
- package/dist/{chunk-QOQQMVR2.js → chunk-UKYS3NU5.js} +2 -2
- package/dist/{chunk-VZRFGJCH.js → chunk-UPLZNJKN.js} +2 -2
- package/dist/{chunk-C23BYTJV.js → chunk-UW5NPZJ2.js} +40 -12
- package/dist/chunk-UW5NPZJ2.js.map +1 -0
- package/dist/{chunk-DOOWFDWN.js → chunk-V6ERBR7U.js} +2 -2
- package/dist/{chunk-7HG7MGAP.js → chunk-W5ZKPI34.js} +462 -2673
- package/dist/chunk-W5ZKPI34.js.map +1 -0
- package/dist/{chunk-HNDIND7P.js → chunk-XX4HPFFM.js} +54 -15
- package/dist/chunk-XX4HPFFM.js.map +1 -0
- package/dist/{chunk-N7CXQ5TA.js → chunk-XZHE2DEN.js} +1 -1
- package/dist/chunk-YXQEKCZM.js +223 -0
- package/dist/chunk-YXQEKCZM.js.map +1 -0
- package/dist/{chunk-6NNNURLK.js → chunk-Z54EM5YZ.js} +4 -4
- package/dist/{chunk-F2Y6I74C.js → chunk-ZRZVLB7O.js} +2 -2
- package/dist/{chunk-CLKBV3Z3.js → chunk-ZUKIUXHQ.js} +9 -9
- package/dist/{cli-jfuZXixq.d.ts → cli-Bze8S0Qs.d.ts} +5 -5
- package/dist/cli.d.ts +22 -22
- package/dist/cli.js +66 -58
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +14 -14
- 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 +2 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +14 -14
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +14 -14
- 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 +2 -2
- package/dist/{contradiction-scan-KCADHXPE.js → contradiction-scan-JDTBXRGW.js} +3 -3
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +16 -16
- 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 +16 -16
- 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-CTDuAVJc.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 +14 -14
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +9 -9
- package/dist/explicit-capture.js +4 -3
- package/dist/external-wiki-access.d.ts +22 -22
- package/dist/external-wiki-access.js +33 -30
- 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 -22
- 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 +20 -20
- package/dist/fallback-llm.d.ts +14 -1
- package/dist/fallback-llm.js +1 -1
- package/dist/{first-start-migration-LMMZFLKK.js → first-start-migration-CRZCZAJN.js} +5 -5
- 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 +704 -1421
- package/dist/index.js +203 -118
- 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 +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +2 -2
- 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 +14 -14
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +14 -14
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +15 -15
- package/dist/{maintenance-heMZSq7G.d.ts → maintenance-C8aRWfhq.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +22 -22
- 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-store.d.ts +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 +15 -15
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +14 -14
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +23 -22
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +19 -19
- package/dist/{orchestrator-BQQPsSjs.d.ts → orchestrator-D85CzDDY.d.ts} +8 -8
- package/dist/orchestrator.d.ts +9 -9
- package/dist/orchestrator.js +86 -80
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-DFfmsnGk.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/{qmd-BF5bcVRs.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 +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- 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 +3 -2
- 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 +116 -116
- package/dist/{scope-profiles-CbUOsE_E.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 +5 -5
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -9
- 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-DAB3xdKM.d.ts → semantic-consolidation-BP7NAX0N.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +15 -15
- package/dist/semantic-rule-promotion.js +14 -14
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +14 -14
- package/dist/{service-DGKg2O6r.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 +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +16 -16
- package/dist/{storage-CC1jKcWg.d.ts → storage-qOnaJ1Lw.d.ts} +4 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +13 -13
- 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 +1 -1
- package/dist/tier-routing.js +2 -2
- package/dist/{tier-stats-JDN6HRNT.js → tier-stats-EXYSKSVN.js} +3 -3
- 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 +90 -90
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-BnQza1y8.d.ts → types-ed-SvS2t.d.ts} +7 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +14 -14
- 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 +1940 -643
- package/src/access-http.ts +81 -74
- package/src/access-mcp.ts +3 -1
- package/src/access-operations.ts +2 -0
- package/src/access-service.ts +612 -845
- package/src/access-surface-catalog.test.ts +40 -10
- package/src/access-surface-catalog.ts +15 -0
- package/src/active-memory-bridge.ts +17 -19
- package/src/config.ts +18 -9
- package/src/fallback-llm.test.ts +54 -0
- package/src/fallback-llm.ts +54 -9
- 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/mcp-read-only-tools.ts +3 -0
- package/src/relay/mission-access.test.ts +11 -11
- package/src/relay/mission-ui.test.ts +3 -1
- package/src/storage/tombstone-blocked-capture-sync.ts +31 -0
- package/src/storage-contract/failure-semantics.test.ts +102 -0
- 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 +8 -0
- package/src/support-passport/card-projection.ts +77 -16
- 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 +533 -31
- package/src/support-passport/card-service.ts +506 -364
- 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 +15 -2
- package/src/support-passport/contracts.ts +13 -0
- package/src/support-passport/errors.ts +6 -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 +45 -1
- package/src/support-passport/grant-generated-batch-scope.test.ts +201 -0
- package/src/support-passport/grant-service-cache.test.ts +2 -2
- package/src/support-passport/grant-service-cancellation.test.ts +2 -0
- package/src/support-passport/grant-service-marker-cache.test.ts +62 -0
- package/src/support-passport/grant-service.test.ts +853 -239
- package/src/support-passport/grant-service.ts +206 -57
- package/src/support-passport/grant-store.ts +7 -3
- package/src/support-passport/index.ts +10 -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/owner-http.test.ts +671 -0
- package/src/support-passport/owner-lock.ts +26 -9
- package/src/support-passport/private-file.test.ts +99 -0
- package/src/support-passport/private-file.ts +139 -12
- 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/types.ts +7 -0
- package/src/write-rate-limiter.test.ts +30 -6
- package/src/write-rate-limiter.ts +29 -18
- package/dist/chunk-2INWPC6E.js.map +0 -1
- package/dist/chunk-2PNXOQ4P.js.map +0 -1
- package/dist/chunk-3SXLD5T3.js.map +0 -1
- package/dist/chunk-3WGI7F4V.js.map +0 -1
- package/dist/chunk-7HG7MGAP.js.map +0 -1
- package/dist/chunk-BIF4SHMD.js.map +0 -1
- package/dist/chunk-C23BYTJV.js.map +0 -1
- package/dist/chunk-CSY44OKU.js.map +0 -1
- package/dist/chunk-DDA55IVB.js.map +0 -1
- package/dist/chunk-HNDIND7P.js.map +0 -1
- package/dist/chunk-L6CQ7WIW.js.map +0 -1
- package/dist/chunk-MKQBXQKU.js.map +0 -1
- package/dist/chunk-QQXWFDRD.js.map +0 -1
- package/dist/chunk-QTO6BMQZ.js.map +0 -1
- package/dist/chunk-WZWZRZ2V.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-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-TKFGFDPJ.js.map → chunk-3TIZPSJC.js.map} +0 -0
- /package/dist/{chunk-B4T3FQMT.js.map → chunk-43SEBY2M.js.map} +0 -0
- /package/dist/{chunk-LVKYLMSM.js.map → chunk-46SZBVDS.js.map} +0 -0
- /package/dist/{chunk-YKZLOPTC.js.map → chunk-5FD7JMTX.js.map} +0 -0
- /package/dist/{chunk-WM5LD6AT.js.map → chunk-6CYHUWCO.js.map} +0 -0
- /package/dist/{chunk-YR65W7BV.js.map → chunk-BRR5EUTF.js.map} +0 -0
- /package/dist/{chunk-JKW663ZN.js.map → chunk-CIAI24TQ.js.map} +0 -0
- /package/dist/{chunk-YDWPYDML.js.map → chunk-EKTP7MNS.js.map} +0 -0
- /package/dist/{chunk-6LEIWWAF.js.map → chunk-F2JRDPON.js.map} +0 -0
- /package/dist/{chunk-VYQ4URQB.js.map → chunk-FFRTNF3L.js.map} +0 -0
- /package/dist/{chunk-TY6MKIWP.js.map → chunk-FONWBCVY.js.map} +0 -0
- /package/dist/{chunk-XSV2YDCJ.js.map → chunk-FPLM2TKD.js.map} +0 -0
- /package/dist/{chunk-VTU5IEKY.js.map → chunk-FTPONF6B.js.map} +0 -0
- /package/dist/{chunk-6PZGKGAO.js.map → chunk-FWX7W5PM.js.map} +0 -0
- /package/dist/{chunk-QPW3PAIH.js.map → chunk-G6DFSS6A.js.map} +0 -0
- /package/dist/{chunk-2DD6ZHOM.js.map → chunk-GJ776BC2.js.map} +0 -0
- /package/dist/{chunk-XKAR4RF6.js.map → chunk-GN3WRFJY.js.map} +0 -0
- /package/dist/{chunk-RG426DJO.js.map → chunk-H7NAZ3UB.js.map} +0 -0
- /package/dist/{chunk-BSXGTDVJ.js.map → chunk-HBUFQRIM.js.map} +0 -0
- /package/dist/{chunk-FFEBSFVT.js.map → chunk-HGJ6NJ4E.js.map} +0 -0
- /package/dist/{chunk-5JKUSBMX.js.map → chunk-IKP5OYQQ.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-DBY26JJJ.js.map → chunk-IQ4NVWEF.js.map} +0 -0
- /package/dist/{chunk-7Q7JWLQ4.js.map → chunk-JYG4OLWH.js.map} +0 -0
- /package/dist/{chunk-3OTSVCPK.js.map → chunk-K26NWEOC.js.map} +0 -0
- /package/dist/{chunk-HOIRB77A.js.map → chunk-L6Z5OAS4.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-NGT35IGQ.js.map → chunk-MIH3CJPN.js.map} +0 -0
- /package/dist/{chunk-QPAQSG3F.js.map → chunk-MK5VEWCR.js.map} +0 -0
- /package/dist/{chunk-L7RTUEDG.js.map → chunk-OCIP6YMD.js.map} +0 -0
- /package/dist/{chunk-UWJNKGBA.js.map → chunk-QZLDRQ4I.js.map} +0 -0
- /package/dist/{chunk-UYOXFFEY.js.map → chunk-RUNORG2P.js.map} +0 -0
- /package/dist/{chunk-LC3FD4BV.js.map → chunk-RZQ6W2W4.js.map} +0 -0
- /package/dist/{chunk-MRGJZ75M.js.map → chunk-SMNYAMJD.js.map} +0 -0
- /package/dist/{chunk-DN2IDKI5.js.map → chunk-TO7ZTWJW.js.map} +0 -0
- /package/dist/{chunk-K2TDK7GG.js.map → chunk-TWTUGCTY.js.map} +0 -0
- /package/dist/{chunk-CUTFSLSD.js.map → chunk-UCTS3DDH.js.map} +0 -0
- /package/dist/{chunk-JYIJNNN3.js.map → chunk-UG6VXIOK.js.map} +0 -0
- /package/dist/{chunk-QOQQMVR2.js.map → chunk-UKYS3NU5.js.map} +0 -0
- /package/dist/{chunk-VZRFGJCH.js.map → chunk-UPLZNJKN.js.map} +0 -0
- /package/dist/{chunk-DOOWFDWN.js.map → chunk-V6ERBR7U.js.map} +0 -0
- /package/dist/{chunk-N7CXQ5TA.js.map → chunk-XZHE2DEN.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-Z54EM5YZ.js.map} +0 -0
- /package/dist/{chunk-F2Y6I74C.js.map → chunk-ZRZVLB7O.js.map} +0 -0
- /package/dist/{chunk-CLKBV3Z3.js.map → chunk-ZUKIUXHQ.js.map} +0 -0
- /package/dist/{contradiction-scan-KCADHXPE.js.map → contradiction-scan-JDTBXRGW.js.map} +0 -0
- /package/dist/{first-start-migration-LMMZFLKK.js.map → first-start-migration-CRZCZAJN.js.map} +0 -0
- /package/dist/{tier-stats-JDN6HRNT.js.map → tier-stats-EXYSKSVN.js.map} +0 -0
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { renameSync, symlinkSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
lstat,
|
|
5
|
+
mkdir,
|
|
6
|
+
mkdtemp,
|
|
7
|
+
open,
|
|
8
|
+
readFile,
|
|
9
|
+
readdir,
|
|
10
|
+
rm,
|
|
11
|
+
symlink,
|
|
12
|
+
writeFile,
|
|
13
|
+
} from "node:fs/promises";
|
|
4
14
|
import { tmpdir } from "node:os";
|
|
5
15
|
import path from "node:path";
|
|
6
16
|
import { test } from "node:test";
|
|
@@ -27,16 +37,24 @@ async function makeSubject() {
|
|
|
27
37
|
const root = await mkdtemp(path.join(tmpdir(), "remnic-support-grants-"));
|
|
28
38
|
const aliceStorage = new StorageManager(path.join(root, "alice"));
|
|
29
39
|
const bobStorage = new StorageManager(path.join(root, "bob"));
|
|
30
|
-
await Promise.all([
|
|
40
|
+
await Promise.all([
|
|
41
|
+
aliceStorage.ensureDirectories(),
|
|
42
|
+
bobStorage.ensureDirectories(),
|
|
43
|
+
]);
|
|
31
44
|
let currentTime = Date.parse("2026-08-11T12:00:00.000Z");
|
|
32
45
|
const now = () => new Date(currentTime);
|
|
33
46
|
const resolveOwner = async (principal: string) => {
|
|
34
|
-
if (principal === "owner:alice")
|
|
35
|
-
|
|
47
|
+
if (principal === "owner:alice")
|
|
48
|
+
return { principal, namespace: "alice", storage: aliceStorage };
|
|
49
|
+
if (principal === "owner:bob")
|
|
50
|
+
return { principal, namespace: "bob", storage: bobStorage };
|
|
36
51
|
throw new Error("unknown test principal");
|
|
37
52
|
};
|
|
38
53
|
const cardService = new SupportPassportCardService({ resolveOwner, now });
|
|
39
|
-
const grantStore = new SupportPassportGrantStore({
|
|
54
|
+
const grantStore = new SupportPassportGrantStore({
|
|
55
|
+
memoryDir: path.join(root, "shared"),
|
|
56
|
+
now,
|
|
57
|
+
});
|
|
40
58
|
const grantService = new SupportPassportGrantService({
|
|
41
59
|
grantStore,
|
|
42
60
|
resolveOwner,
|
|
@@ -65,7 +83,10 @@ async function makeSubject() {
|
|
|
65
83
|
};
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
async function createActiveCard(
|
|
86
|
+
async function createActiveCard(
|
|
87
|
+
subject: Awaited<ReturnType<typeof makeSubject>>,
|
|
88
|
+
title = "Quiet space",
|
|
89
|
+
) {
|
|
69
90
|
const draft = await subject.cardService.createManualDraft({
|
|
70
91
|
principal: "owner:alice",
|
|
71
92
|
title,
|
|
@@ -80,7 +101,10 @@ async function createActiveCard(subject: Awaited<ReturnType<typeof makeSubject>>
|
|
|
80
101
|
});
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
function expiryAfter(
|
|
104
|
+
function expiryAfter(
|
|
105
|
+
subject: Awaited<ReturnType<typeof makeSubject>>,
|
|
106
|
+
milliseconds: number,
|
|
107
|
+
): string {
|
|
84
108
|
return new Date(subject.now().getTime() + milliseconds).toISOString();
|
|
85
109
|
}
|
|
86
110
|
|
|
@@ -103,7 +127,7 @@ test("a grant stores only hashed credentials with private file permissions", asy
|
|
|
103
127
|
"state",
|
|
104
128
|
"support-passport",
|
|
105
129
|
"grants",
|
|
106
|
-
`${created.grant.grantId}.json
|
|
130
|
+
`${created.grant.grantId}.json`,
|
|
107
131
|
);
|
|
108
132
|
const body = await readFile(filePath, "utf8");
|
|
109
133
|
const state = JSON.parse(body);
|
|
@@ -122,14 +146,52 @@ test("a grant stores only hashed credentials with private file permissions", asy
|
|
|
122
146
|
}
|
|
123
147
|
});
|
|
124
148
|
|
|
149
|
+
test("grant creation stops the new link when its owner request is cancelled after persistence", async () => {
|
|
150
|
+
const subject = await makeSubject();
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const create = subject.grantStore.create.bind(subject.grantStore);
|
|
153
|
+
subject.grantStore.create = async (...args) => {
|
|
154
|
+
const created = await create(...args);
|
|
155
|
+
controller.abort(new Error("simulated owner request timeout"));
|
|
156
|
+
return created;
|
|
157
|
+
};
|
|
158
|
+
try {
|
|
159
|
+
const card = await createActiveCard(subject);
|
|
160
|
+
await assert.rejects(
|
|
161
|
+
subject.grantService.createGrant(
|
|
162
|
+
{
|
|
163
|
+
principal: "owner:alice",
|
|
164
|
+
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
165
|
+
expiresAt: expiryAfter(subject, 3_600_000),
|
|
166
|
+
},
|
|
167
|
+
{ signal: controller.signal },
|
|
168
|
+
),
|
|
169
|
+
/simulated owner request timeout/,
|
|
170
|
+
);
|
|
171
|
+
const grants = await subject.grantService.listGrants({
|
|
172
|
+
principal: "owner:alice",
|
|
173
|
+
});
|
|
174
|
+
assert.equal(grants.length, 1);
|
|
175
|
+
assert.equal(grants[0]?.status, "revoked");
|
|
176
|
+
} finally {
|
|
177
|
+
await subject.cleanup();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
125
181
|
test("owner grant listings read only the indexed owner grants", async () => {
|
|
126
|
-
const root = await mkdtemp(
|
|
182
|
+
const root = await mkdtemp(
|
|
183
|
+
path.join(tmpdir(), "remnic-support-grant-index-"),
|
|
184
|
+
);
|
|
127
185
|
try {
|
|
128
|
-
const grantIds = [
|
|
186
|
+
const grantIds = [
|
|
187
|
+
"00000000-0000-4000-8000-000000000001",
|
|
188
|
+
"00000000-0000-4000-8000-000000000002",
|
|
189
|
+
];
|
|
129
190
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
130
191
|
const store = new SupportPassportGrantStore({
|
|
131
192
|
memoryDir: root,
|
|
132
|
-
makeGrantId: () =>
|
|
193
|
+
makeGrantId: () =>
|
|
194
|
+
grantIds.shift() ?? "00000000-0000-4000-8000-000000000003",
|
|
133
195
|
now: () => now,
|
|
134
196
|
});
|
|
135
197
|
const common = {
|
|
@@ -153,7 +215,7 @@ test("owner grant listings read only the indexed owner grants", async () => {
|
|
|
153
215
|
|
|
154
216
|
assert.deepEqual(
|
|
155
217
|
listed.map((state) => state.grantId),
|
|
156
|
-
[alice.state.grantId]
|
|
218
|
+
[alice.state.grantId],
|
|
157
219
|
);
|
|
158
220
|
assert.deepEqual(readGrantIds, [alice.state.grantId]);
|
|
159
221
|
assert.notEqual(alice.state.grantId, bob.state.grantId);
|
|
@@ -163,25 +225,34 @@ test("owner grant listings read only the indexed owner grants", async () => {
|
|
|
163
225
|
});
|
|
164
226
|
|
|
165
227
|
test("owner grant listings propagate indexed grant read failures", async () => {
|
|
166
|
-
const root = await mkdtemp(
|
|
228
|
+
const root = await mkdtemp(
|
|
229
|
+
path.join(tmpdir(), "remnic-support-grant-list-error-"),
|
|
230
|
+
);
|
|
167
231
|
try {
|
|
168
232
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
169
|
-
const store = new SupportPassportGrantStore({
|
|
233
|
+
const store = new SupportPassportGrantStore({
|
|
234
|
+
memoryDir: root,
|
|
235
|
+
now: () => now,
|
|
236
|
+
});
|
|
170
237
|
const created = await store.create({
|
|
171
238
|
namespace: "alice",
|
|
172
239
|
principal: "owner:alice",
|
|
173
240
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
174
241
|
expiresAt: new Date(now.getTime() + 3_600_000).toISOString(),
|
|
175
242
|
});
|
|
176
|
-
const inspected = store as unknown as {
|
|
243
|
+
const inspected = store as unknown as {
|
|
244
|
+
readState(grantId: string): Promise<unknown>;
|
|
245
|
+
};
|
|
177
246
|
inspected.readState = async (grantId) => {
|
|
178
247
|
assert.equal(grantId, created.state.grantId);
|
|
179
|
-
throw Object.assign(new Error("simulated grant read failure"), {
|
|
248
|
+
throw Object.assign(new Error("simulated grant read failure"), {
|
|
249
|
+
code: "EIO",
|
|
250
|
+
});
|
|
180
251
|
};
|
|
181
252
|
|
|
182
253
|
await assert.rejects(
|
|
183
254
|
store.listForOwner("alice", "owner:alice"),
|
|
184
|
-
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO"
|
|
255
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
185
256
|
);
|
|
186
257
|
} finally {
|
|
187
258
|
await rm(root, { recursive: true, force: true });
|
|
@@ -189,13 +260,16 @@ test("owner grant listings propagate indexed grant read failures", async () => {
|
|
|
189
260
|
});
|
|
190
261
|
|
|
191
262
|
test("owner grant listings put active links before inactive history", async () => {
|
|
192
|
-
const root = await mkdtemp(
|
|
263
|
+
const root = await mkdtemp(
|
|
264
|
+
path.join(tmpdir(), "remnic-support-grant-list-order-"),
|
|
265
|
+
);
|
|
193
266
|
try {
|
|
194
267
|
let currentTime = Date.parse("2026-08-11T12:00:00.000Z");
|
|
195
268
|
let nextGrantId = 1;
|
|
196
269
|
const store = new SupportPassportGrantStore({
|
|
197
270
|
memoryDir: root,
|
|
198
|
-
makeGrantId: () =>
|
|
271
|
+
makeGrantId: () =>
|
|
272
|
+
`00000000-0000-4000-8000-${String(nextGrantId++).padStart(12, "0")}`,
|
|
199
273
|
now: () => new Date(currentTime),
|
|
200
274
|
});
|
|
201
275
|
const input = {
|
|
@@ -206,7 +280,10 @@ test("owner grant listings put active links before inactive history", async () =
|
|
|
206
280
|
};
|
|
207
281
|
const inactive = await store.create(input);
|
|
208
282
|
currentTime += 1_000;
|
|
209
|
-
const active = await store.create({
|
|
283
|
+
const active = await store.create({
|
|
284
|
+
...input,
|
|
285
|
+
expiresAt: new Date(currentTime + 3_600_000).toISOString(),
|
|
286
|
+
});
|
|
210
287
|
await store.revoke({
|
|
211
288
|
grantId: inactive.state.grantId,
|
|
212
289
|
namespace: input.namespace,
|
|
@@ -217,7 +294,7 @@ test("owner grant listings put active links before inactive history", async () =
|
|
|
217
294
|
|
|
218
295
|
assert.deepEqual(
|
|
219
296
|
listed.map((state) => state.grantId),
|
|
220
|
-
[active.state.grantId, inactive.state.grantId]
|
|
297
|
+
[active.state.grantId, inactive.state.grantId],
|
|
221
298
|
);
|
|
222
299
|
} finally {
|
|
223
300
|
await rm(root, { recursive: true, force: true });
|
|
@@ -225,10 +302,15 @@ test("owner grant listings put active links before inactive history", async () =
|
|
|
225
302
|
});
|
|
226
303
|
|
|
227
304
|
test("owner grant operations reject a noncanonical namespace and trim the principal", async () => {
|
|
228
|
-
const root = await mkdtemp(
|
|
305
|
+
const root = await mkdtemp(
|
|
306
|
+
path.join(tmpdir(), "remnic-support-grant-namespace-"),
|
|
307
|
+
);
|
|
229
308
|
try {
|
|
230
309
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
231
|
-
const store = new SupportPassportGrantStore({
|
|
310
|
+
const store = new SupportPassportGrantStore({
|
|
311
|
+
memoryDir: root,
|
|
312
|
+
now: () => now,
|
|
313
|
+
});
|
|
232
314
|
await assert.rejects(
|
|
233
315
|
store.create({
|
|
234
316
|
namespace: " alice ",
|
|
@@ -236,7 +318,8 @@ test("owner grant operations reject a noncanonical namespace and trim the princi
|
|
|
236
318
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
237
319
|
expiresAt: new Date(now.getTime() + 3_600_000).toISOString(),
|
|
238
320
|
}),
|
|
239
|
-
(error: unknown) =>
|
|
321
|
+
(error: unknown) =>
|
|
322
|
+
error instanceof SupportPassportError && error.code === "invalid_input",
|
|
240
323
|
);
|
|
241
324
|
const created = await store.create({
|
|
242
325
|
namespace: "alice",
|
|
@@ -246,8 +329,10 @@ test("owner grant operations reject a noncanonical namespace and trim the princi
|
|
|
246
329
|
});
|
|
247
330
|
|
|
248
331
|
assert.deepEqual(
|
|
249
|
-
(await store.listForOwner("alice", "owner:alice")).map(
|
|
250
|
-
|
|
332
|
+
(await store.listForOwner("alice", "owner:alice")).map(
|
|
333
|
+
(state) => state.grantId,
|
|
334
|
+
),
|
|
335
|
+
[created.state.grantId],
|
|
251
336
|
);
|
|
252
337
|
const revoked = await store.revoke({
|
|
253
338
|
grantId: created.state.grantId,
|
|
@@ -262,13 +347,16 @@ test("owner grant operations reject a noncanonical namespace and trim the princi
|
|
|
262
347
|
});
|
|
263
348
|
|
|
264
349
|
test("owner grant history stays bounded while an inactive link frees capacity", async () => {
|
|
265
|
-
const root = await mkdtemp(
|
|
350
|
+
const root = await mkdtemp(
|
|
351
|
+
path.join(tmpdir(), "remnic-support-grant-history-"),
|
|
352
|
+
);
|
|
266
353
|
try {
|
|
267
354
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
268
355
|
let nextGrantId = 1;
|
|
269
356
|
const store = new SupportPassportGrantStore({
|
|
270
357
|
memoryDir: root,
|
|
271
|
-
makeGrantId: () =>
|
|
358
|
+
makeGrantId: () =>
|
|
359
|
+
`00000000-0000-4000-8000-${String(nextGrantId++).padStart(12, "0")}`,
|
|
272
360
|
now: () => now,
|
|
273
361
|
});
|
|
274
362
|
const input = {
|
|
@@ -282,7 +370,8 @@ test("owner grant history stays bounded while an inactive link frees capacity",
|
|
|
282
370
|
|
|
283
371
|
await assert.rejects(
|
|
284
372
|
store.create(input),
|
|
285
|
-
(error: unknown) =>
|
|
373
|
+
(error: unknown) =>
|
|
374
|
+
error instanceof SupportPassportError && error.code === "invalid_input",
|
|
286
375
|
);
|
|
287
376
|
await store.revoke({
|
|
288
377
|
grantId: first.state.grantId,
|
|
@@ -290,7 +379,13 @@ test("owner grant history stays bounded while an inactive link frees capacity",
|
|
|
290
379
|
principal: input.principal,
|
|
291
380
|
expectedStateVersion: first.state.stateVersion,
|
|
292
381
|
});
|
|
293
|
-
const firstGrantPath = path.join(
|
|
382
|
+
const firstGrantPath = path.join(
|
|
383
|
+
root,
|
|
384
|
+
"state",
|
|
385
|
+
"support-passport",
|
|
386
|
+
"grants",
|
|
387
|
+
`${first.state.grantId}.json`,
|
|
388
|
+
);
|
|
294
389
|
const inspected = store as unknown as {
|
|
295
390
|
readState(grantId: string): Promise<unknown>;
|
|
296
391
|
writeOwnerIndex(ownerHash: string, grantIds: string[]): Promise<void>;
|
|
@@ -298,25 +393,44 @@ test("owner grant history stays bounded while an inactive link frees capacity",
|
|
|
298
393
|
const readState = inspected.readState.bind(store);
|
|
299
394
|
const writeOwnerIndex = inspected.writeOwnerIndex.bind(store);
|
|
300
395
|
inspected.writeOwnerIndex = async () => {
|
|
301
|
-
throw Object.assign(new Error("simulated owner index write failure"), {
|
|
396
|
+
throw Object.assign(new Error("simulated owner index write failure"), {
|
|
397
|
+
code: "EIO",
|
|
398
|
+
});
|
|
302
399
|
};
|
|
303
|
-
await assert.rejects(
|
|
400
|
+
await assert.rejects(
|
|
401
|
+
store.create(input),
|
|
402
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
403
|
+
);
|
|
304
404
|
inspected.writeOwnerIndex = writeOwnerIndex;
|
|
305
405
|
assert.equal((await lstat(firstGrantPath)).isFile(), true);
|
|
306
|
-
assert.equal(
|
|
406
|
+
assert.equal(
|
|
407
|
+
(await store.listForOwner(input.namespace, input.principal)).length,
|
|
408
|
+
100,
|
|
409
|
+
);
|
|
307
410
|
|
|
308
411
|
inspected.readState = async (grantId) => {
|
|
309
412
|
if (grantId === first.state.grantId) {
|
|
310
|
-
throw Object.assign(new Error("simulated indexed grant read failure"), {
|
|
413
|
+
throw Object.assign(new Error("simulated indexed grant read failure"), {
|
|
414
|
+
code: "EIO",
|
|
415
|
+
});
|
|
311
416
|
}
|
|
312
417
|
return await readState(grantId);
|
|
313
418
|
};
|
|
314
|
-
await assert.rejects(
|
|
419
|
+
await assert.rejects(
|
|
420
|
+
store.create(input),
|
|
421
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
422
|
+
);
|
|
315
423
|
inspected.readState = readState;
|
|
316
|
-
assert.equal(
|
|
424
|
+
assert.equal(
|
|
425
|
+
(await store.listForOwner(input.namespace, input.principal)).length,
|
|
426
|
+
100,
|
|
427
|
+
);
|
|
317
428
|
const evictedGrantLocks: string[] = [];
|
|
318
429
|
const lockAwareStore = store as unknown as {
|
|
319
|
-
withGrantLock<T>(
|
|
430
|
+
withGrantLock<T>(
|
|
431
|
+
grantId: string,
|
|
432
|
+
task: (lock: { refresh(): Promise<boolean> }) => Promise<T>,
|
|
433
|
+
): Promise<T>;
|
|
320
434
|
};
|
|
321
435
|
const withGrantLock = lockAwareStore.withGrantLock.bind(store);
|
|
322
436
|
lockAwareStore.withGrantLock = async (grantId, task) => {
|
|
@@ -328,21 +442,26 @@ test("owner grant history stays bounded while an inactive link frees capacity",
|
|
|
328
442
|
assert.equal(listed.length, 100);
|
|
329
443
|
assert.equal(
|
|
330
444
|
listed.some((state) => state.grantId === first.state.grantId),
|
|
331
|
-
false
|
|
445
|
+
false,
|
|
332
446
|
);
|
|
333
447
|
assert.equal(
|
|
334
448
|
listed.some((state) => state.grantId === replacement.state.grantId),
|
|
335
|
-
true
|
|
449
|
+
true,
|
|
336
450
|
);
|
|
337
451
|
assert.deepEqual(evictedGrantLocks, [first.state.grantId]);
|
|
338
|
-
await assert.rejects(
|
|
452
|
+
await assert.rejects(
|
|
453
|
+
lstat(firstGrantPath),
|
|
454
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "ENOENT",
|
|
455
|
+
);
|
|
339
456
|
} finally {
|
|
340
457
|
await rm(root, { recursive: true, force: true });
|
|
341
458
|
}
|
|
342
459
|
});
|
|
343
460
|
|
|
344
461
|
test("grant creation accepts a state write that committed before its durability error", async () => {
|
|
345
|
-
const root = await mkdtemp(
|
|
462
|
+
const root = await mkdtemp(
|
|
463
|
+
path.join(tmpdir(), "remnic-support-grant-state-commit-"),
|
|
464
|
+
);
|
|
346
465
|
try {
|
|
347
466
|
const grantId = "00000000-0000-4000-8000-000000000001";
|
|
348
467
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
@@ -362,7 +481,9 @@ test("grant creation accepts a state write that committed before its durability
|
|
|
362
481
|
const writeState = inspected.writeState.bind(store);
|
|
363
482
|
inspected.writeState = async (state, requireAbsent) => {
|
|
364
483
|
await writeState(state, requireAbsent);
|
|
365
|
-
throw Object.assign(new Error("simulated post-commit state error"), {
|
|
484
|
+
throw Object.assign(new Error("simulated post-commit state error"), {
|
|
485
|
+
code: "EIO",
|
|
486
|
+
});
|
|
366
487
|
};
|
|
367
488
|
|
|
368
489
|
const created = await store.create({
|
|
@@ -374,8 +495,10 @@ test("grant creation accepts a state write that committed before its durability
|
|
|
374
495
|
|
|
375
496
|
assert.equal(created.state.grantId, grantId);
|
|
376
497
|
assert.deepEqual(
|
|
377
|
-
(await store.listForOwner("alice", "owner:alice")).map(
|
|
378
|
-
|
|
498
|
+
(await store.listForOwner("alice", "owner:alice")).map(
|
|
499
|
+
(state) => state.grantId,
|
|
500
|
+
),
|
|
501
|
+
[grantId],
|
|
379
502
|
);
|
|
380
503
|
assert.deepEqual(
|
|
381
504
|
directorySyncs.map((directory) => path.basename(directory)),
|
|
@@ -427,18 +550,26 @@ test("grant creation fails closed when a committed state cannot be re-synced", a
|
|
|
427
550
|
});
|
|
428
551
|
|
|
429
552
|
test("grant creation re-syncs an owner index write that committed before its durability error", async () => {
|
|
430
|
-
const root = await mkdtemp(
|
|
553
|
+
const root = await mkdtemp(
|
|
554
|
+
path.join(tmpdir(), "remnic-support-grant-index-commit-"),
|
|
555
|
+
);
|
|
431
556
|
try {
|
|
432
557
|
const grantId = "00000000-0000-4000-8000-000000000001";
|
|
433
558
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
434
|
-
const store = new SupportPassportGrantStore({
|
|
559
|
+
const store = new SupportPassportGrantStore({
|
|
560
|
+
memoryDir: root,
|
|
561
|
+
makeGrantId: () => grantId,
|
|
562
|
+
now: () => now,
|
|
563
|
+
});
|
|
435
564
|
const inspected = store as unknown as {
|
|
436
565
|
writeOwnerIndex(ownerHash: string, grantIds: string[]): Promise<void>;
|
|
437
566
|
};
|
|
438
567
|
const writeOwnerIndex = inspected.writeOwnerIndex.bind(store);
|
|
439
568
|
inspected.writeOwnerIndex = async (ownerHash, grantIds) => {
|
|
440
569
|
await writeOwnerIndex(ownerHash, grantIds);
|
|
441
|
-
throw Object.assign(new Error("simulated post-commit index error"), {
|
|
570
|
+
throw Object.assign(new Error("simulated post-commit index error"), {
|
|
571
|
+
code: "EIO",
|
|
572
|
+
});
|
|
442
573
|
};
|
|
443
574
|
|
|
444
575
|
const created = await store.create({
|
|
@@ -450,8 +581,10 @@ test("grant creation re-syncs an owner index write that committed before its dur
|
|
|
450
581
|
|
|
451
582
|
assert.equal(created.state.grantId, grantId);
|
|
452
583
|
assert.deepEqual(
|
|
453
|
-
(await store.listForOwner("alice", "owner:alice")).map(
|
|
454
|
-
|
|
584
|
+
(await store.listForOwner("alice", "owner:alice")).map(
|
|
585
|
+
(state) => state.grantId,
|
|
586
|
+
),
|
|
587
|
+
[grantId],
|
|
455
588
|
);
|
|
456
589
|
} finally {
|
|
457
590
|
await rm(root, { recursive: true, force: true });
|
|
@@ -514,7 +647,8 @@ test("owner grant rollover ignores a foreign derived-index entry", async () => {
|
|
|
514
647
|
let nextGrantId = 1;
|
|
515
648
|
const store = new SupportPassportGrantStore({
|
|
516
649
|
memoryDir: root,
|
|
517
|
-
makeGrantId: () =>
|
|
650
|
+
makeGrantId: () =>
|
|
651
|
+
`00000000-0000-4000-8000-${String(nextGrantId++).padStart(12, "0")}`,
|
|
518
652
|
now: () => now,
|
|
519
653
|
});
|
|
520
654
|
const common = {
|
|
@@ -524,7 +658,9 @@ test("owner grant rollover ignores a foreign derived-index entry", async () => {
|
|
|
524
658
|
};
|
|
525
659
|
const aliceGrants = [];
|
|
526
660
|
for (let index = 0; index < 99; index += 1) {
|
|
527
|
-
aliceGrants.push(
|
|
661
|
+
aliceGrants.push(
|
|
662
|
+
await store.create({ ...common, principal: "owner:alice" }),
|
|
663
|
+
);
|
|
528
664
|
}
|
|
529
665
|
const bob = await store.create({ ...common, principal: "owner:bob" });
|
|
530
666
|
await store.revoke({
|
|
@@ -538,12 +674,21 @@ test("owner grant rollover ignores a foreign derived-index entry", async () => {
|
|
|
538
674
|
};
|
|
539
675
|
const firstAliceGrant = aliceGrants[0];
|
|
540
676
|
assert.ok(firstAliceGrant);
|
|
541
|
-
const aliceOwnerHash = inspected.ownerHash(
|
|
677
|
+
const aliceOwnerHash = inspected.ownerHash(
|
|
678
|
+
common.namespace,
|
|
679
|
+
firstAliceGrant.state.principalHash,
|
|
680
|
+
);
|
|
542
681
|
await inspected.writeOwnerIndex(aliceOwnerHash, [
|
|
543
682
|
...aliceGrants.map((grant) => grant.state.grantId),
|
|
544
683
|
bob.state.grantId,
|
|
545
684
|
]);
|
|
546
|
-
const bobGrantPath = path.join(
|
|
685
|
+
const bobGrantPath = path.join(
|
|
686
|
+
root,
|
|
687
|
+
"state",
|
|
688
|
+
"support-passport",
|
|
689
|
+
"grants",
|
|
690
|
+
`${bob.state.grantId}.json`,
|
|
691
|
+
);
|
|
547
692
|
|
|
548
693
|
const replacement = await store.create({ ...common, principal: "owner:alice" });
|
|
549
694
|
|
|
@@ -556,7 +701,9 @@ test("owner grant rollover ignores a foreign derived-index entry", async () => {
|
|
|
556
701
|
});
|
|
557
702
|
|
|
558
703
|
test("owner grant history uses one expiry cutoff while it prunes old links", async () => {
|
|
559
|
-
const root = await mkdtemp(
|
|
704
|
+
const root = await mkdtemp(
|
|
705
|
+
path.join(tmpdir(), "remnic-support-grant-cutoff-"),
|
|
706
|
+
);
|
|
560
707
|
try {
|
|
561
708
|
const initialTime = Date.parse("2026-08-11T12:00:00.000Z");
|
|
562
709
|
let currentTime = initialTime;
|
|
@@ -565,10 +712,13 @@ test("owner grant history uses one expiry cutoff while it prunes old links", asy
|
|
|
565
712
|
let nextGrantId = 1;
|
|
566
713
|
const store = new SupportPassportGrantStore({
|
|
567
714
|
memoryDir: root,
|
|
568
|
-
makeGrantId: () =>
|
|
715
|
+
makeGrantId: () =>
|
|
716
|
+
`00000000-0000-4000-8000-${String(nextGrantId++).padStart(12, "0")}`,
|
|
569
717
|
now: () => {
|
|
570
718
|
partitionCalls += 1;
|
|
571
|
-
return new Date(
|
|
719
|
+
return new Date(
|
|
720
|
+
currentTime + (crossExpiry && partitionCalls > 101 ? 1 : 0),
|
|
721
|
+
);
|
|
572
722
|
},
|
|
573
723
|
});
|
|
574
724
|
const common = {
|
|
@@ -610,11 +760,11 @@ test("owner grant history uses one expiry cutoff while it prunes old links", asy
|
|
|
610
760
|
assert.equal(new Set(listed.map((state) => state.grantId)).size, 100);
|
|
611
761
|
assert.equal(
|
|
612
762
|
listed.some((state) => state.grantId === crossing.state.grantId),
|
|
613
|
-
true
|
|
763
|
+
true,
|
|
614
764
|
);
|
|
615
765
|
assert.equal(
|
|
616
766
|
listed.some((state) => state.grantId === created.state.grantId),
|
|
617
|
-
true
|
|
767
|
+
true,
|
|
618
768
|
);
|
|
619
769
|
} finally {
|
|
620
770
|
await rm(root, { recursive: true, force: true });
|
|
@@ -626,7 +776,10 @@ test("grant mutations report a storage conflict when the file lock is busy", asy
|
|
|
626
776
|
try {
|
|
627
777
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
628
778
|
const rejectLock = (async (_lockPath, _options, task) =>
|
|
629
|
-
await task(false, {
|
|
779
|
+
await task(false, {
|
|
780
|
+
refresh: async () => false,
|
|
781
|
+
failure: "timeout",
|
|
782
|
+
})) as typeof withHeldFileLock;
|
|
630
783
|
const store = new SupportPassportGrantStore({
|
|
631
784
|
memoryDir: root,
|
|
632
785
|
now: () => now,
|
|
@@ -641,7 +794,9 @@ test("grant mutations report a storage conflict when the file lock is busy", asy
|
|
|
641
794
|
expiresAt: new Date(now.getTime() + 300_000).toISOString(),
|
|
642
795
|
}),
|
|
643
796
|
(error: unknown) =>
|
|
644
|
-
error instanceof SupportPassportError &&
|
|
797
|
+
error instanceof SupportPassportError &&
|
|
798
|
+
error.code === "storage_conflict" &&
|
|
799
|
+
error.status === 409,
|
|
645
800
|
);
|
|
646
801
|
} finally {
|
|
647
802
|
await rm(root, { recursive: true, force: true });
|
|
@@ -652,7 +807,9 @@ test("grant creation rechecks the owner lock immediately before commit", async (
|
|
|
652
807
|
const subject = await makeSubject();
|
|
653
808
|
try {
|
|
654
809
|
const card = await createActiveCard(subject);
|
|
655
|
-
const originalRead = subject.aliceStorage.readAllMemories.bind(
|
|
810
|
+
const originalRead = subject.aliceStorage.readAllMemories.bind(
|
|
811
|
+
subject.aliceStorage,
|
|
812
|
+
);
|
|
656
813
|
let replacedLock = false;
|
|
657
814
|
subject.aliceStorage.readAllMemories = async (...args) => {
|
|
658
815
|
const memories = await originalRead(...args);
|
|
@@ -662,7 +819,10 @@ test("grant creation rechecks the owner lock immediately before commit", async (
|
|
|
662
819
|
namespace: "alice",
|
|
663
820
|
principal: "owner:alice",
|
|
664
821
|
});
|
|
665
|
-
await writeFile(
|
|
822
|
+
await writeFile(
|
|
823
|
+
lockPath,
|
|
824
|
+
`${process.pid} 00000000-0000-4000-8000-000000000000 peer\n`,
|
|
825
|
+
);
|
|
666
826
|
}
|
|
667
827
|
return memories;
|
|
668
828
|
};
|
|
@@ -674,9 +834,53 @@ test("grant creation rechecks the owner lock immediately before commit", async (
|
|
|
674
834
|
expiresAt: expiryAfter(subject, 300_000),
|
|
675
835
|
}),
|
|
676
836
|
(error: unknown) =>
|
|
677
|
-
error instanceof SupportPassportError &&
|
|
837
|
+
error instanceof SupportPassportError &&
|
|
838
|
+
error.code === "storage_conflict" &&
|
|
839
|
+
error.status === 409,
|
|
840
|
+
);
|
|
841
|
+
assert.deepEqual(
|
|
842
|
+
await subject.grantService.listGrants({ principal: "owner:alice" }),
|
|
843
|
+
[],
|
|
844
|
+
);
|
|
845
|
+
} finally {
|
|
846
|
+
await subject.cleanup();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test("grant creation does not report a commit before owner indexing completes", async () => {
|
|
851
|
+
const subject = await makeSubject();
|
|
852
|
+
try {
|
|
853
|
+
const active = await createActiveCard(subject);
|
|
854
|
+
const inspected = subject.grantStore as unknown as {
|
|
855
|
+
addToOwnerIndex(): Promise<void>;
|
|
856
|
+
};
|
|
857
|
+
inspected.addToOwnerIndex = async () => {
|
|
858
|
+
throw new Error("simulated owner index failure");
|
|
859
|
+
};
|
|
860
|
+
let committed = 0;
|
|
861
|
+
|
|
862
|
+
await assert.rejects(
|
|
863
|
+
subject.grantService.createGrant(
|
|
864
|
+
{
|
|
865
|
+
principal: "owner:alice",
|
|
866
|
+
cards: [{ cardId: active.cardId, revision: active.revision }],
|
|
867
|
+
expiresAt: new Date(
|
|
868
|
+
subject.now().getTime() + 3_600_000,
|
|
869
|
+
).toISOString(),
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
onCommitted: () => {
|
|
873
|
+
committed += 1;
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
),
|
|
877
|
+
/simulated owner index failure/,
|
|
878
|
+
);
|
|
879
|
+
assert.equal(committed, 0);
|
|
880
|
+
assert.deepEqual(
|
|
881
|
+
await subject.grantService.listGrants({ principal: "owner:alice" }),
|
|
882
|
+
[],
|
|
678
883
|
);
|
|
679
|
-
assert.deepEqual(await subject.grantService.listGrants({ principal: "owner:alice" }), []);
|
|
680
884
|
} finally {
|
|
681
885
|
await subject.cleanup();
|
|
682
886
|
}
|
|
@@ -798,7 +1002,9 @@ test("grant creation measures its minimum lifetime from request receipt", async
|
|
|
798
1002
|
try {
|
|
799
1003
|
const card = await createActiveCard(subject);
|
|
800
1004
|
const expiresAt = expiryAfter(subject, 300_000);
|
|
801
|
-
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
1005
|
+
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
1006
|
+
subject.aliceStorage,
|
|
1007
|
+
);
|
|
802
1008
|
let delayed = false;
|
|
803
1009
|
subject.aliceStorage.readAllMemories = async (...args) => {
|
|
804
1010
|
const memories = await readAllMemories(...args);
|
|
@@ -883,19 +1089,25 @@ test("directory sync ignores only explicit unsupported errors", async () => {
|
|
|
883
1089
|
|
|
884
1090
|
await assert.rejects(
|
|
885
1091
|
syncDirectoryForDurability("/unused", failingOpen("EIO")),
|
|
886
|
-
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO"
|
|
1092
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
887
1093
|
);
|
|
888
1094
|
await syncDirectoryForDurability("/unused", failingOpen("EINVAL"));
|
|
889
1095
|
});
|
|
890
1096
|
|
|
891
1097
|
test("grant creation aborts before replacing an owner index after lock ownership is lost", async () => {
|
|
892
|
-
const root = await mkdtemp(
|
|
1098
|
+
const root = await mkdtemp(
|
|
1099
|
+
path.join(tmpdir(), "remnic-support-grant-lock-loss-"),
|
|
1100
|
+
);
|
|
893
1101
|
try {
|
|
894
|
-
const grantIds = [
|
|
1102
|
+
const grantIds = [
|
|
1103
|
+
"00000000-0000-4000-8000-000000000001",
|
|
1104
|
+
"00000000-0000-4000-8000-000000000002",
|
|
1105
|
+
];
|
|
895
1106
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
896
1107
|
const store = new SupportPassportGrantStore({
|
|
897
1108
|
memoryDir: root,
|
|
898
|
-
makeGrantId: () =>
|
|
1109
|
+
makeGrantId: () =>
|
|
1110
|
+
grantIds.shift() ?? "00000000-0000-4000-8000-000000000003",
|
|
899
1111
|
now: () => now,
|
|
900
1112
|
});
|
|
901
1113
|
const input = {
|
|
@@ -907,7 +1119,9 @@ test("grant creation aborts before replacing an owner index after lock ownership
|
|
|
907
1119
|
const first = await store.create(input);
|
|
908
1120
|
let refreshes = 0;
|
|
909
1121
|
const inspected = store as unknown as {
|
|
910
|
-
withMutationLock<T>(
|
|
1122
|
+
withMutationLock<T>(
|
|
1123
|
+
task: (lock: { refresh(): Promise<boolean> }) => Promise<T>,
|
|
1124
|
+
): Promise<T>;
|
|
911
1125
|
};
|
|
912
1126
|
inspected.withMutationLock = async (task) =>
|
|
913
1127
|
await task({
|
|
@@ -919,12 +1133,16 @@ test("grant creation aborts before replacing an owner index after lock ownership
|
|
|
919
1133
|
|
|
920
1134
|
await assert.rejects(
|
|
921
1135
|
store.create(input),
|
|
922
|
-
(error: unknown) =>
|
|
1136
|
+
(error: unknown) =>
|
|
1137
|
+
error instanceof SupportPassportError &&
|
|
1138
|
+
error.code === "storage_conflict",
|
|
923
1139
|
);
|
|
924
1140
|
assert.equal(refreshes, 2);
|
|
925
1141
|
assert.deepEqual(
|
|
926
|
-
(await store.listForOwner("alice", "owner:alice")).map(
|
|
927
|
-
|
|
1142
|
+
(await store.listForOwner("alice", "owner:alice")).map(
|
|
1143
|
+
(state) => state.grantId,
|
|
1144
|
+
),
|
|
1145
|
+
[first.state.grantId],
|
|
928
1146
|
);
|
|
929
1147
|
} finally {
|
|
930
1148
|
await rm(root, { recursive: true, force: true });
|
|
@@ -1332,13 +1550,20 @@ test("one owner's index transaction does not block another owner", async () => {
|
|
|
1332
1550
|
});
|
|
1333
1551
|
|
|
1334
1552
|
test("grant creation rechecks its mutation lock after the commit callback", async () => {
|
|
1335
|
-
const root = await mkdtemp(
|
|
1553
|
+
const root = await mkdtemp(
|
|
1554
|
+
path.join(tmpdir(), "remnic-support-grant-callback-lock-"),
|
|
1555
|
+
);
|
|
1336
1556
|
try {
|
|
1337
1557
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
1338
|
-
const store = new SupportPassportGrantStore({
|
|
1558
|
+
const store = new SupportPassportGrantStore({
|
|
1559
|
+
memoryDir: root,
|
|
1560
|
+
now: () => now,
|
|
1561
|
+
});
|
|
1339
1562
|
let refreshes = 0;
|
|
1340
1563
|
const inspected = store as unknown as {
|
|
1341
|
-
withMutationLock<T>(
|
|
1564
|
+
withMutationLock<T>(
|
|
1565
|
+
task: (lock: { refresh(): Promise<boolean> }) => Promise<T>,
|
|
1566
|
+
): Promise<T>;
|
|
1342
1567
|
};
|
|
1343
1568
|
inspected.withMutationLock = async (task) =>
|
|
1344
1569
|
await task({
|
|
@@ -1356,9 +1581,11 @@ test("grant creation rechecks its mutation lock after the commit callback", asyn
|
|
|
1356
1581
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
1357
1582
|
expiresAt: new Date(now.getTime() + 300_000).toISOString(),
|
|
1358
1583
|
},
|
|
1359
|
-
async () => undefined
|
|
1584
|
+
async () => undefined,
|
|
1360
1585
|
),
|
|
1361
|
-
(error: unknown) =>
|
|
1586
|
+
(error: unknown) =>
|
|
1587
|
+
error instanceof SupportPassportError &&
|
|
1588
|
+
error.code === "storage_conflict",
|
|
1362
1589
|
);
|
|
1363
1590
|
assert.equal(refreshes, 2);
|
|
1364
1591
|
assert.deepEqual(await readdir(path.join(root, "state", "support-passport", "grants")), ["owners"]);
|
|
@@ -1368,10 +1595,15 @@ test("grant creation rechecks its mutation lock after the commit callback", asyn
|
|
|
1368
1595
|
});
|
|
1369
1596
|
|
|
1370
1597
|
test("grant revocation rechecks its mutation lock after the commit callback", async () => {
|
|
1371
|
-
const root = await mkdtemp(
|
|
1598
|
+
const root = await mkdtemp(
|
|
1599
|
+
path.join(tmpdir(), "remnic-support-revoke-callback-lock-"),
|
|
1600
|
+
);
|
|
1372
1601
|
try {
|
|
1373
1602
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
1374
|
-
const store = new SupportPassportGrantStore({
|
|
1603
|
+
const store = new SupportPassportGrantStore({
|
|
1604
|
+
memoryDir: root,
|
|
1605
|
+
now: () => now,
|
|
1606
|
+
});
|
|
1375
1607
|
const created = await store.create({
|
|
1376
1608
|
namespace: "alice",
|
|
1377
1609
|
principal: "owner:alice",
|
|
@@ -1380,7 +1612,10 @@ test("grant revocation rechecks its mutation lock after the commit callback", as
|
|
|
1380
1612
|
});
|
|
1381
1613
|
let refreshes = 0;
|
|
1382
1614
|
const inspected = store as unknown as {
|
|
1383
|
-
withGrantLock<T>(
|
|
1615
|
+
withGrantLock<T>(
|
|
1616
|
+
grantId: string,
|
|
1617
|
+
task: (lock: { refresh(): Promise<boolean> }) => Promise<T>,
|
|
1618
|
+
): Promise<T>;
|
|
1384
1619
|
};
|
|
1385
1620
|
inspected.withGrantLock = async (_grantId, task) =>
|
|
1386
1621
|
await task({
|
|
@@ -1392,13 +1627,23 @@ test("grant revocation rechecks its mutation lock after the commit callback", as
|
|
|
1392
1627
|
|
|
1393
1628
|
await assert.rejects(
|
|
1394
1629
|
store.revoke(
|
|
1395
|
-
{
|
|
1396
|
-
|
|
1630
|
+
{
|
|
1631
|
+
grantId: created.state.grantId,
|
|
1632
|
+
namespace: "alice",
|
|
1633
|
+
principal: "owner:alice",
|
|
1634
|
+
},
|
|
1635
|
+
{ beforeCommit: async () => undefined },
|
|
1397
1636
|
),
|
|
1398
|
-
(error: unknown) =>
|
|
1637
|
+
(error: unknown) =>
|
|
1638
|
+
error instanceof SupportPassportError &&
|
|
1639
|
+
error.code === "storage_conflict",
|
|
1399
1640
|
);
|
|
1400
1641
|
assert.equal(refreshes, 2);
|
|
1401
|
-
assert.equal(
|
|
1642
|
+
assert.equal(
|
|
1643
|
+
(await store.authenticate(created.state.grantId, created.secret))
|
|
1644
|
+
.revokedAt,
|
|
1645
|
+
undefined,
|
|
1646
|
+
);
|
|
1402
1647
|
} finally {
|
|
1403
1648
|
await rm(root, { recursive: true, force: true });
|
|
1404
1649
|
}
|
|
@@ -1439,7 +1684,9 @@ test("a helper sees only the selected active card through a valid secret", async
|
|
|
1439
1684
|
|
|
1440
1685
|
test("grants reject cards owned by another namespace in shared storage", async () => {
|
|
1441
1686
|
StorageManager.clearAllStaticCaches();
|
|
1442
|
-
const root = await mkdtemp(
|
|
1687
|
+
const root = await mkdtemp(
|
|
1688
|
+
path.join(tmpdir(), "remnic-support-grant-card-scope-"),
|
|
1689
|
+
);
|
|
1443
1690
|
try {
|
|
1444
1691
|
const storage = new StorageManager(path.join(root, "shared-storage"));
|
|
1445
1692
|
await storage.ensureDirectories();
|
|
@@ -1450,7 +1697,10 @@ test("grants reject cards owned by another namespace in shared storage", async (
|
|
|
1450
1697
|
storage,
|
|
1451
1698
|
});
|
|
1452
1699
|
const cardService = new SupportPassportCardService({ resolveOwner, now });
|
|
1453
|
-
const grantStore = new SupportPassportGrantStore({
|
|
1700
|
+
const grantStore = new SupportPassportGrantStore({
|
|
1701
|
+
memoryDir: path.join(root, "grants"),
|
|
1702
|
+
now,
|
|
1703
|
+
});
|
|
1454
1704
|
const grantService = new SupportPassportGrantService({
|
|
1455
1705
|
grantStore,
|
|
1456
1706
|
resolveOwner,
|
|
@@ -1476,7 +1726,9 @@ test("grants reject cards owned by another namespace in shared storage", async (
|
|
|
1476
1726
|
cards: [{ cardId: aliceCard.cardId, revision: aliceCard.revision }],
|
|
1477
1727
|
expiresAt: "2026-08-11T13:00:00.000Z",
|
|
1478
1728
|
}),
|
|
1479
|
-
(error: unknown) =>
|
|
1729
|
+
(error: unknown) =>
|
|
1730
|
+
error instanceof SupportPassportError &&
|
|
1731
|
+
error.code === "invalid_card_status",
|
|
1480
1732
|
);
|
|
1481
1733
|
const forged = await grantStore.create({
|
|
1482
1734
|
namespace: "bob",
|
|
@@ -1485,8 +1737,12 @@ test("grants reject cards owned by another namespace in shared storage", async (
|
|
|
1485
1737
|
expiresAt: "2026-08-11T13:00:00.000Z",
|
|
1486
1738
|
});
|
|
1487
1739
|
await assert.rejects(
|
|
1488
|
-
grantService.readGrant({
|
|
1489
|
-
|
|
1740
|
+
grantService.readGrant({
|
|
1741
|
+
grantId: forged.state.grantId,
|
|
1742
|
+
secret: forged.secret,
|
|
1743
|
+
}),
|
|
1744
|
+
(error: unknown) =>
|
|
1745
|
+
error instanceof SupportPassportError && error.code === "grant_stale",
|
|
1490
1746
|
);
|
|
1491
1747
|
} finally {
|
|
1492
1748
|
StorageManager.clearAllStaticCaches();
|
|
@@ -1496,14 +1752,23 @@ test("grants reject cards owned by another namespace in shared storage", async (
|
|
|
1496
1752
|
|
|
1497
1753
|
test("grants reject cards owned by another principal inside a shared namespace", async () => {
|
|
1498
1754
|
StorageManager.clearAllStaticCaches();
|
|
1499
|
-
const root = await mkdtemp(
|
|
1755
|
+
const root = await mkdtemp(
|
|
1756
|
+
path.join(tmpdir(), "remnic-support-grant-owner-scope-"),
|
|
1757
|
+
);
|
|
1500
1758
|
try {
|
|
1501
1759
|
const storage = new StorageManager(path.join(root, "shared-storage"));
|
|
1502
1760
|
await storage.ensureDirectories();
|
|
1503
1761
|
const now = () => new Date("2026-08-11T12:00:00.000Z");
|
|
1504
|
-
const resolveOwner = async (principal: string) => ({
|
|
1762
|
+
const resolveOwner = async (principal: string) => ({
|
|
1763
|
+
principal,
|
|
1764
|
+
namespace: "team",
|
|
1765
|
+
storage,
|
|
1766
|
+
});
|
|
1505
1767
|
const cardService = new SupportPassportCardService({ resolveOwner, now });
|
|
1506
|
-
const grantStore = new SupportPassportGrantStore({
|
|
1768
|
+
const grantStore = new SupportPassportGrantStore({
|
|
1769
|
+
memoryDir: path.join(root, "grants"),
|
|
1770
|
+
now,
|
|
1771
|
+
});
|
|
1507
1772
|
const grantService = new SupportPassportGrantService({
|
|
1508
1773
|
grantStore,
|
|
1509
1774
|
resolveOwner,
|
|
@@ -1529,7 +1794,9 @@ test("grants reject cards owned by another principal inside a shared namespace",
|
|
|
1529
1794
|
cards: [{ cardId: aliceCard.cardId, revision: aliceCard.revision }],
|
|
1530
1795
|
expiresAt: "2026-08-11T13:00:00.000Z",
|
|
1531
1796
|
}),
|
|
1532
|
-
(error: unknown) =>
|
|
1797
|
+
(error: unknown) =>
|
|
1798
|
+
error instanceof SupportPassportError &&
|
|
1799
|
+
error.code === "invalid_card_status",
|
|
1533
1800
|
);
|
|
1534
1801
|
const forged = await grantStore.create({
|
|
1535
1802
|
namespace: "team",
|
|
@@ -1538,8 +1805,12 @@ test("grants reject cards owned by another principal inside a shared namespace",
|
|
|
1538
1805
|
expiresAt: "2026-08-11T13:00:00.000Z",
|
|
1539
1806
|
});
|
|
1540
1807
|
await assert.rejects(
|
|
1541
|
-
grantService.readGrant({
|
|
1542
|
-
|
|
1808
|
+
grantService.readGrant({
|
|
1809
|
+
grantId: forged.state.grantId,
|
|
1810
|
+
secret: forged.secret,
|
|
1811
|
+
}),
|
|
1812
|
+
(error: unknown) =>
|
|
1813
|
+
error instanceof SupportPassportError && error.code === "grant_stale",
|
|
1543
1814
|
);
|
|
1544
1815
|
} finally {
|
|
1545
1816
|
StorageManager.clearAllStaticCaches();
|
|
@@ -1581,6 +1852,10 @@ test("helper snapshots ignore colliding card IDs from other owners and reject ow
|
|
|
1581
1852
|
expiresAt: "2026-08-11T13:00:00.000Z",
|
|
1582
1853
|
});
|
|
1583
1854
|
const originalMemories = await storage.readAllMemories();
|
|
1855
|
+
const getCorpusScanVersion = storage.getCorpusScanVersion.bind(storage);
|
|
1856
|
+
let testCorpusVersion = 0;
|
|
1857
|
+
storage.getCorpusScanVersion = () =>
|
|
1858
|
+
`${getCorpusScanVersion()}:test-${testCorpusVersion}`;
|
|
1584
1859
|
const aliceMemory = originalMemories.find((memory) => memory.frontmatter.id === card.cardId);
|
|
1585
1860
|
assert.ok(aliceMemory);
|
|
1586
1861
|
const foreignCollision = {
|
|
@@ -1607,6 +1882,7 @@ test("helper snapshots ignore colliding card IDs from other owners and reject ow
|
|
|
1607
1882
|
path: path.join(storage.dir, "preferences", "owned-duplicate.md"),
|
|
1608
1883
|
};
|
|
1609
1884
|
storage.readAllMemories = async () => [...originalMemories, foreignCollision, ownedDuplicate];
|
|
1885
|
+
testCorpusVersion += 1;
|
|
1610
1886
|
await assert.rejects(
|
|
1611
1887
|
grantService.readGrant({ grantId: created.grant.grantId, secret: created.secret }),
|
|
1612
1888
|
(error: unknown) => error instanceof SupportPassportError && error.code === "card_data_invalid",
|
|
@@ -1621,7 +1897,9 @@ test("grant creation and card withdrawal cannot interleave", async () => {
|
|
|
1621
1897
|
const subject = await makeSubject();
|
|
1622
1898
|
try {
|
|
1623
1899
|
const card = await createActiveCard(subject);
|
|
1624
|
-
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
1900
|
+
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
1901
|
+
subject.aliceStorage,
|
|
1902
|
+
);
|
|
1625
1903
|
let releaseRead!: () => void;
|
|
1626
1904
|
let markReadStarted!: () => void;
|
|
1627
1905
|
const readStarted = new Promise<void>((resolve) => {
|
|
@@ -1681,9 +1959,14 @@ test("bad secrets return not found, while valid revoked and expired grants revea
|
|
|
1681
1959
|
});
|
|
1682
1960
|
|
|
1683
1961
|
await assert.rejects(
|
|
1684
|
-
subject.grantService.readGrant({
|
|
1962
|
+
subject.grantService.readGrant({
|
|
1963
|
+
grantId: created.grant.grantId,
|
|
1964
|
+
secret: "x".repeat(43),
|
|
1965
|
+
}),
|
|
1685
1966
|
(error: unknown) =>
|
|
1686
|
-
error instanceof SupportPassportError &&
|
|
1967
|
+
error instanceof SupportPassportError &&
|
|
1968
|
+
error.code === "grant_not_found" &&
|
|
1969
|
+
error.status === 404,
|
|
1687
1970
|
);
|
|
1688
1971
|
|
|
1689
1972
|
await assert.rejects(
|
|
@@ -1694,10 +1977,15 @@ test("bad secrets return not found, while valid revoked and expired grants revea
|
|
|
1694
1977
|
expectedStateVersion: created.grant.stateVersion + 1,
|
|
1695
1978
|
}),
|
|
1696
1979
|
(error: unknown) =>
|
|
1697
|
-
error instanceof SupportPassportError &&
|
|
1980
|
+
error instanceof SupportPassportError &&
|
|
1981
|
+
error.code === "state_conflict" &&
|
|
1982
|
+
error.status === 409,
|
|
1698
1983
|
);
|
|
1699
1984
|
|
|
1700
|
-
const peerStore = new SupportPassportGrantStore({
|
|
1985
|
+
const peerStore = new SupportPassportGrantStore({
|
|
1986
|
+
memoryDir: path.join(subject.root, "shared"),
|
|
1987
|
+
now: subject.now,
|
|
1988
|
+
});
|
|
1701
1989
|
const peerRevoked = await peerStore.revoke({
|
|
1702
1990
|
namespace: "alice",
|
|
1703
1991
|
principal: "owner:alice",
|
|
@@ -1713,8 +2001,14 @@ test("bad secrets return not found, while valid revoked and expired grants revea
|
|
|
1713
2001
|
assert.equal(repeated.stateVersion, peerRevoked.stateVersion);
|
|
1714
2002
|
assert.equal(repeated.revokedAt, peerRevoked.revokedAt);
|
|
1715
2003
|
await assert.rejects(
|
|
1716
|
-
subject.grantService.readGrant({
|
|
1717
|
-
|
|
2004
|
+
subject.grantService.readGrant({
|
|
2005
|
+
grantId: created.grant.grantId,
|
|
2006
|
+
secret: created.secret,
|
|
2007
|
+
}),
|
|
2008
|
+
(error: unknown) =>
|
|
2009
|
+
error instanceof SupportPassportError &&
|
|
2010
|
+
error.code === "grant_gone" &&
|
|
2011
|
+
error.status === 410,
|
|
1718
2012
|
);
|
|
1719
2013
|
|
|
1720
2014
|
const second = await subject.grantService.createGrant({
|
|
@@ -1724,9 +2018,14 @@ test("bad secrets return not found, while valid revoked and expired grants revea
|
|
|
1724
2018
|
});
|
|
1725
2019
|
subject.advance(300_000);
|
|
1726
2020
|
await assert.rejects(
|
|
1727
|
-
subject.grantService.readGrant({
|
|
2021
|
+
subject.grantService.readGrant({
|
|
2022
|
+
grantId: second.grant.grantId,
|
|
2023
|
+
secret: second.secret,
|
|
2024
|
+
}),
|
|
1728
2025
|
(error: unknown) =>
|
|
1729
|
-
error instanceof SupportPassportError &&
|
|
2026
|
+
error instanceof SupportPassportError &&
|
|
2027
|
+
error.code === "grant_expired" &&
|
|
2028
|
+
error.status === 410,
|
|
1730
2029
|
);
|
|
1731
2030
|
} finally {
|
|
1732
2031
|
await subject.cleanup();
|
|
@@ -1742,20 +2041,26 @@ test("a grant that expires during guide assembly does not return a guide", async
|
|
|
1742
2041
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
1743
2042
|
expiresAt: expiryAfter(subject, 300_000),
|
|
1744
2043
|
});
|
|
1745
|
-
const
|
|
2044
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2045
|
+
subject.grantStore,
|
|
2046
|
+
);
|
|
1746
2047
|
let advanced = false;
|
|
1747
|
-
subject.
|
|
1748
|
-
const
|
|
2048
|
+
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
2049
|
+
const state = await authenticate(grantId, secret);
|
|
1749
2050
|
if (!advanced) {
|
|
1750
2051
|
advanced = true;
|
|
1751
2052
|
subject.advance(300_000);
|
|
1752
2053
|
}
|
|
1753
|
-
return
|
|
2054
|
+
return state;
|
|
1754
2055
|
};
|
|
1755
2056
|
|
|
1756
2057
|
await assert.rejects(
|
|
1757
|
-
subject.grantService.readGrant({
|
|
1758
|
-
|
|
2058
|
+
subject.grantService.readGrant({
|
|
2059
|
+
grantId: created.grant.grantId,
|
|
2060
|
+
secret: created.secret,
|
|
2061
|
+
}),
|
|
2062
|
+
(error: unknown) =>
|
|
2063
|
+
error instanceof SupportPassportError && error.code === "grant_expired",
|
|
1759
2064
|
);
|
|
1760
2065
|
} finally {
|
|
1761
2066
|
await subject.cleanup();
|
|
@@ -1773,17 +2078,22 @@ test("revocation wins while an optimistic helper card read is still pending", as
|
|
|
1773
2078
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
1774
2079
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
1775
2080
|
});
|
|
1776
|
-
const peerStore = new SupportPassportGrantStore({
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
2081
|
+
const peerStore = new SupportPassportGrantStore({
|
|
2082
|
+
memoryDir: path.join(subject.root, "shared"),
|
|
2083
|
+
now: subject.now,
|
|
2084
|
+
});
|
|
2085
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2086
|
+
subject.grantStore,
|
|
2087
|
+
);
|
|
2088
|
+
let authentications = 0;
|
|
2089
|
+
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
2090
|
+
const state = await authenticate(grantId, secret);
|
|
2091
|
+
authentications += 1;
|
|
2092
|
+
if (authentications === 1) {
|
|
1783
2093
|
cardReadStarted.resolve();
|
|
1784
2094
|
await releaseCardRead.promise;
|
|
1785
2095
|
}
|
|
1786
|
-
return
|
|
2096
|
+
return state;
|
|
1787
2097
|
};
|
|
1788
2098
|
|
|
1789
2099
|
const readPromise = subject.grantService.readGrant({
|
|
@@ -1802,7 +2112,8 @@ test("revocation wins while an optimistic helper card read is still pending", as
|
|
|
1802
2112
|
releaseCardRead.resolve();
|
|
1803
2113
|
await assert.rejects(
|
|
1804
2114
|
readPromise,
|
|
1805
|
-
(error: unknown) =>
|
|
2115
|
+
(error: unknown) =>
|
|
2116
|
+
error instanceof SupportPassportError && error.code === "grant_gone",
|
|
1806
2117
|
);
|
|
1807
2118
|
} finally {
|
|
1808
2119
|
releaseCardRead.resolve();
|
|
@@ -1827,7 +2138,9 @@ test("helper reads for different grants do not block each other", async () => {
|
|
|
1827
2138
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
1828
2139
|
});
|
|
1829
2140
|
const firstStarted = Promise.withResolvers<void>();
|
|
1830
|
-
const authenticate = subject.grantStore.authenticate.bind(
|
|
2141
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2142
|
+
subject.grantStore,
|
|
2143
|
+
);
|
|
1831
2144
|
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
1832
2145
|
const state = await authenticate(grantId, secret);
|
|
1833
2146
|
if (grantId === first.grant.grantId) {
|
|
@@ -1836,7 +2149,10 @@ test("helper reads for different grants do not block each other", async () => {
|
|
|
1836
2149
|
}
|
|
1837
2150
|
return state;
|
|
1838
2151
|
};
|
|
1839
|
-
const firstRead = subject.grantService.readGrant({
|
|
2152
|
+
const firstRead = subject.grantService.readGrant({
|
|
2153
|
+
grantId: first.grant.grantId,
|
|
2154
|
+
secret: first.secret,
|
|
2155
|
+
});
|
|
1840
2156
|
await firstStarted.promise;
|
|
1841
2157
|
const secondGuide = await subject.grantService.readGrant({
|
|
1842
2158
|
grantId: second.grant.grantId,
|
|
@@ -1877,7 +2193,7 @@ test("helper guide snapshots reuse selected cards from the corpus snapshot", asy
|
|
|
1877
2193
|
|
|
1878
2194
|
assert.deepEqual(
|
|
1879
2195
|
guide.cards.map((card) => card.cardId),
|
|
1880
|
-
[firstCard.cardId, secondCard.cardId]
|
|
2196
|
+
[firstCard.cardId, secondCard.cardId],
|
|
1881
2197
|
);
|
|
1882
2198
|
} finally {
|
|
1883
2199
|
await subject.cleanup();
|
|
@@ -1895,7 +2211,9 @@ test("final helper guide assembly blocks card withdrawal", async () => {
|
|
|
1895
2211
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
1896
2212
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
1897
2213
|
});
|
|
1898
|
-
const authenticate = subject.grantStore.authenticate.bind(
|
|
2214
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2215
|
+
subject.grantStore,
|
|
2216
|
+
);
|
|
1899
2217
|
let authentications = 0;
|
|
1900
2218
|
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
1901
2219
|
const state = await authenticate(grantId, secret);
|
|
@@ -1947,7 +2265,9 @@ test("final helper guide assembly blocks owner revocation", async () => {
|
|
|
1947
2265
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
1948
2266
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
1949
2267
|
});
|
|
1950
|
-
const authenticate = subject.grantStore.authenticate.bind(
|
|
2268
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2269
|
+
subject.grantStore,
|
|
2270
|
+
);
|
|
1951
2271
|
let authentications = 0;
|
|
1952
2272
|
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
1953
2273
|
const state = await authenticate(grantId, secret);
|
|
@@ -1997,7 +2317,9 @@ test("helper guide assembly fails when owner-lock ownership is lost", async () =
|
|
|
1997
2317
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
1998
2318
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
1999
2319
|
});
|
|
2000
|
-
const authenticate = subject.grantStore.authenticate.bind(
|
|
2320
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2321
|
+
subject.grantStore,
|
|
2322
|
+
);
|
|
2001
2323
|
let authentications = 0;
|
|
2002
2324
|
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
2003
2325
|
const state = await authenticate(grantId, secret);
|
|
@@ -2007,14 +2329,22 @@ test("helper guide assembly fails when owner-lock ownership is lost", async () =
|
|
|
2007
2329
|
namespace: "alice",
|
|
2008
2330
|
principal: "owner:alice",
|
|
2009
2331
|
});
|
|
2010
|
-
await writeFile(
|
|
2332
|
+
await writeFile(
|
|
2333
|
+
lockPath,
|
|
2334
|
+
`${process.pid} 00000000-0000-4000-8000-000000000000 peer\n`,
|
|
2335
|
+
);
|
|
2011
2336
|
}
|
|
2012
2337
|
return state;
|
|
2013
2338
|
};
|
|
2014
2339
|
|
|
2015
2340
|
await assert.rejects(
|
|
2016
|
-
subject.grantService.readGrant({
|
|
2017
|
-
|
|
2341
|
+
subject.grantService.readGrant({
|
|
2342
|
+
grantId: created.grant.grantId,
|
|
2343
|
+
secret: created.secret,
|
|
2344
|
+
}),
|
|
2345
|
+
(error: unknown) =>
|
|
2346
|
+
error instanceof SupportPassportError &&
|
|
2347
|
+
error.code === "storage_conflict",
|
|
2018
2348
|
);
|
|
2019
2349
|
} finally {
|
|
2020
2350
|
await subject.cleanup();
|
|
@@ -2030,7 +2360,9 @@ test("helper guide assembly rechecks the owner lock after grant reauthentication
|
|
|
2030
2360
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2031
2361
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2032
2362
|
});
|
|
2033
|
-
const authenticate = subject.grantStore.authenticate.bind(
|
|
2363
|
+
const authenticate = subject.grantStore.authenticate.bind(
|
|
2364
|
+
subject.grantStore,
|
|
2365
|
+
);
|
|
2034
2366
|
let authentications = 0;
|
|
2035
2367
|
subject.grantStore.authenticate = async (grantId, secret) => {
|
|
2036
2368
|
const state = await authenticate(grantId, secret);
|
|
@@ -2040,14 +2372,22 @@ test("helper guide assembly rechecks the owner lock after grant reauthentication
|
|
|
2040
2372
|
namespace: "alice",
|
|
2041
2373
|
principal: "owner:alice",
|
|
2042
2374
|
});
|
|
2043
|
-
await writeFile(
|
|
2375
|
+
await writeFile(
|
|
2376
|
+
lockPath,
|
|
2377
|
+
`${process.pid} 00000000-0000-4000-8000-000000000000 peer\n`,
|
|
2378
|
+
);
|
|
2044
2379
|
}
|
|
2045
2380
|
return state;
|
|
2046
2381
|
};
|
|
2047
2382
|
|
|
2048
2383
|
await assert.rejects(
|
|
2049
|
-
subject.grantService.readGrant({
|
|
2050
|
-
|
|
2384
|
+
subject.grantService.readGrant({
|
|
2385
|
+
grantId: created.grant.grantId,
|
|
2386
|
+
secret: created.secret,
|
|
2387
|
+
}),
|
|
2388
|
+
(error: unknown) =>
|
|
2389
|
+
error instanceof SupportPassportError &&
|
|
2390
|
+
error.code === "storage_conflict",
|
|
2051
2391
|
);
|
|
2052
2392
|
assert.equal(authentications, 3);
|
|
2053
2393
|
} finally {
|
|
@@ -2064,7 +2404,14 @@ test("helper guide assembly refreshes the owner lock after its final snapshot re
|
|
|
2064
2404
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2065
2405
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2066
2406
|
});
|
|
2067
|
-
|
|
2407
|
+
await subject.aliceStorage.writeMemory(
|
|
2408
|
+
"fact",
|
|
2409
|
+
"An unrelated memory invalidates the cached card snapshot.",
|
|
2410
|
+
{ source: "support-passport-test" },
|
|
2411
|
+
);
|
|
2412
|
+
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
2413
|
+
subject.aliceStorage,
|
|
2414
|
+
);
|
|
2068
2415
|
let reads = 0;
|
|
2069
2416
|
subject.aliceStorage.readAllMemories = async (...args) => {
|
|
2070
2417
|
const memories = await readAllMemories(...args);
|
|
@@ -2074,14 +2421,22 @@ test("helper guide assembly refreshes the owner lock after its final snapshot re
|
|
|
2074
2421
|
namespace: "alice",
|
|
2075
2422
|
principal: "owner:alice",
|
|
2076
2423
|
});
|
|
2077
|
-
await writeFile(
|
|
2424
|
+
await writeFile(
|
|
2425
|
+
lockPath,
|
|
2426
|
+
`${process.pid} 00000000-0000-4000-8000-000000000000 peer\n`,
|
|
2427
|
+
);
|
|
2078
2428
|
}
|
|
2079
2429
|
return memories;
|
|
2080
2430
|
};
|
|
2081
2431
|
|
|
2082
2432
|
await assert.rejects(
|
|
2083
|
-
subject.grantService.readGrant({
|
|
2084
|
-
|
|
2433
|
+
subject.grantService.readGrant({
|
|
2434
|
+
grantId: created.grant.grantId,
|
|
2435
|
+
secret: created.secret,
|
|
2436
|
+
}),
|
|
2437
|
+
(error: unknown) =>
|
|
2438
|
+
error instanceof SupportPassportError &&
|
|
2439
|
+
error.code === "storage_conflict",
|
|
2085
2440
|
);
|
|
2086
2441
|
assert.equal(reads, 1);
|
|
2087
2442
|
} finally {
|
|
@@ -2098,16 +2453,26 @@ test("helper guide assembly rechecks expiry after its final owner-lock refresh",
|
|
|
2098
2453
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2099
2454
|
expiresAt: expiryAfter(subject, 300_000),
|
|
2100
2455
|
});
|
|
2101
|
-
const withAuthenticatedGrant =
|
|
2102
|
-
|
|
2456
|
+
const withAuthenticatedGrant =
|
|
2457
|
+
subject.grantStore.withAuthenticatedGrant.bind(subject.grantStore);
|
|
2458
|
+
subject.grantStore.withAuthenticatedGrant = async (
|
|
2459
|
+
grantId,
|
|
2460
|
+
secret,
|
|
2461
|
+
task,
|
|
2462
|
+
beforeReturn,
|
|
2463
|
+
) =>
|
|
2103
2464
|
await withAuthenticatedGrant(grantId, secret, task, async (state) => {
|
|
2104
2465
|
subject.advance(300_000);
|
|
2105
2466
|
await beforeReturn?.(state);
|
|
2106
2467
|
});
|
|
2107
2468
|
|
|
2108
2469
|
await assert.rejects(
|
|
2109
|
-
subject.grantService.readGrant({
|
|
2110
|
-
|
|
2470
|
+
subject.grantService.readGrant({
|
|
2471
|
+
grantId: created.grant.grantId,
|
|
2472
|
+
secret: created.secret,
|
|
2473
|
+
}),
|
|
2474
|
+
(error: unknown) =>
|
|
2475
|
+
error instanceof SupportPassportError && error.code === "grant_expired",
|
|
2111
2476
|
);
|
|
2112
2477
|
} finally {
|
|
2113
2478
|
await subject.cleanup();
|
|
@@ -2134,8 +2499,89 @@ test("a changed card makes the whole grant stale without a partial guide", async
|
|
|
2134
2499
|
});
|
|
2135
2500
|
|
|
2136
2501
|
await assert.rejects(
|
|
2137
|
-
subject.grantService.readGrant({
|
|
2138
|
-
|
|
2502
|
+
subject.grantService.readGrant({
|
|
2503
|
+
grantId: created.grant.grantId,
|
|
2504
|
+
secret: created.secret,
|
|
2505
|
+
}),
|
|
2506
|
+
(error: unknown) =>
|
|
2507
|
+
error instanceof SupportPassportError &&
|
|
2508
|
+
error.code === "grant_stale" &&
|
|
2509
|
+
error.status === 410,
|
|
2510
|
+
);
|
|
2511
|
+
} finally {
|
|
2512
|
+
await subject.cleanup();
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
2515
|
+
|
|
2516
|
+
test("an active replacement makes predecessor shares stale before retirement finishes", async () => {
|
|
2517
|
+
const subject = await makeSubject();
|
|
2518
|
+
try {
|
|
2519
|
+
const active = await createActiveCard(subject, "Plan changes");
|
|
2520
|
+
const created = await subject.grantService.createGrant({
|
|
2521
|
+
principal: "owner:alice",
|
|
2522
|
+
cards: [{ cardId: active.cardId, revision: active.revision }],
|
|
2523
|
+
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2524
|
+
});
|
|
2525
|
+
const replacement = await subject.cardService.replaceCard({
|
|
2526
|
+
principal: "owner:alice",
|
|
2527
|
+
cardId: active.cardId,
|
|
2528
|
+
expectedRevision: active.revision,
|
|
2529
|
+
title: "Plan changes",
|
|
2530
|
+
statement: "Tell me early when plans change.",
|
|
2531
|
+
category: "transitions",
|
|
2532
|
+
reviewBy: "2026-09-01T12:00:00.000Z",
|
|
2533
|
+
});
|
|
2534
|
+
const writeFrontmatter =
|
|
2535
|
+
subject.aliceStorage.writeMemoryFrontmatterIfUnchanged.bind(
|
|
2536
|
+
subject.aliceStorage,
|
|
2537
|
+
);
|
|
2538
|
+
subject.aliceStorage.writeMemoryFrontmatterIfUnchanged = async (
|
|
2539
|
+
memory,
|
|
2540
|
+
patch,
|
|
2541
|
+
lifecycle,
|
|
2542
|
+
) => {
|
|
2543
|
+
if (lifecycle?.reasonCode === "support-passport-replacement-pending") {
|
|
2544
|
+
throw new Error("simulated interruption before predecessor retirement");
|
|
2545
|
+
}
|
|
2546
|
+
return await writeFrontmatter(memory, patch, lifecycle);
|
|
2547
|
+
};
|
|
2548
|
+
const approved = await subject.cardService.approveCard({
|
|
2549
|
+
principal: "owner:alice",
|
|
2550
|
+
cardId: replacement.cardId,
|
|
2551
|
+
expectedRevision: replacement.revision,
|
|
2552
|
+
});
|
|
2553
|
+
|
|
2554
|
+
await assert.rejects(
|
|
2555
|
+
subject.grantService.readGrant({
|
|
2556
|
+
grantId: created.grant.grantId,
|
|
2557
|
+
secret: created.secret,
|
|
2558
|
+
}),
|
|
2559
|
+
(error: unknown) =>
|
|
2560
|
+
error instanceof SupportPassportError && error.code === "grant_stale",
|
|
2561
|
+
);
|
|
2562
|
+
await assert.rejects(
|
|
2563
|
+
subject.grantService.createGrant({
|
|
2564
|
+
principal: "owner:alice",
|
|
2565
|
+
cards: [{ cardId: active.cardId, revision: active.revision }],
|
|
2566
|
+
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2567
|
+
}),
|
|
2568
|
+
(error: unknown) =>
|
|
2569
|
+
error instanceof SupportPassportError &&
|
|
2570
|
+
error.code === "invalid_card_status",
|
|
2571
|
+
);
|
|
2572
|
+
const replacementGrant = await subject.grantService.createGrant({
|
|
2573
|
+
principal: "owner:alice",
|
|
2574
|
+
cards: [{ cardId: approved.cardId, revision: approved.revision }],
|
|
2575
|
+
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2576
|
+
});
|
|
2577
|
+
assert.equal(
|
|
2578
|
+
(
|
|
2579
|
+
await subject.grantService.readGrant({
|
|
2580
|
+
grantId: replacementGrant.grant.grantId,
|
|
2581
|
+
secret: replacementGrant.secret,
|
|
2582
|
+
})
|
|
2583
|
+
).cards[0]?.cardId,
|
|
2584
|
+
approved.cardId,
|
|
2139
2585
|
);
|
|
2140
2586
|
} finally {
|
|
2141
2587
|
await subject.cleanup();
|
|
@@ -2183,15 +2629,21 @@ test("unrelated memory writes do not invalidate an unchanged shared guide", asyn
|
|
|
2183
2629
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2184
2630
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2185
2631
|
});
|
|
2186
|
-
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
2632
|
+
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
2633
|
+
subject.aliceStorage,
|
|
2634
|
+
);
|
|
2187
2635
|
let wroteUnrelated = false;
|
|
2188
2636
|
subject.aliceStorage.readAllMemories = async (...args) => {
|
|
2189
2637
|
const memories = await readAllMemories(...args);
|
|
2190
2638
|
if (!wroteUnrelated) {
|
|
2191
2639
|
wroteUnrelated = true;
|
|
2192
|
-
await subject.aliceStorage.writeMemory(
|
|
2193
|
-
|
|
2194
|
-
|
|
2640
|
+
await subject.aliceStorage.writeMemory(
|
|
2641
|
+
"fact",
|
|
2642
|
+
"An unrelated memory write.",
|
|
2643
|
+
{
|
|
2644
|
+
source: "support-passport-test",
|
|
2645
|
+
},
|
|
2646
|
+
);
|
|
2195
2647
|
}
|
|
2196
2648
|
return memories;
|
|
2197
2649
|
};
|
|
@@ -2207,7 +2659,7 @@ test("unrelated memory writes do not invalidate an unchanged shared guide", asyn
|
|
|
2207
2659
|
}
|
|
2208
2660
|
});
|
|
2209
2661
|
|
|
2210
|
-
test("one unchanged guide read
|
|
2662
|
+
test("one unchanged guide read reuses one fresh corpus snapshot", async () => {
|
|
2211
2663
|
const subject = await makeSubject();
|
|
2212
2664
|
try {
|
|
2213
2665
|
const card = await createActiveCard(subject);
|
|
@@ -2216,7 +2668,14 @@ test("one unchanged guide read validates a fresh final corpus snapshot", async (
|
|
|
2216
2668
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2217
2669
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2218
2670
|
});
|
|
2219
|
-
|
|
2671
|
+
await subject.aliceStorage.writeMemory(
|
|
2672
|
+
"fact",
|
|
2673
|
+
"An unrelated memory invalidates the cached card snapshot.",
|
|
2674
|
+
{ source: "support-passport-test" },
|
|
2675
|
+
);
|
|
2676
|
+
const readAllMemories = subject.aliceStorage.readAllMemories.bind(
|
|
2677
|
+
subject.aliceStorage,
|
|
2678
|
+
);
|
|
2220
2679
|
let corpusReads = 0;
|
|
2221
2680
|
subject.aliceStorage.readAllMemories = async (...args) => {
|
|
2222
2681
|
corpusReads += 1;
|
|
@@ -2232,7 +2691,7 @@ test("one unchanged guide read validates a fresh final corpus snapshot", async (
|
|
|
2232
2691
|
});
|
|
2233
2692
|
|
|
2234
2693
|
assert.equal(guide.cards[0]?.cardId, card.cardId);
|
|
2235
|
-
assert.equal(corpusReads,
|
|
2694
|
+
assert.equal(corpusReads, 1);
|
|
2236
2695
|
} finally {
|
|
2237
2696
|
await subject.cleanup();
|
|
2238
2697
|
}
|
|
@@ -2248,9 +2707,12 @@ test("grant creation rejects an approved card with an invalid update timestamp",
|
|
|
2248
2707
|
await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(memory, {
|
|
2249
2708
|
updated: "2026-08-11T12:00:00+99:99",
|
|
2250
2709
|
}),
|
|
2251
|
-
true
|
|
2710
|
+
true,
|
|
2711
|
+
);
|
|
2712
|
+
assert.deepEqual(
|
|
2713
|
+
await subject.cardService.listCards({ principal: "owner:alice" }),
|
|
2714
|
+
[],
|
|
2252
2715
|
);
|
|
2253
|
-
assert.deepEqual(await subject.cardService.listCards({ principal: "owner:alice" }), []);
|
|
2254
2716
|
|
|
2255
2717
|
await assert.rejects(
|
|
2256
2718
|
subject.grantService.createGrant({
|
|
@@ -2258,9 +2720,14 @@ test("grant creation rejects an approved card with an invalid update timestamp",
|
|
|
2258
2720
|
cards: [{ cardId: card.cardId, revision: card.revision }],
|
|
2259
2721
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2260
2722
|
}),
|
|
2261
|
-
(error: unknown) =>
|
|
2723
|
+
(error: unknown) =>
|
|
2724
|
+
error instanceof SupportPassportError &&
|
|
2725
|
+
error.code === "invalid_card_status",
|
|
2726
|
+
);
|
|
2727
|
+
assert.deepEqual(
|
|
2728
|
+
await subject.grantService.listGrants({ principal: "owner:alice" }),
|
|
2729
|
+
[],
|
|
2262
2730
|
);
|
|
2263
|
-
assert.deepEqual(await subject.grantService.listGrants({ principal: "owner:alice" }), []);
|
|
2264
2731
|
} finally {
|
|
2265
2732
|
await subject.cleanup();
|
|
2266
2733
|
}
|
|
@@ -2272,25 +2739,38 @@ test("public guides compare card update timestamps as instants", async () => {
|
|
|
2272
2739
|
const first = await createActiveCard(subject, "Earlier card");
|
|
2273
2740
|
const second = await createActiveCard(subject, "Later card");
|
|
2274
2741
|
const firstMemory = await subject.aliceStorage.getMemoryById(first.cardId);
|
|
2275
|
-
const secondMemory = await subject.aliceStorage.getMemoryById(
|
|
2742
|
+
const secondMemory = await subject.aliceStorage.getMemoryById(
|
|
2743
|
+
second.cardId,
|
|
2744
|
+
);
|
|
2276
2745
|
assert.ok(firstMemory);
|
|
2277
2746
|
assert.ok(secondMemory);
|
|
2278
2747
|
assert.equal(
|
|
2279
|
-
await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2748
|
+
await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(
|
|
2749
|
+
firstMemory,
|
|
2750
|
+
{
|
|
2751
|
+
updated: "2026-08-11T12:00:00+05:00",
|
|
2752
|
+
},
|
|
2753
|
+
),
|
|
2754
|
+
true,
|
|
2283
2755
|
);
|
|
2284
2756
|
assert.equal(
|
|
2285
|
-
await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2757
|
+
await subject.aliceStorage.writeMemoryFrontmatterIfUnchanged(
|
|
2758
|
+
secondMemory,
|
|
2759
|
+
{
|
|
2760
|
+
updated: "2026-08-11T10:00:00Z",
|
|
2761
|
+
},
|
|
2762
|
+
),
|
|
2763
|
+
true,
|
|
2289
2764
|
);
|
|
2290
|
-
const cards = await subject.cardService.listCards({
|
|
2765
|
+
const cards = await subject.cardService.listCards({
|
|
2766
|
+
principal: "owner:alice",
|
|
2767
|
+
});
|
|
2291
2768
|
const created = await subject.grantService.createGrant({
|
|
2292
2769
|
principal: "owner:alice",
|
|
2293
|
-
cards: cards.map((card) => ({
|
|
2770
|
+
cards: cards.map((card) => ({
|
|
2771
|
+
cardId: card.cardId,
|
|
2772
|
+
revision: card.revision,
|
|
2773
|
+
})),
|
|
2294
2774
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2295
2775
|
});
|
|
2296
2776
|
|
|
@@ -2318,7 +2798,10 @@ test("grant creation rejects drafts, duplicate cards, and unsafe durations", asy
|
|
|
2318
2798
|
const active = await createActiveCard(subject);
|
|
2319
2799
|
|
|
2320
2800
|
for (const input of [
|
|
2321
|
-
{
|
|
2801
|
+
{
|
|
2802
|
+
cards: [{ cardId: draft.cardId, revision: draft.revision }],
|
|
2803
|
+
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2804
|
+
},
|
|
2322
2805
|
{
|
|
2323
2806
|
cards: [
|
|
2324
2807
|
{ cardId: active.cardId, revision: active.revision },
|
|
@@ -2326,7 +2809,10 @@ test("grant creation rejects drafts, duplicate cards, and unsafe durations", asy
|
|
|
2326
2809
|
],
|
|
2327
2810
|
expiresAt: expiryAfter(subject, 3_600_000),
|
|
2328
2811
|
},
|
|
2329
|
-
{
|
|
2812
|
+
{
|
|
2813
|
+
cards: [{ cardId: active.cardId, revision: active.revision }],
|
|
2814
|
+
expiresAt: expiryAfter(subject, 299_999),
|
|
2815
|
+
},
|
|
2330
2816
|
{
|
|
2331
2817
|
cards: [{ cardId: active.cardId, revision: active.revision }],
|
|
2332
2818
|
expiresAt: expiryAfter(subject, 604_800_001),
|
|
@@ -2337,8 +2823,11 @@ test("grant creation rejects drafts, duplicate cards, and unsafe durations", asy
|
|
|
2337
2823
|
},
|
|
2338
2824
|
]) {
|
|
2339
2825
|
await assert.rejects(
|
|
2340
|
-
subject.grantService.createGrant({
|
|
2341
|
-
|
|
2826
|
+
subject.grantService.createGrant({
|
|
2827
|
+
principal: "owner:alice",
|
|
2828
|
+
...input,
|
|
2829
|
+
}),
|
|
2830
|
+
(error: unknown) => error instanceof SupportPassportError,
|
|
2342
2831
|
);
|
|
2343
2832
|
}
|
|
2344
2833
|
} finally {
|
|
@@ -2351,9 +2840,14 @@ test("the grant store rejects a symlinked grant directory", async () => {
|
|
|
2351
2840
|
try {
|
|
2352
2841
|
const memoryDir = path.join(root, "memory");
|
|
2353
2842
|
const outside = path.join(root, "outside");
|
|
2354
|
-
await mkdir(path.join(memoryDir, "state", "support-passport"), {
|
|
2843
|
+
await mkdir(path.join(memoryDir, "state", "support-passport"), {
|
|
2844
|
+
recursive: true,
|
|
2845
|
+
});
|
|
2355
2846
|
await mkdir(outside);
|
|
2356
|
-
await symlink(
|
|
2847
|
+
await symlink(
|
|
2848
|
+
outside,
|
|
2849
|
+
path.join(memoryDir, "state", "support-passport", "grants"),
|
|
2850
|
+
);
|
|
2357
2851
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
2358
2852
|
const store = new SupportPassportGrantStore({ memoryDir, now: () => now });
|
|
2359
2853
|
|
|
@@ -2364,7 +2858,7 @@ test("the grant store rejects a symlinked grant directory", async () => {
|
|
|
2364
2858
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
2365
2859
|
expiresAt: new Date(now.getTime() + 300_000).toISOString(),
|
|
2366
2860
|
}),
|
|
2367
|
-
/must remain inside the memory directory
|
|
2861
|
+
/must remain inside the memory directory/,
|
|
2368
2862
|
);
|
|
2369
2863
|
} finally {
|
|
2370
2864
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2373,15 +2867,27 @@ test("the grant store rejects a symlinked grant directory", async () => {
|
|
|
2373
2867
|
|
|
2374
2868
|
test("private file operations select supported directory access strategies", () => {
|
|
2375
2869
|
assert.throws(
|
|
2376
|
-
() =>
|
|
2377
|
-
|
|
2870
|
+
() =>
|
|
2871
|
+
requirePrivateFileDescriptorRoot(
|
|
2872
|
+
"win32",
|
|
2873
|
+
"private file directory cannot be pinned",
|
|
2874
|
+
),
|
|
2875
|
+
/private file directory cannot be pinned/,
|
|
2876
|
+
);
|
|
2877
|
+
assert.equal(
|
|
2878
|
+
requirePrivateFileDescriptorRoot("linux", "unreachable"),
|
|
2879
|
+
"/proc/self/fd",
|
|
2880
|
+
);
|
|
2881
|
+
assert.equal(
|
|
2882
|
+
requirePrivateFileDescriptorRoot("darwin", "unreachable"),
|
|
2883
|
+
"/dev/fd",
|
|
2378
2884
|
);
|
|
2379
|
-
assert.equal(requirePrivateFileDescriptorRoot("linux", "unreachable"), "/proc/self/fd");
|
|
2380
|
-
assert.equal(requirePrivateFileDescriptorRoot("darwin", "unreachable"), "/dev/fd");
|
|
2381
2885
|
});
|
|
2382
2886
|
|
|
2383
2887
|
test("private directory creation syncs every verified parent entry", async () => {
|
|
2384
|
-
const root = await mkdtemp(
|
|
2888
|
+
const root = await mkdtemp(
|
|
2889
|
+
path.join(tmpdir(), "remnic-support-private-directory-sync-"),
|
|
2890
|
+
);
|
|
2385
2891
|
try {
|
|
2386
2892
|
const target = path.join(root, "state", "support-passport", "grants");
|
|
2387
2893
|
let syncs = 0;
|
|
@@ -2389,9 +2895,19 @@ test("private directory creation syncs every verified parent entry", async () =>
|
|
|
2389
2895
|
syncs += 1;
|
|
2390
2896
|
};
|
|
2391
2897
|
|
|
2392
|
-
await ensurePrivateDirectoryNoFollow(
|
|
2898
|
+
await ensurePrivateDirectoryNoFollow(
|
|
2899
|
+
root,
|
|
2900
|
+
target,
|
|
2901
|
+
"private directory creation failed",
|
|
2902
|
+
syncVerifiedParent,
|
|
2903
|
+
);
|
|
2393
2904
|
assert.equal(syncs, 3);
|
|
2394
|
-
await ensurePrivateDirectoryNoFollow(
|
|
2905
|
+
await ensurePrivateDirectoryNoFollow(
|
|
2906
|
+
root,
|
|
2907
|
+
target,
|
|
2908
|
+
"private directory creation failed",
|
|
2909
|
+
syncVerifiedParent,
|
|
2910
|
+
);
|
|
2395
2911
|
assert.equal(syncs, 6);
|
|
2396
2912
|
} finally {
|
|
2397
2913
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2399,21 +2915,35 @@ test("private directory creation syncs every verified parent entry", async () =>
|
|
|
2399
2915
|
});
|
|
2400
2916
|
|
|
2401
2917
|
test("private directory creation retries a failed parent sync", async () => {
|
|
2402
|
-
const root = await mkdtemp(
|
|
2918
|
+
const root = await mkdtemp(
|
|
2919
|
+
path.join(tmpdir(), "remnic-support-private-directory-sync-retry-"),
|
|
2920
|
+
);
|
|
2403
2921
|
try {
|
|
2404
2922
|
const target = path.join(root, "state", "support-passport", "grants");
|
|
2405
2923
|
await assert.rejects(
|
|
2406
|
-
ensurePrivateDirectoryNoFollow(
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2924
|
+
ensurePrivateDirectoryNoFollow(
|
|
2925
|
+
root,
|
|
2926
|
+
target,
|
|
2927
|
+
"private directory creation failed",
|
|
2928
|
+
async () => {
|
|
2929
|
+
throw Object.assign(new Error("simulated directory sync failure"), {
|
|
2930
|
+
code: "EIO",
|
|
2931
|
+
});
|
|
2932
|
+
},
|
|
2933
|
+
),
|
|
2934
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
2410
2935
|
);
|
|
2411
2936
|
assert.equal((await lstat(path.join(root, "state"))).isDirectory(), true);
|
|
2412
2937
|
|
|
2413
2938
|
let retrySyncs = 0;
|
|
2414
|
-
await ensurePrivateDirectoryNoFollow(
|
|
2415
|
-
|
|
2416
|
-
|
|
2939
|
+
await ensurePrivateDirectoryNoFollow(
|
|
2940
|
+
root,
|
|
2941
|
+
target,
|
|
2942
|
+
"private directory creation failed",
|
|
2943
|
+
async () => {
|
|
2944
|
+
retrySyncs += 1;
|
|
2945
|
+
},
|
|
2946
|
+
);
|
|
2417
2947
|
assert.equal(retrySyncs, 3);
|
|
2418
2948
|
} finally {
|
|
2419
2949
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2421,24 +2951,38 @@ test("private directory creation retries a failed parent sync", async () => {
|
|
|
2421
2951
|
});
|
|
2422
2952
|
|
|
2423
2953
|
test("private directory creation retries parent sync when rollback cannot remove the child", async () => {
|
|
2424
|
-
const root = await mkdtemp(
|
|
2954
|
+
const root = await mkdtemp(
|
|
2955
|
+
path.join(tmpdir(), "remnic-support-private-directory-sync-populated-"),
|
|
2956
|
+
);
|
|
2425
2957
|
try {
|
|
2426
2958
|
const target = path.join(root, "state");
|
|
2427
2959
|
let failed = false;
|
|
2428
2960
|
await assert.rejects(
|
|
2429
|
-
ensurePrivateDirectoryNoFollow(
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2961
|
+
ensurePrivateDirectoryNoFollow(
|
|
2962
|
+
root,
|
|
2963
|
+
target,
|
|
2964
|
+
"private directory creation failed",
|
|
2965
|
+
async () => {
|
|
2966
|
+
if (failed) return;
|
|
2967
|
+
failed = true;
|
|
2968
|
+
await writeFile(path.join(target, "peer-entry"), "peer");
|
|
2969
|
+
throw Object.assign(new Error("simulated directory sync failure"), {
|
|
2970
|
+
code: "EIO",
|
|
2971
|
+
});
|
|
2972
|
+
},
|
|
2973
|
+
),
|
|
2974
|
+
(error: unknown) => (error as NodeJS.ErrnoException).code === "EIO",
|
|
2436
2975
|
);
|
|
2437
2976
|
|
|
2438
2977
|
let retrySyncs = 0;
|
|
2439
|
-
await ensurePrivateDirectoryNoFollow(
|
|
2440
|
-
|
|
2441
|
-
|
|
2978
|
+
await ensurePrivateDirectoryNoFollow(
|
|
2979
|
+
root,
|
|
2980
|
+
target,
|
|
2981
|
+
"private directory creation failed",
|
|
2982
|
+
async () => {
|
|
2983
|
+
retrySyncs += 1;
|
|
2984
|
+
},
|
|
2985
|
+
);
|
|
2442
2986
|
assert.equal(retrySyncs, 1);
|
|
2443
2987
|
} finally {
|
|
2444
2988
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2469,14 +3013,20 @@ test("a failed parent sync never unlinks a directory opened by a concurrent setu
|
|
|
2469
3013
|
});
|
|
2470
3014
|
|
|
2471
3015
|
test("private directory tree creation syncs missing memory-root ancestors", async () => {
|
|
2472
|
-
const root = await mkdtemp(
|
|
3016
|
+
const root = await mkdtemp(
|
|
3017
|
+
path.join(tmpdir(), "remnic-support-private-root-sync-"),
|
|
3018
|
+
);
|
|
2473
3019
|
try {
|
|
2474
3020
|
const target = path.join(root, "new-parent", "memory");
|
|
2475
3021
|
let syncs = 0;
|
|
2476
3022
|
|
|
2477
|
-
await ensurePrivateDirectoryTreeNoFollow(
|
|
2478
|
-
|
|
2479
|
-
|
|
3023
|
+
await ensurePrivateDirectoryTreeNoFollow(
|
|
3024
|
+
target,
|
|
3025
|
+
"private directory creation failed",
|
|
3026
|
+
async () => {
|
|
3027
|
+
syncs += 1;
|
|
3028
|
+
},
|
|
3029
|
+
);
|
|
2480
3030
|
|
|
2481
3031
|
const expectedSyncs = path.relative(path.parse(target).root, target).split(path.sep).filter(Boolean).length;
|
|
2482
3032
|
assert.equal(syncs, expectedSyncs);
|
|
@@ -2504,7 +3054,9 @@ test("private directory tree ignores a read-only filesystem-root sync", async ()
|
|
|
2504
3054
|
});
|
|
2505
3055
|
|
|
2506
3056
|
test("private directory tree creation rejects a symlink in the ancestor chain", async () => {
|
|
2507
|
-
const root = await mkdtemp(
|
|
3057
|
+
const root = await mkdtemp(
|
|
3058
|
+
path.join(tmpdir(), "remnic-support-private-root-link-"),
|
|
3059
|
+
);
|
|
2508
3060
|
try {
|
|
2509
3061
|
const outside = path.join(root, "outside");
|
|
2510
3062
|
const linked = path.join(root, "linked");
|
|
@@ -2513,8 +3065,11 @@ test("private directory tree creation rejects a symlink in the ancestor chain",
|
|
|
2513
3065
|
await symlink(outside, linked);
|
|
2514
3066
|
|
|
2515
3067
|
await assert.rejects(
|
|
2516
|
-
ensurePrivateDirectoryTreeNoFollow(
|
|
2517
|
-
|
|
3068
|
+
ensurePrivateDirectoryTreeNoFollow(
|
|
3069
|
+
target,
|
|
3070
|
+
"private directory creation failed",
|
|
3071
|
+
),
|
|
3072
|
+
/private directory creation failed/,
|
|
2518
3073
|
);
|
|
2519
3074
|
assert.deepEqual(await readdir(outside), []);
|
|
2520
3075
|
} finally {
|
|
@@ -2580,7 +3135,9 @@ test("Windows private-file operations fail before mutation", async () => {
|
|
|
2580
3135
|
});
|
|
2581
3136
|
|
|
2582
3137
|
test("grant cleanup never follows a swapped grant directory", async () => {
|
|
2583
|
-
const root = await mkdtemp(
|
|
3138
|
+
const root = await mkdtemp(
|
|
3139
|
+
path.join(tmpdir(), "remnic-support-grant-cleanup-swap-"),
|
|
3140
|
+
);
|
|
2584
3141
|
try {
|
|
2585
3142
|
const grantId = "00000000-0000-4000-8000-000000000001";
|
|
2586
3143
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
@@ -2600,15 +3157,23 @@ test("grant cleanup never follows a swapped grant directory", async () => {
|
|
|
2600
3157
|
const outsideDir = path.join(root, "outside");
|
|
2601
3158
|
const fileName = `${grantId}.json`;
|
|
2602
3159
|
await mkdir(outsideDir);
|
|
2603
|
-
await writeFile(path.join(outsideDir, fileName), "outside must remain", {
|
|
3160
|
+
await writeFile(path.join(outsideDir, fileName), "outside must remain", {
|
|
3161
|
+
mode: 0o600,
|
|
3162
|
+
});
|
|
2604
3163
|
renameSync(grantsDir, parkedDir);
|
|
2605
3164
|
symlinkSync(outsideDir, grantsDir, "dir");
|
|
2606
3165
|
const inspected = store as unknown as {
|
|
2607
3166
|
removeGrantStates(grantIds: string[]): Promise<void>;
|
|
2608
3167
|
};
|
|
2609
3168
|
|
|
2610
|
-
await assert.rejects(
|
|
2611
|
-
|
|
3169
|
+
await assert.rejects(
|
|
3170
|
+
inspected.removeGrantStates([grantId]),
|
|
3171
|
+
/regular files in a stable directory/,
|
|
3172
|
+
);
|
|
3173
|
+
assert.equal(
|
|
3174
|
+
await readFile(path.join(outsideDir, fileName), "utf8"),
|
|
3175
|
+
"outside must remain",
|
|
3176
|
+
);
|
|
2612
3177
|
assert.equal((await lstat(path.join(parkedDir, fileName))).isFile(), true);
|
|
2613
3178
|
} finally {
|
|
2614
3179
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2620,7 +3185,12 @@ test("grant writes reject a directory swapped after the initial safety check", a
|
|
|
2620
3185
|
try {
|
|
2621
3186
|
const memoryDir = path.join(root, "memory");
|
|
2622
3187
|
const outside = path.join(root, "outside");
|
|
2623
|
-
const grantsDir = path.join(
|
|
3188
|
+
const grantsDir = path.join(
|
|
3189
|
+
memoryDir,
|
|
3190
|
+
"state",
|
|
3191
|
+
"support-passport",
|
|
3192
|
+
"grants",
|
|
3193
|
+
);
|
|
2624
3194
|
const parkedDir = path.join(root, "parked-grants");
|
|
2625
3195
|
await mkdir(outside, { recursive: true });
|
|
2626
3196
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
@@ -2637,12 +3207,14 @@ test("grant writes reject a directory swapped after the initial safety check", a
|
|
|
2637
3207
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
2638
3208
|
expiresAt: new Date(now.getTime() + 300_000).toISOString(),
|
|
2639
3209
|
},
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
3210
|
+
{
|
|
3211
|
+
beforeCommit: async () => {
|
|
3212
|
+
renameSync(grantsDir, parkedDir);
|
|
3213
|
+
symlinkSync(outside, grantsDir, "dir");
|
|
3214
|
+
},
|
|
3215
|
+
},
|
|
2644
3216
|
),
|
|
2645
|
-
/regular files in a stable directory
|
|
3217
|
+
/regular files in a stable directory/,
|
|
2646
3218
|
);
|
|
2647
3219
|
assert.deepEqual(await readdir(outside), []);
|
|
2648
3220
|
} finally {
|
|
@@ -2651,7 +3223,9 @@ test("grant writes reject a directory swapped after the initial safety check", a
|
|
|
2651
3223
|
});
|
|
2652
3224
|
|
|
2653
3225
|
test("grant writes reject an ancestor swapped after the initial safety check", async () => {
|
|
2654
|
-
const root = await mkdtemp(
|
|
3226
|
+
const root = await mkdtemp(
|
|
3227
|
+
path.join(tmpdir(), "remnic-support-grant-ancestor-swap-"),
|
|
3228
|
+
);
|
|
2655
3229
|
try {
|
|
2656
3230
|
const memoryDir = path.join(root, "memory");
|
|
2657
3231
|
const passportDir = path.join(memoryDir, "state", "support-passport");
|
|
@@ -2659,7 +3233,9 @@ test("grant writes reject an ancestor swapped after the initial safety check", a
|
|
|
2659
3233
|
const outside = path.join(root, "outside-support-passport");
|
|
2660
3234
|
await mkdir(path.join(outside, "grants", "owners"), { recursive: true });
|
|
2661
3235
|
const acceptLock = (async (_lockPath, _options, task) =>
|
|
2662
|
-
await task(true, {
|
|
3236
|
+
await task(true, {
|
|
3237
|
+
refresh: async () => true,
|
|
3238
|
+
})) as typeof withHeldFileLock;
|
|
2663
3239
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
2664
3240
|
const store = new SupportPassportGrantStore({
|
|
2665
3241
|
memoryDir,
|
|
@@ -2677,12 +3253,14 @@ test("grant writes reject an ancestor swapped after the initial safety check", a
|
|
|
2677
3253
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
2678
3254
|
expiresAt: new Date(now.getTime() + 300_000).toISOString(),
|
|
2679
3255
|
},
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3256
|
+
{
|
|
3257
|
+
beforeCommit: async () => {
|
|
3258
|
+
renameSync(passportDir, parkedDir);
|
|
3259
|
+
symlinkSync(outside, passportDir, "dir");
|
|
3260
|
+
},
|
|
3261
|
+
},
|
|
2684
3262
|
),
|
|
2685
|
-
/regular files in a stable directory
|
|
3263
|
+
/regular files in a stable directory/,
|
|
2686
3264
|
);
|
|
2687
3265
|
assert.deepEqual(await readdir(path.join(outside, "grants")), ["owners"]);
|
|
2688
3266
|
} finally {
|
|
@@ -2724,7 +3302,9 @@ test("grant reads reject a memory-root ancestor swapped after a completed operat
|
|
|
2724
3302
|
});
|
|
2725
3303
|
|
|
2726
3304
|
test("the grant store fails closed for corrupt and symlinked grant files", async () => {
|
|
2727
|
-
const root = await mkdtemp(
|
|
3305
|
+
const root = await mkdtemp(
|
|
3306
|
+
path.join(tmpdir(), "remnic-support-grant-state-"),
|
|
3307
|
+
);
|
|
2728
3308
|
try {
|
|
2729
3309
|
const firstGrantId = "00000000-0000-4000-8000-000000000001";
|
|
2730
3310
|
const secondGrantId = "00000000-0000-4000-8000-000000000002";
|
|
@@ -2753,15 +3333,20 @@ test("the grant store fails closed for corrupt and symlinked grant files", async
|
|
|
2753
3333
|
await writeFile(outsidePath, await readFile(linkedPath), { mode: 0o600 });
|
|
2754
3334
|
await rm(linkedPath);
|
|
2755
3335
|
await symlink(outsidePath, linkedPath);
|
|
2756
|
-
await assert.rejects(
|
|
3336
|
+
await assert.rejects(
|
|
3337
|
+
store.authenticate(secondGrantId, linked.secret),
|
|
3338
|
+
/must be regular files/,
|
|
3339
|
+
);
|
|
2757
3340
|
await assert.rejects(store.listForOwner("alice", "owner:alice"));
|
|
2758
3341
|
} finally {
|
|
2759
3342
|
await rm(root, { recursive: true, force: true });
|
|
2760
3343
|
}
|
|
2761
3344
|
});
|
|
2762
3345
|
|
|
2763
|
-
test("the grant store
|
|
2764
|
-
const root = await mkdtemp(
|
|
3346
|
+
test("the grant store omits a state file whose grant ID does not match its file name", async () => {
|
|
3347
|
+
const root = await mkdtemp(
|
|
3348
|
+
path.join(tmpdir(), "remnic-support-grant-identity-"),
|
|
3349
|
+
);
|
|
2765
3350
|
try {
|
|
2766
3351
|
const firstGrantId = "00000000-0000-4000-8000-000000000001";
|
|
2767
3352
|
const secondGrantId = "00000000-0000-4000-8000-000000000002";
|
|
@@ -2784,7 +3369,7 @@ test("the grant store rejects a state file whose grant ID does not match its fil
|
|
|
2784
3369
|
await writeFile(
|
|
2785
3370
|
path.join(grantDir, `${secondGrantId}.json`),
|
|
2786
3371
|
await readFile(path.join(grantDir, `${firstGrantId}.json`)),
|
|
2787
|
-
{ mode: 0o600 }
|
|
3372
|
+
{ mode: 0o600 },
|
|
2788
3373
|
);
|
|
2789
3374
|
|
|
2790
3375
|
await assert.rejects(store.authenticate(secondGrantId, second.secret), /grant ID must match its file name/);
|
|
@@ -2799,11 +3384,17 @@ test("the grant store rejects a state file whose grant ID does not match its fil
|
|
|
2799
3384
|
});
|
|
2800
3385
|
|
|
2801
3386
|
test("the grant store rejects unsafe durations and grant ID collisions", async () => {
|
|
2802
|
-
const root = await mkdtemp(
|
|
3387
|
+
const root = await mkdtemp(
|
|
3388
|
+
path.join(tmpdir(), "remnic-support-grant-collision-"),
|
|
3389
|
+
);
|
|
2803
3390
|
try {
|
|
2804
3391
|
const grantId = "00000000-0000-4000-8000-000000000001";
|
|
2805
3392
|
const now = new Date("2026-08-11T12:00:00.000Z");
|
|
2806
|
-
const store = new SupportPassportGrantStore({
|
|
3393
|
+
const store = new SupportPassportGrantStore({
|
|
3394
|
+
memoryDir: root,
|
|
3395
|
+
makeGrantId: () => grantId,
|
|
3396
|
+
now: () => now,
|
|
3397
|
+
});
|
|
2807
3398
|
const input = {
|
|
2808
3399
|
namespace: "alice",
|
|
2809
3400
|
principal: "owner:alice",
|
|
@@ -2813,11 +3404,17 @@ test("the grant store rejects unsafe durations and grant ID collisions", async (
|
|
|
2813
3404
|
await store.create(input);
|
|
2814
3405
|
await assert.rejects(
|
|
2815
3406
|
store.create(input),
|
|
2816
|
-
(error: unknown) =>
|
|
3407
|
+
(error: unknown) =>
|
|
3408
|
+
error instanceof SupportPassportError &&
|
|
3409
|
+
error.code === "storage_conflict",
|
|
2817
3410
|
);
|
|
2818
3411
|
await assert.rejects(
|
|
2819
|
-
store.create({
|
|
2820
|
-
|
|
3412
|
+
store.create({
|
|
3413
|
+
...input,
|
|
3414
|
+
expiresAt: new Date(now.getTime() + 299_999).toISOString(),
|
|
3415
|
+
}),
|
|
3416
|
+
(error: unknown) =>
|
|
3417
|
+
error instanceof SupportPassportError && error.code === "invalid_input",
|
|
2821
3418
|
);
|
|
2822
3419
|
} finally {
|
|
2823
3420
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2825,7 +3422,9 @@ test("the grant store rejects unsafe durations and grant ID collisions", async (
|
|
|
2825
3422
|
});
|
|
2826
3423
|
|
|
2827
3424
|
test("the grant store canonicalizes UUID letter case", async () => {
|
|
2828
|
-
const root = await mkdtemp(
|
|
3425
|
+
const root = await mkdtemp(
|
|
3426
|
+
path.join(tmpdir(), "remnic-support-grant-id-case-"),
|
|
3427
|
+
);
|
|
2829
3428
|
try {
|
|
2830
3429
|
const uppercaseGrantId = "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA";
|
|
2831
3430
|
const lowercaseGrantId = uppercaseGrantId.toLowerCase();
|
|
@@ -2843,10 +3442,19 @@ test("the grant store canonicalizes UUID letter case", async () => {
|
|
|
2843
3442
|
});
|
|
2844
3443
|
|
|
2845
3444
|
assert.equal(created.state.grantId, lowercaseGrantId);
|
|
2846
|
-
assert.equal((await store.authenticate(uppercaseGrantId, created.secret)).grantId, lowercaseGrantId);
|
|
2847
3445
|
assert.equal(
|
|
2848
|
-
(await store.
|
|
2849
|
-
lowercaseGrantId
|
|
3446
|
+
(await store.authenticate(uppercaseGrantId, created.secret)).grantId,
|
|
3447
|
+
lowercaseGrantId,
|
|
3448
|
+
);
|
|
3449
|
+
assert.equal(
|
|
3450
|
+
(
|
|
3451
|
+
await store.revoke({
|
|
3452
|
+
grantId: uppercaseGrantId,
|
|
3453
|
+
namespace: "alice",
|
|
3454
|
+
principal: "owner:alice",
|
|
3455
|
+
})
|
|
3456
|
+
).grantId,
|
|
3457
|
+
lowercaseGrantId,
|
|
2850
3458
|
);
|
|
2851
3459
|
} finally {
|
|
2852
3460
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2857,7 +3465,10 @@ test("the grant store rejects invalid calendar dates", async () => {
|
|
|
2857
3465
|
const root = await mkdtemp(path.join(tmpdir(), "remnic-support-grant-date-"));
|
|
2858
3466
|
try {
|
|
2859
3467
|
const now = new Date("2026-02-28T12:00:00.000Z");
|
|
2860
|
-
const store = new SupportPassportGrantStore({
|
|
3468
|
+
const store = new SupportPassportGrantStore({
|
|
3469
|
+
memoryDir: root,
|
|
3470
|
+
now: () => now,
|
|
3471
|
+
});
|
|
2861
3472
|
|
|
2862
3473
|
await assert.rejects(
|
|
2863
3474
|
store.create({
|
|
@@ -2866,7 +3477,8 @@ test("the grant store rejects invalid calendar dates", async () => {
|
|
|
2866
3477
|
cards: [{ cardId: "card-1", revision: "a".repeat(64) }],
|
|
2867
3478
|
expiresAt: "2026-02-31T12:00:00.000Z",
|
|
2868
3479
|
}),
|
|
2869
|
-
(error: unknown) =>
|
|
3480
|
+
(error: unknown) =>
|
|
3481
|
+
error instanceof SupportPassportError && error.code === "invalid_input",
|
|
2870
3482
|
);
|
|
2871
3483
|
} finally {
|
|
2872
3484
|
await rm(root, { recursive: true, force: true });
|
|
@@ -2874,7 +3486,9 @@ test("the grant store rejects invalid calendar dates", async () => {
|
|
|
2874
3486
|
});
|
|
2875
3487
|
|
|
2876
3488
|
test("the grant store expands a leading tilde in its memory directory", () => {
|
|
2877
|
-
const store = new SupportPassportGrantStore({
|
|
3489
|
+
const store = new SupportPassportGrantStore({
|
|
3490
|
+
memoryDir: "~/support-passport-path-test",
|
|
3491
|
+
});
|
|
2878
3492
|
const memoryDir = (store as unknown as { memoryDir: string }).memoryDir;
|
|
2879
3493
|
assert.equal(memoryDir.includes(`${path.sep}~${path.sep}`), false);
|
|
2880
3494
|
assert.equal(path.basename(memoryDir), "support-passport-path-test");
|