@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,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import type { StorageManager } from "../index.js";
|
|
2
4
|
import { log } from "../logger.js";
|
|
3
|
-
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
4
5
|
import type { MemoryFile } from "../types.js";
|
|
5
6
|
import type { HeldFileLockController } from "../utils/serialize-mutations.js";
|
|
6
7
|
import { composeMemoryEnvelope } from "../write-envelope.js";
|
|
@@ -9,10 +10,30 @@ import {
|
|
|
9
10
|
SUPPORT_PASSPORT_CARD_TAG,
|
|
10
11
|
type StoredSupportPassportCard,
|
|
11
12
|
computeSupportPassportOwnerKey,
|
|
12
|
-
decodeSupportPassportNamespaceAttributes,
|
|
13
13
|
encodeSupportPassportNamespaceAttributes,
|
|
14
|
-
projectSupportPassportCard,
|
|
15
14
|
} from "./card-projection.js";
|
|
15
|
+
import {
|
|
16
|
+
completeSupportPassportReplacementPrior,
|
|
17
|
+
prepareSupportPassportReplacementPrior,
|
|
18
|
+
validateSupportPassportReplacementPrior,
|
|
19
|
+
} from "./card-replacement.js";
|
|
20
|
+
import {
|
|
21
|
+
MAX_OWNER_VISIBLE_CARDS,
|
|
22
|
+
type SupportPassportCardServiceDependencies,
|
|
23
|
+
type SupportPassportOwnerScope,
|
|
24
|
+
invalidInput,
|
|
25
|
+
isStorageConflict,
|
|
26
|
+
once,
|
|
27
|
+
ownerListCards,
|
|
28
|
+
ownerVisibleCards,
|
|
29
|
+
ownsMemory,
|
|
30
|
+
projectOwnedCard,
|
|
31
|
+
projectRequiredCard,
|
|
32
|
+
requireRevision,
|
|
33
|
+
requireStatus,
|
|
34
|
+
revisionFor,
|
|
35
|
+
validateOwnerScope,
|
|
36
|
+
} from "./card-state.js";
|
|
16
37
|
import {
|
|
17
38
|
type SupportPassportCard,
|
|
18
39
|
type SupportPassportCardCategory,
|
|
@@ -22,52 +43,41 @@ import {
|
|
|
22
43
|
SupportPassportListCardsInputSchema,
|
|
23
44
|
type SupportPassportManualDraftInput,
|
|
24
45
|
SupportPassportManualDraftInputSchema,
|
|
25
|
-
SupportPassportNamespaceSchema,
|
|
26
46
|
type SupportPassportReplaceCardInput,
|
|
27
47
|
SupportPassportReplaceCardInputSchema,
|
|
28
|
-
computeSupportPassportCardRevision,
|
|
29
48
|
} from "./contracts.js";
|
|
30
49
|
import { SupportPassportError } from "./errors.js";
|
|
50
|
+
import {
|
|
51
|
+
type GeneratedBatchMarker,
|
|
52
|
+
commitSupportPassportGeneratedBatch,
|
|
53
|
+
isCommittedGeneratedCard,
|
|
54
|
+
persistSupportPassportGeneratedBatchMarker,
|
|
55
|
+
projectCommittedSupportPassportCards,
|
|
56
|
+
readCommittedSupportPassportCards,
|
|
57
|
+
recoverSupportPassportGeneratedBatches,
|
|
58
|
+
rollbackSupportPassportGeneratedBatch,
|
|
59
|
+
} from "./generated-batch.js";
|
|
60
|
+
import { type SupportPassportDraftCard, SupportPassportDraftOutputSchema } from "./model-contracts.js";
|
|
31
61
|
import { withSupportPassportOwnerLock } from "./owner-lock.js";
|
|
32
|
-
|
|
33
|
-
export interface SupportPassportOwnerScope {
|
|
34
|
-
principal: string;
|
|
35
|
-
namespace: string;
|
|
36
|
-
storage: StorageManager;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SupportPassportCardServiceDependencies {
|
|
40
|
-
resolveOwner(principal: string): Promise<SupportPassportOwnerScope>;
|
|
41
|
-
now?: () => Date;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const OWNER_VISIBLE_STATUSES = new Set(["pending_review", "active"]);
|
|
45
|
-
const MAX_OWNER_VISIBLE_CARDS = 100;
|
|
46
|
-
|
|
47
|
-
function invalidInput(): SupportPassportError {
|
|
48
|
-
return new SupportPassportError("invalid_input", "The support card request is invalid.", 400);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isStorageConflict(error: unknown): error is SupportPassportError {
|
|
52
|
-
return error instanceof SupportPassportError && error.code === "storage_conflict";
|
|
53
|
-
}
|
|
54
|
-
|
|
62
|
+
export type { SupportPassportCardServiceDependencies, SupportPassportOwnerScope } from "./card-state.js";
|
|
55
63
|
export class SupportPassportCardService {
|
|
56
64
|
private readonly resolveOwner: SupportPassportCardServiceDependencies["resolveOwner"];
|
|
57
65
|
private readonly now: () => Date;
|
|
58
|
-
|
|
59
66
|
constructor(dependencies: SupportPassportCardServiceDependencies) {
|
|
60
67
|
this.resolveOwner = dependencies.resolveOwner;
|
|
61
68
|
this.now = dependencies.now ?? (() => new Date());
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
async listCards(input: {
|
|
71
|
+
async listCards(input: {
|
|
72
|
+
principal: string;
|
|
73
|
+
}): Promise<SupportPassportCard[]> {
|
|
65
74
|
const parsed = SupportPassportListCardsInputSchema.safeParse(input);
|
|
66
75
|
if (!parsed.success) throw invalidInput();
|
|
67
76
|
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
68
|
-
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (
|
|
69
|
-
const
|
|
70
|
-
const
|
|
77
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async () => {
|
|
78
|
+
const memories = await storage.readAllMemories();
|
|
79
|
+
const stored = await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
|
|
80
|
+
const cards = ownerListCards(stored)
|
|
71
81
|
.sort((a, b) => a.order - b.order || a.card.cardId.localeCompare(b.card.cardId))
|
|
72
82
|
.map((item) => item.card);
|
|
73
83
|
const output = SupportPassportCardListSchema.safeParse(cards);
|
|
@@ -78,12 +88,17 @@ export class SupportPassportCardService {
|
|
|
78
88
|
});
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
async createManualDraft(
|
|
91
|
+
async createManualDraft(
|
|
92
|
+
input: SupportPassportManualDraftInput,
|
|
93
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
94
|
+
): Promise<SupportPassportCard> {
|
|
82
95
|
const parsed = SupportPassportManualDraftInputSchema.safeParse(input);
|
|
83
96
|
if (!parsed.success) throw invalidInput();
|
|
84
97
|
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
const onCommitted = once(options.onCommitted);
|
|
99
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
100
|
+
options.signal?.throwIfAborted();
|
|
101
|
+
const created = await this.createDraft(
|
|
87
102
|
storage,
|
|
88
103
|
{
|
|
89
104
|
title: parsed.data.title,
|
|
@@ -91,21 +106,127 @@ export class SupportPassportCardService {
|
|
|
91
106
|
category: parsed.data.category,
|
|
92
107
|
reviewBy: parsed.data.reviewBy,
|
|
93
108
|
sourceMemoryIds: [],
|
|
109
|
+
onCommitted,
|
|
94
110
|
},
|
|
95
111
|
lock,
|
|
96
112
|
principal,
|
|
97
113
|
namespace
|
|
98
|
-
)
|
|
99
|
-
|
|
114
|
+
);
|
|
115
|
+
if (!options.signal?.aborted) return created;
|
|
116
|
+
if (!(await this.rejectGeneratedDraft(storage, created.cardId, lock, principal, namespace))) {
|
|
117
|
+
throw new SupportPassportError("storage_conflict", "A cancelled support card could not be rolled back.", 500);
|
|
118
|
+
}
|
|
119
|
+
options.signal.throwIfAborted();
|
|
120
|
+
return created;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async createGeneratedDrafts(input: {
|
|
125
|
+
principal: string;
|
|
126
|
+
cards: SupportPassportDraftCard[];
|
|
127
|
+
}): Promise<SupportPassportCard[]> {
|
|
128
|
+
const principal = SupportPassportListCardsInputSchema.safeParse({
|
|
129
|
+
principal: input.principal,
|
|
130
|
+
});
|
|
131
|
+
if (!principal.success) throw invalidInput();
|
|
132
|
+
const owner = await this.resolveOwnerScope(principal.data.principal);
|
|
133
|
+
return await this.createGeneratedDraftsForOwner({
|
|
134
|
+
authenticatedPrincipal: principal.data.principal,
|
|
135
|
+
owner,
|
|
136
|
+
cards: input.cards,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async createGeneratedDraftsForOwner(input: {
|
|
141
|
+
authenticatedPrincipal: string;
|
|
142
|
+
owner: SupportPassportOwnerScope;
|
|
143
|
+
cards: SupportPassportDraftCard[];
|
|
144
|
+
signal?: AbortSignal;
|
|
145
|
+
onCommitted?: () => void;
|
|
146
|
+
commitWithValidatedSources?: (commit: () => Promise<void>) => Promise<void>;
|
|
147
|
+
}): Promise<SupportPassportCard[]> {
|
|
148
|
+
const output = SupportPassportDraftOutputSchema.safeParse({
|
|
149
|
+
cards: input.cards,
|
|
150
|
+
});
|
|
151
|
+
if (!output.success) throw invalidInput();
|
|
152
|
+
const owner = validateOwnerScope(input.owner, input.authenticatedPrincipal);
|
|
153
|
+
const { principal, namespace, storage } = owner;
|
|
154
|
+
const onCommitted = once(input.onCommitted);
|
|
155
|
+
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
156
|
+
const batchContext = this.generatedBatchContext(storage, lock, principal, namespace, onCommitted);
|
|
157
|
+
const batchId = randomUUID();
|
|
158
|
+
const created: SupportPassportCard[] = [];
|
|
159
|
+
const createdIds: string[] = [];
|
|
160
|
+
const createdRecords: StoredSupportPassportCard[] = [];
|
|
161
|
+
let marker: GeneratedBatchMarker | null = null;
|
|
162
|
+
try {
|
|
163
|
+
input.signal?.throwIfAborted();
|
|
164
|
+
input.signal?.throwIfAborted();
|
|
165
|
+
const storedCards = await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
|
|
166
|
+
if (ownerVisibleCards(storedCards).length + output.data.cards.length > MAX_OWNER_VISIBLE_CARDS) {
|
|
167
|
+
throw new SupportPassportError(
|
|
168
|
+
"invalid_input",
|
|
169
|
+
"A support passport can contain at most 100 visible cards.",
|
|
170
|
+
400
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
let nextOrder = storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
|
|
174
|
+
marker = await persistSupportPassportGeneratedBatchMarker(batchContext, batchId, output.data.cards.length);
|
|
175
|
+
for (const card of output.data.cards) {
|
|
176
|
+
input.signal?.throwIfAborted();
|
|
177
|
+
created.push(
|
|
178
|
+
await this.persistDraft(
|
|
179
|
+
storage,
|
|
180
|
+
{
|
|
181
|
+
title: card.title,
|
|
182
|
+
statement: card.statement,
|
|
183
|
+
category: card.category,
|
|
184
|
+
sourceMemoryIds: card.sourceMemoryIds,
|
|
185
|
+
order: nextOrder,
|
|
186
|
+
generatedBatch: { batchId, size: output.data.cards.length },
|
|
187
|
+
onPersisted: (record) => {
|
|
188
|
+
createdIds.push(record.card.cardId);
|
|
189
|
+
createdRecords.push(record);
|
|
190
|
+
onCommitted();
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
lock,
|
|
194
|
+
principal,
|
|
195
|
+
namespace
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
nextOrder += 1;
|
|
199
|
+
}
|
|
200
|
+
input.signal?.throwIfAborted();
|
|
201
|
+
const commit = async () =>
|
|
202
|
+
await commitSupportPassportGeneratedBatch(batchContext, marker!, createdRecords);
|
|
203
|
+
if (input.commitWithValidatedSources) await input.commitWithValidatedSources(commit);
|
|
204
|
+
else await commit();
|
|
205
|
+
return created;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!marker) throw error;
|
|
208
|
+
if (
|
|
209
|
+
!(await rollbackSupportPassportGeneratedBatch(batchContext, batchId, createdIds))
|
|
210
|
+
) {
|
|
211
|
+
throw new SupportPassportError("storage_conflict", "A generated draft batch could not be rolled back.", 500);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
100
216
|
}
|
|
101
217
|
|
|
102
|
-
async replaceCard(
|
|
218
|
+
async replaceCard(
|
|
219
|
+
input: SupportPassportReplaceCardInput,
|
|
220
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
221
|
+
): Promise<SupportPassportCard> {
|
|
103
222
|
const parsed = SupportPassportReplaceCardInputSchema.safeParse(input);
|
|
104
223
|
if (!parsed.success) throw invalidInput();
|
|
105
224
|
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
225
|
+
const onCommitted = once(options.onCommitted);
|
|
106
226
|
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
227
|
+
options.signal?.throwIfAborted();
|
|
107
228
|
const loadedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
108
|
-
const storedCards = await
|
|
229
|
+
const storedCards = await readCommittedSupportPassportCards(storage, namespace, principal);
|
|
109
230
|
const interruptedReplacements = storedCards.filter(
|
|
110
231
|
(item) =>
|
|
111
232
|
item.card.status === "pending_review" &&
|
|
@@ -142,22 +263,26 @@ export class SupportPassportCardService {
|
|
|
142
263
|
interruptedReplacement.memory,
|
|
143
264
|
lock,
|
|
144
265
|
principal,
|
|
145
|
-
namespace
|
|
266
|
+
namespace,
|
|
267
|
+
{},
|
|
268
|
+
onCommitted
|
|
146
269
|
);
|
|
147
|
-
return
|
|
270
|
+
return projectRequiredCard(recovered, namespace, principal).card;
|
|
148
271
|
}
|
|
149
|
-
|
|
150
|
-
await this.readStoredCards(storage, lock, principal, namespace);
|
|
272
|
+
requireRevision(loadedPrior, parsed.data.expectedRevision);
|
|
273
|
+
await this.readStoredCards(storage, lock, principal, namespace, onCommitted);
|
|
151
274
|
const refreshedPrior = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
152
|
-
|
|
275
|
+
requireRevision(refreshedPrior, parsed.data.expectedRevision);
|
|
153
276
|
const recoveredMemory = await this.recoverReplacementTransition(
|
|
154
277
|
storage,
|
|
155
278
|
refreshedPrior.memory,
|
|
156
279
|
lock,
|
|
157
280
|
principal,
|
|
158
|
-
namespace
|
|
281
|
+
namespace,
|
|
282
|
+
{},
|
|
283
|
+
onCommitted
|
|
159
284
|
);
|
|
160
|
-
const prior =
|
|
285
|
+
const prior = projectRequiredCard(recoveredMemory, namespace, principal);
|
|
161
286
|
if (prior.card.status !== "active" && prior.card.status !== "pending_review") {
|
|
162
287
|
throw new SupportPassportError(
|
|
163
288
|
"invalid_card_status",
|
|
@@ -178,11 +303,22 @@ export class SupportPassportCardService {
|
|
|
178
303
|
replacedRevision: prior.card.revision,
|
|
179
304
|
order: prior.order,
|
|
180
305
|
draftReplacementPrepared: prior.card.status === "pending_review",
|
|
306
|
+
onCommitted,
|
|
181
307
|
},
|
|
182
308
|
lock,
|
|
183
309
|
principal,
|
|
184
310
|
namespace
|
|
185
311
|
);
|
|
312
|
+
if (options.signal?.aborted) {
|
|
313
|
+
if (!(await this.rejectGeneratedDraft(storage, replacement.cardId, lock, principal, namespace))) {
|
|
314
|
+
throw new SupportPassportError(
|
|
315
|
+
"storage_conflict",
|
|
316
|
+
"A cancelled support card edit could not be rolled back.",
|
|
317
|
+
500
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
options.signal.throwIfAborted();
|
|
321
|
+
}
|
|
186
322
|
if (prior.card.status === "active") return replacement;
|
|
187
323
|
|
|
188
324
|
const replacedAt = this.now().toISOString();
|
|
@@ -193,12 +329,27 @@ export class SupportPassportCardService {
|
|
|
193
329
|
{ actor: principal, reasonCode: "owner-replaced-draft" }
|
|
194
330
|
);
|
|
195
331
|
if (rejected) {
|
|
196
|
-
|
|
332
|
+
onCommitted();
|
|
333
|
+
return await this.finishPreparedDraftReplacement(
|
|
334
|
+
storage,
|
|
335
|
+
replacement.cardId,
|
|
336
|
+
lock,
|
|
337
|
+
principal,
|
|
338
|
+
namespace,
|
|
339
|
+
onCommitted
|
|
340
|
+
);
|
|
197
341
|
}
|
|
198
342
|
const currentPrior = await storage.getMemoryById(prior.card.cardId);
|
|
199
|
-
const currentPriorCard = currentPrior ?
|
|
343
|
+
const currentPriorCard = currentPrior ? projectOwnedCard(currentPrior, namespace, principal) : null;
|
|
200
344
|
if (currentPriorCard?.card.status === "rejected") {
|
|
201
|
-
return await this.finishPreparedDraftReplacement(
|
|
345
|
+
return await this.finishPreparedDraftReplacement(
|
|
346
|
+
storage,
|
|
347
|
+
replacement.cardId,
|
|
348
|
+
lock,
|
|
349
|
+
principal,
|
|
350
|
+
namespace,
|
|
351
|
+
onCommitted
|
|
352
|
+
);
|
|
202
353
|
}
|
|
203
354
|
|
|
204
355
|
await this.rejectCreatedDraft(
|
|
@@ -207,28 +358,36 @@ export class SupportPassportCardService {
|
|
|
207
358
|
"draft-replacement-failed",
|
|
208
359
|
lock,
|
|
209
360
|
principal,
|
|
210
|
-
namespace
|
|
361
|
+
namespace,
|
|
362
|
+
onCommitted
|
|
211
363
|
);
|
|
212
364
|
throw new SupportPassportError("storage_conflict", "The support card changed before it was edited.", 409);
|
|
213
365
|
});
|
|
214
366
|
}
|
|
215
367
|
|
|
216
|
-
async approveCard(
|
|
368
|
+
async approveCard(
|
|
369
|
+
input: SupportPassportCardMutationInput,
|
|
370
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
371
|
+
): Promise<SupportPassportCard> {
|
|
217
372
|
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
218
373
|
if (!parsed.success) throw invalidInput();
|
|
219
374
|
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
375
|
+
const onCommitted = once(options.onCommitted);
|
|
220
376
|
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
377
|
+
options.signal?.throwIfAborted();
|
|
221
378
|
const loadedCard = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
222
|
-
|
|
223
|
-
|
|
379
|
+
requireRevision(loadedCard, parsed.data.expectedRevision);
|
|
380
|
+
requireStatus(loadedCard, "pending_review");
|
|
224
381
|
const recoveredMemory = await this.recoverReplacementTransition(
|
|
225
382
|
storage,
|
|
226
383
|
loadedCard.memory,
|
|
227
384
|
lock,
|
|
228
385
|
principal,
|
|
229
|
-
namespace
|
|
386
|
+
namespace,
|
|
387
|
+
{},
|
|
388
|
+
onCommitted
|
|
230
389
|
);
|
|
231
|
-
const card =
|
|
390
|
+
const card = projectRequiredCard(recoveredMemory, namespace, principal);
|
|
232
391
|
if (card.card.status === "active") return card.card;
|
|
233
392
|
if (card.card.status === "rejected") {
|
|
234
393
|
throw new SupportPassportError(
|
|
@@ -237,8 +396,8 @@ export class SupportPassportCardService {
|
|
|
237
396
|
409
|
|
238
397
|
);
|
|
239
398
|
}
|
|
240
|
-
|
|
241
|
-
await
|
|
399
|
+
requireStatus(card, "pending_review");
|
|
400
|
+
await validateSupportPassportReplacementPrior(storage, card, namespace, principal);
|
|
242
401
|
const updatedAt = this.now().toISOString();
|
|
243
402
|
await this.requireOwnerLock(lock);
|
|
244
403
|
let approved: boolean;
|
|
@@ -246,21 +405,26 @@ export class SupportPassportCardService {
|
|
|
246
405
|
approved = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
247
406
|
card.memory,
|
|
248
407
|
{ status: "active", updated: updatedAt },
|
|
249
|
-
{
|
|
408
|
+
{
|
|
409
|
+
actor: principal,
|
|
410
|
+
reasonCode: parsed.data.reasonCode ?? "owner-approved",
|
|
411
|
+
}
|
|
250
412
|
);
|
|
251
413
|
} catch (error) {
|
|
252
414
|
const current = await storage.getMemoryById(card.card.cardId);
|
|
253
|
-
const currentCard = current ?
|
|
415
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
254
416
|
if (currentCard?.card.status === "active") {
|
|
255
|
-
|
|
417
|
+
onCommitted();
|
|
418
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
256
419
|
}
|
|
257
420
|
throw error;
|
|
258
421
|
}
|
|
259
422
|
if (!approved) {
|
|
260
423
|
const current = await storage.getMemoryById(card.card.cardId);
|
|
261
|
-
const currentCard = current ?
|
|
424
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
262
425
|
if (currentCard?.card.status === "active") {
|
|
263
|
-
|
|
426
|
+
onCommitted();
|
|
427
|
+
return await this.finishCommittedApproval(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
264
428
|
}
|
|
265
429
|
throw new SupportPassportError("storage_conflict", "The support card changed before approval.", 409);
|
|
266
430
|
}
|
|
@@ -268,11 +432,12 @@ export class SupportPassportCardService {
|
|
|
268
432
|
...card.card,
|
|
269
433
|
status: "active",
|
|
270
434
|
updatedAt,
|
|
271
|
-
revision:
|
|
435
|
+
revision: revisionFor(card.card, "active", updatedAt),
|
|
272
436
|
};
|
|
437
|
+
onCommitted();
|
|
273
438
|
try {
|
|
274
439
|
const current = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
275
|
-
return await this.finishCommittedApproval(storage, current, lock, principal, namespace);
|
|
440
|
+
return await this.finishCommittedApproval(storage, current, lock, principal, namespace, onCommitted);
|
|
276
441
|
} catch (error) {
|
|
277
442
|
if (isStorageConflict(error)) throw error;
|
|
278
443
|
log.warn(
|
|
@@ -283,12 +448,18 @@ export class SupportPassportCardService {
|
|
|
283
448
|
});
|
|
284
449
|
}
|
|
285
450
|
|
|
286
|
-
async rejectCard(
|
|
287
|
-
|
|
451
|
+
async rejectCard(
|
|
452
|
+
input: SupportPassportCardMutationInput,
|
|
453
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
454
|
+
): Promise<SupportPassportCard> {
|
|
455
|
+
return await this.changeStatus(input, "pending_review", "rejected", "owner-rejected", options);
|
|
288
456
|
}
|
|
289
457
|
|
|
290
|
-
async withdrawCard(
|
|
291
|
-
|
|
458
|
+
async withdrawCard(
|
|
459
|
+
input: SupportPassportCardMutationInput,
|
|
460
|
+
options: { signal?: AbortSignal; onCommitted?: () => void } = {}
|
|
461
|
+
): Promise<SupportPassportCard> {
|
|
462
|
+
return await this.changeStatus(input, "active", "archived", "owner-withdrew", options);
|
|
292
463
|
}
|
|
293
464
|
|
|
294
465
|
private async createDraft(
|
|
@@ -304,6 +475,9 @@ export class SupportPassportCardService {
|
|
|
304
475
|
replacedRevision?: string;
|
|
305
476
|
order?: number;
|
|
306
477
|
draftReplacementPrepared?: boolean;
|
|
478
|
+
generatedBatch?: { batchId: string; size: number };
|
|
479
|
+
onPersisted?: (card: StoredSupportPassportCard) => void;
|
|
480
|
+
onCommitted?: () => void;
|
|
307
481
|
},
|
|
308
482
|
lock: HeldFileLockController,
|
|
309
483
|
principal: string,
|
|
@@ -311,8 +485,8 @@ export class SupportPassportCardService {
|
|
|
311
485
|
): Promise<SupportPassportCard> {
|
|
312
486
|
const now = this.now();
|
|
313
487
|
const reviewBy = input.reviewBy ?? now.toISOString();
|
|
314
|
-
const storedCards = await this.readStoredCards(storage, lock, principal, namespace);
|
|
315
|
-
const visibleCards =
|
|
488
|
+
const storedCards = await this.readStoredCards(storage, lock, principal, namespace, input.onCommitted);
|
|
489
|
+
const visibleCards = ownerListCards(storedCards);
|
|
316
490
|
const replacesVisibleCard = visibleCards.some(
|
|
317
491
|
(item) =>
|
|
318
492
|
(input.replacesDraftId === item.card.cardId && item.card.status === "pending_review") ||
|
|
@@ -321,8 +495,34 @@ export class SupportPassportCardService {
|
|
|
321
495
|
if (visibleCards.length - (replacesVisibleCard ? 1 : 0) >= MAX_OWNER_VISIBLE_CARDS) {
|
|
322
496
|
throw new SupportPassportError("invalid_input", "A support passport can contain at most 100 visible cards.", 400);
|
|
323
497
|
}
|
|
324
|
-
const
|
|
325
|
-
|
|
498
|
+
const order = input.order ?? storedCards.reduce((maximum, card) => Math.max(maximum, card.order), -1) + 1;
|
|
499
|
+
return await this.persistDraft(storage, { ...input, order }, lock, principal, namespace);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private async persistDraft(
|
|
503
|
+
storage: StorageManager,
|
|
504
|
+
input: {
|
|
505
|
+
title: string;
|
|
506
|
+
statement: string;
|
|
507
|
+
category: SupportPassportCardCategory;
|
|
508
|
+
reviewBy?: string;
|
|
509
|
+
sourceMemoryIds: string[];
|
|
510
|
+
supersedes?: string;
|
|
511
|
+
replacesDraftId?: string;
|
|
512
|
+
replacedRevision?: string;
|
|
513
|
+
order: number;
|
|
514
|
+
draftReplacementPrepared?: boolean;
|
|
515
|
+
generatedBatch?: { batchId: string; size: number };
|
|
516
|
+
onPersisted?: (card: StoredSupportPassportCard) => void;
|
|
517
|
+
onCommitted?: () => void;
|
|
518
|
+
},
|
|
519
|
+
lock: HeldFileLockController,
|
|
520
|
+
principal: string,
|
|
521
|
+
namespace: string
|
|
522
|
+
): Promise<SupportPassportCard> {
|
|
523
|
+
const now = this.now();
|
|
524
|
+
const reviewBy = input.reviewBy ?? now.toISOString();
|
|
525
|
+
const order = input.order;
|
|
326
526
|
if (!Number.isSafeInteger(order)) {
|
|
327
527
|
throw new SupportPassportError("storage_conflict", "The support card order range is exhausted.", 409);
|
|
328
528
|
}
|
|
@@ -338,15 +538,26 @@ export class SupportPassportCardService {
|
|
|
338
538
|
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.category]: input.category,
|
|
339
539
|
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.order]: String(order),
|
|
340
540
|
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.reviewBy]: reviewBy,
|
|
341
|
-
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.sourceMemoryIds]: input.sourceMemoryIds.join(","),
|
|
342
541
|
...(input.replacesDraftId
|
|
343
|
-
? {
|
|
542
|
+
? {
|
|
543
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacesDraftId]: input.replacesDraftId,
|
|
544
|
+
}
|
|
344
545
|
: {}),
|
|
345
546
|
...(input.replacedRevision
|
|
346
|
-
? {
|
|
547
|
+
? {
|
|
548
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.replacedRevision]: input.replacedRevision,
|
|
549
|
+
}
|
|
347
550
|
: {}),
|
|
348
551
|
...(input.draftReplacementPrepared
|
|
349
|
-
? {
|
|
552
|
+
? {
|
|
553
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared]: "true",
|
|
554
|
+
}
|
|
555
|
+
: {}),
|
|
556
|
+
...(input.generatedBatch
|
|
557
|
+
? {
|
|
558
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchId]: input.generatedBatch.batchId,
|
|
559
|
+
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.generatedBatchSize]: String(input.generatedBatch.size),
|
|
560
|
+
}
|
|
350
561
|
: {}),
|
|
351
562
|
},
|
|
352
563
|
sourceReason: "support-passport",
|
|
@@ -364,46 +575,61 @@ export class SupportPassportCardService {
|
|
|
364
575
|
if (written.tombstoneBlocked) {
|
|
365
576
|
throw new SupportPassportError("storage_conflict", "The support card needs memory review before use.", 409);
|
|
366
577
|
}
|
|
367
|
-
|
|
578
|
+
const stored = projectRequiredCard(written.memory, namespace, principal);
|
|
579
|
+
input.onPersisted?.(stored);
|
|
580
|
+
if (!input.generatedBatch) input.onCommitted?.();
|
|
581
|
+
return stored.card;
|
|
368
582
|
}
|
|
369
583
|
|
|
370
584
|
private async changeStatus(
|
|
371
585
|
input: SupportPassportCardMutationInput,
|
|
372
586
|
expectedStatus: "pending_review" | "active",
|
|
373
|
-
nextStatus: "rejected" | "archived"
|
|
587
|
+
nextStatus: "rejected" | "archived",
|
|
588
|
+
defaultReasonCode: string,
|
|
589
|
+
options: { signal?: AbortSignal; onCommitted?: () => void }
|
|
374
590
|
): Promise<SupportPassportCard> {
|
|
375
591
|
const parsed = SupportPassportCardMutationInputSchema.safeParse(input);
|
|
376
592
|
if (!parsed.success) throw invalidInput();
|
|
377
593
|
const { principal, namespace, storage } = await this.resolveOwnerScope(parsed.data.principal);
|
|
594
|
+
const onCommitted = once(options.onCommitted);
|
|
378
595
|
return await withSupportPassportOwnerLock(storage, { namespace, principal }, async (lock) => {
|
|
596
|
+
options.signal?.throwIfAborted();
|
|
379
597
|
let card = await this.requireCard(storage, parsed.data.cardId, namespace, principal);
|
|
380
|
-
|
|
381
|
-
|
|
598
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
599
|
+
requireStatus(card, expectedStatus);
|
|
382
600
|
if (expectedStatus === "pending_review" && nextStatus === "rejected") {
|
|
383
|
-
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
|
|
601
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
|
|
384
602
|
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
385
|
-
|
|
386
|
-
|
|
603
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
604
|
+
requireStatus(card, expectedStatus);
|
|
387
605
|
}
|
|
388
606
|
if (
|
|
389
607
|
expectedStatus === "pending_review" ||
|
|
390
608
|
(expectedStatus === "active" && (card.replacesDraftId || card.memory.frontmatter.supersedes))
|
|
391
609
|
) {
|
|
392
|
-
card =
|
|
393
|
-
await this.recoverReplacementTransition(
|
|
394
|
-
|
|
395
|
-
|
|
610
|
+
card = projectRequiredCard(
|
|
611
|
+
await this.recoverReplacementTransition(
|
|
612
|
+
storage,
|
|
613
|
+
card.memory,
|
|
614
|
+
lock,
|
|
615
|
+
principal,
|
|
616
|
+
namespace,
|
|
617
|
+
{
|
|
618
|
+
rollbackConflictedApproval: expectedStatus !== "active",
|
|
619
|
+
},
|
|
620
|
+
onCommitted
|
|
621
|
+
),
|
|
396
622
|
namespace,
|
|
397
623
|
principal
|
|
398
624
|
);
|
|
399
625
|
}
|
|
400
626
|
if (card.card.status === nextStatus) return card.card;
|
|
401
|
-
|
|
627
|
+
requireStatus(card, expectedStatus);
|
|
402
628
|
if (expectedStatus === "active" && nextStatus === "archived") {
|
|
403
|
-
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace);
|
|
629
|
+
await this.rejectPendingReplacementForPredecessor(storage, card, lock, principal, namespace, onCommitted);
|
|
404
630
|
card = await this.requireCard(storage, card.card.cardId, namespace, principal);
|
|
405
|
-
|
|
406
|
-
|
|
631
|
+
requireRevision(card, parsed.data.expectedRevision);
|
|
632
|
+
requireStatus(card, expectedStatus);
|
|
407
633
|
}
|
|
408
634
|
const updatedAt = this.now().toISOString();
|
|
409
635
|
await this.requireOwnerLock(lock);
|
|
@@ -414,7 +640,10 @@ export class SupportPassportCardService {
|
|
|
414
640
|
updated: updatedAt,
|
|
415
641
|
...(nextStatus === "archived" ? { archivedAt: updatedAt } : {}),
|
|
416
642
|
},
|
|
417
|
-
{
|
|
643
|
+
{
|
|
644
|
+
actor: principal,
|
|
645
|
+
reasonCode: parsed.data.reasonCode ?? defaultReasonCode,
|
|
646
|
+
}
|
|
418
647
|
);
|
|
419
648
|
if (!changed)
|
|
420
649
|
throw new SupportPassportError(
|
|
@@ -422,41 +651,19 @@ export class SupportPassportCardService {
|
|
|
422
651
|
"The support card changed before the request completed.",
|
|
423
652
|
409
|
|
424
653
|
);
|
|
654
|
+
onCommitted();
|
|
425
655
|
return {
|
|
426
656
|
...card.card,
|
|
427
657
|
status: nextStatus,
|
|
428
658
|
updatedAt,
|
|
429
|
-
revision:
|
|
659
|
+
revision: revisionFor(card.card, nextStatus, updatedAt),
|
|
430
660
|
};
|
|
431
661
|
});
|
|
432
662
|
}
|
|
433
663
|
|
|
434
|
-
private revisionFor(card: SupportPassportCard, status: SupportPassportCard["status"], updatedAt: string): string {
|
|
435
|
-
return computeSupportPassportCardRevision({
|
|
436
|
-
cardId: card.cardId,
|
|
437
|
-
title: card.title,
|
|
438
|
-
statement: card.statement,
|
|
439
|
-
category: card.category,
|
|
440
|
-
status,
|
|
441
|
-
updatedAt,
|
|
442
|
-
reviewBy: card.reviewBy,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
664
|
private async resolveOwnerScope(principal: string): Promise<SupportPassportOwnerScope> {
|
|
447
|
-
const requestedPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal });
|
|
448
665
|
const owner = await this.resolveOwner(principal);
|
|
449
|
-
|
|
450
|
-
const ownerPrincipal = SupportPassportListCardsInputSchema.safeParse({ principal: owner.principal });
|
|
451
|
-
if (
|
|
452
|
-
!requestedPrincipal.success ||
|
|
453
|
-
!namespace.success ||
|
|
454
|
-
!ownerPrincipal.success ||
|
|
455
|
-
ownerPrincipal.data.principal !== requestedPrincipal.data.principal
|
|
456
|
-
) {
|
|
457
|
-
throw new SupportPassportError("card_data_invalid", "The support passport owner scope is invalid.", 500);
|
|
458
|
-
}
|
|
459
|
-
return { ...owner, principal: ownerPrincipal.data.principal, namespace: namespace.data };
|
|
666
|
+
return validateOwnerScope(owner, principal);
|
|
460
667
|
}
|
|
461
668
|
|
|
462
669
|
private async rejectCreatedDraft(
|
|
@@ -465,7 +672,8 @@ export class SupportPassportCardService {
|
|
|
465
672
|
reasonCode: string,
|
|
466
673
|
lock: HeldFileLockController,
|
|
467
674
|
principal: string,
|
|
468
|
-
namespace: string
|
|
675
|
+
namespace: string,
|
|
676
|
+
onCommitted?: () => void
|
|
469
677
|
): Promise<void> {
|
|
470
678
|
try {
|
|
471
679
|
const current = await this.requireCard(storage, cardId, namespace, principal);
|
|
@@ -475,7 +683,8 @@ export class SupportPassportCardService {
|
|
|
475
683
|
{ status: "rejected", updated: this.now().toISOString() },
|
|
476
684
|
{ actor: principal, reasonCode }
|
|
477
685
|
);
|
|
478
|
-
if (
|
|
686
|
+
if (rejected) onCommitted?.();
|
|
687
|
+
else log.warn("support passport could not roll back a replacement draft");
|
|
479
688
|
} catch (error) {
|
|
480
689
|
log.warn(
|
|
481
690
|
`support passport could not roll back a replacement draft: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -483,164 +692,70 @@ export class SupportPassportCardService {
|
|
|
483
692
|
}
|
|
484
693
|
}
|
|
485
694
|
|
|
486
|
-
private async
|
|
695
|
+
private async rejectGeneratedDraft(
|
|
487
696
|
storage: StorageManager,
|
|
488
697
|
cardId: string,
|
|
489
|
-
|
|
490
|
-
principal: string
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return card;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private projectOwnedCard(memory: MemoryFile, namespace: string, principal: string): StoredSupportPassportCard | null {
|
|
511
|
-
const card = projectSupportPassportCard(memory);
|
|
512
|
-
return card?.namespace === namespace &&
|
|
513
|
-
card.owner === computeSupportPassportOwnerKey(principal)
|
|
514
|
-
? card
|
|
515
|
-
: null;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private ownsMemory(memory: MemoryFile, namespace: string, principal: string): boolean {
|
|
519
|
-
const attributes = memory.frontmatter.structuredAttributes;
|
|
520
|
-
return (
|
|
521
|
-
attributes !== undefined &&
|
|
522
|
-
decodeSupportPassportNamespaceAttributes(attributes) === namespace &&
|
|
523
|
-
attributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner] === computeSupportPassportOwnerKey(principal)
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
private async readProjectedCards(
|
|
528
|
-
storage: StorageManager,
|
|
529
|
-
namespace: string,
|
|
530
|
-
principal: string
|
|
531
|
-
): Promise<StoredSupportPassportCard[]> {
|
|
532
|
-
const owner = computeSupportPassportOwnerKey(principal);
|
|
533
|
-
const projected = (await storage.readAllMemories())
|
|
534
|
-
.map(projectSupportPassportCard)
|
|
535
|
-
.filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
|
|
536
|
-
const ids = new Set<string>();
|
|
537
|
-
for (const item of projected) {
|
|
538
|
-
if (ids.has(item.card.cardId)) {
|
|
539
|
-
throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
|
|
540
|
-
}
|
|
541
|
-
ids.add(item.card.cardId);
|
|
542
|
-
}
|
|
543
|
-
return projected;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
private requireRevision(card: StoredSupportPassportCard, expectedRevision: string): void {
|
|
547
|
-
if (card.card.revision !== expectedRevision) {
|
|
548
|
-
throw new SupportPassportError("revision_conflict", "The support card changed after it was loaded.", 409);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
private requireStatus(card: StoredSupportPassportCard, status: SupportPassportCard["status"]): void {
|
|
553
|
-
if (card.card.status !== status) {
|
|
554
|
-
throw new SupportPassportError("invalid_card_status", `The support card must have status ${status}.`, 409);
|
|
698
|
+
lock: HeldFileLockController,
|
|
699
|
+
principal: string,
|
|
700
|
+
namespace: string
|
|
701
|
+
): Promise<boolean> {
|
|
702
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
703
|
+
const memory = await storage.getMemoryById(cardId);
|
|
704
|
+
if (!memory) return true;
|
|
705
|
+
const stored = projectOwnedCard(memory, namespace, principal);
|
|
706
|
+
if (!stored) return false;
|
|
707
|
+
if (stored.card.status === "rejected") return true;
|
|
708
|
+
if (stored.card.status !== "pending_review") return false;
|
|
709
|
+
await this.requireOwnerLock(lock);
|
|
710
|
+
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
711
|
+
memory,
|
|
712
|
+
{ status: "rejected", updated: this.now().toISOString() },
|
|
713
|
+
{ actor: principal, reasonCode: "draft-batch-failed" }
|
|
714
|
+
);
|
|
715
|
+
if (rejected) return true;
|
|
555
716
|
}
|
|
717
|
+
return false;
|
|
556
718
|
}
|
|
557
719
|
|
|
558
720
|
private async readStoredCards(
|
|
559
721
|
storage: StorageManager,
|
|
560
722
|
lock: HeldFileLockController,
|
|
561
723
|
principal: string,
|
|
562
|
-
namespace: string
|
|
724
|
+
namespace: string,
|
|
725
|
+
onCommitted?: () => void
|
|
563
726
|
): Promise<StoredSupportPassportCard[]> {
|
|
564
727
|
const initialVersion = storage.getCorpusScanVersion();
|
|
565
728
|
const initial = await storage.readAllMemories();
|
|
729
|
+
await recoverSupportPassportGeneratedBatches(
|
|
730
|
+
this.generatedBatchContext(storage, lock, principal, namespace, onCommitted),
|
|
731
|
+
initial
|
|
732
|
+
);
|
|
566
733
|
for (const memory of initial) {
|
|
567
|
-
const card =
|
|
568
|
-
if (card)
|
|
734
|
+
const card = projectOwnedCard(memory, namespace, principal);
|
|
735
|
+
if (card) {
|
|
736
|
+
await this.recoverReplacementTransition(storage, memory, lock, principal, namespace, {}, onCommitted);
|
|
737
|
+
}
|
|
569
738
|
}
|
|
570
739
|
const memories: MemoryFile[] =
|
|
571
740
|
storage.getCorpusScanVersion() === initialVersion ? initial : await storage.readAllMemories();
|
|
572
|
-
|
|
573
|
-
const projected = memories
|
|
574
|
-
.map(projectSupportPassportCard)
|
|
575
|
-
.filter((card): card is StoredSupportPassportCard => card?.namespace === namespace && card.owner === owner);
|
|
576
|
-
const ids = new Set<string>();
|
|
577
|
-
for (const item of projected) {
|
|
578
|
-
if (ids.has(item.card.cardId)) {
|
|
579
|
-
throw new SupportPassportError("card_data_invalid", "Support card IDs must be unique.", 500);
|
|
580
|
-
}
|
|
581
|
-
ids.add(item.card.cardId);
|
|
582
|
-
}
|
|
583
|
-
return projected;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
private ownerVisibleCards(storedCards: StoredSupportPassportCard[]): StoredSupportPassportCard[] {
|
|
587
|
-
const visibleCards = storedCards.filter(
|
|
588
|
-
(item) => OWNER_VISIBLE_STATUSES.has(item.card.status) && !item.draftReplacementPrepared
|
|
589
|
-
);
|
|
590
|
-
if (visibleCards.length <= MAX_OWNER_VISIBLE_CARDS) return visibleCards;
|
|
591
|
-
const activeCardsWithPendingReplacements = new Set(
|
|
592
|
-
visibleCards
|
|
593
|
-
.filter((item) => item.card.status === "pending_review")
|
|
594
|
-
.map((item) => item.memory.frontmatter.supersedes)
|
|
595
|
-
.filter((cardId): cardId is string => typeof cardId === "string")
|
|
596
|
-
);
|
|
597
|
-
return visibleCards.filter(
|
|
598
|
-
(item) => item.card.status !== "active" || !activeCardsWithPendingReplacements.has(item.card.cardId)
|
|
599
|
-
);
|
|
741
|
+
return await projectCommittedSupportPassportCards(storage, memories, namespace, principal);
|
|
600
742
|
}
|
|
601
743
|
|
|
602
|
-
private
|
|
744
|
+
private generatedBatchContext(
|
|
603
745
|
storage: StorageManager,
|
|
604
|
-
replacement: StoredSupportPassportCard,
|
|
605
746
|
lock: HeldFileLockController,
|
|
606
747
|
principal: string,
|
|
607
|
-
namespace: string
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (prior?.card.status === "rejected") return null;
|
|
619
|
-
if (!priorMemory || (!alreadyRetired && prior?.card.status !== "active")) {
|
|
620
|
-
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
621
|
-
}
|
|
622
|
-
if (alreadyRetired) return priorId;
|
|
623
|
-
const retiredAt = this.now().toISOString();
|
|
624
|
-
await this.requireOwnerLock(lock);
|
|
625
|
-
const retired = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
626
|
-
priorMemory,
|
|
627
|
-
{
|
|
628
|
-
status: "superseded",
|
|
629
|
-
supersededBy: replacement.card.cardId,
|
|
630
|
-
supersededAt: retiredAt,
|
|
631
|
-
supersessionCause: "direct",
|
|
632
|
-
updated: retiredAt,
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
actor: principal,
|
|
636
|
-
reasonCode: "support-passport-replacement-pending",
|
|
637
|
-
relatedMemoryIds: [replacement.card.cardId],
|
|
638
|
-
}
|
|
639
|
-
);
|
|
640
|
-
if (!retired) {
|
|
641
|
-
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
642
|
-
}
|
|
643
|
-
return priorId;
|
|
748
|
+
namespace: string,
|
|
749
|
+
onCommitted?: () => void
|
|
750
|
+
) {
|
|
751
|
+
return {
|
|
752
|
+
storage,
|
|
753
|
+
principal,
|
|
754
|
+
namespace,
|
|
755
|
+
now: this.now,
|
|
756
|
+
requireOwnerLock: async () => await this.requireOwnerLock(lock),
|
|
757
|
+
...(onCommitted ? { onCommitted } : {}),
|
|
758
|
+
};
|
|
644
759
|
}
|
|
645
760
|
|
|
646
761
|
private async rejectPendingReplacementForPredecessor(
|
|
@@ -648,12 +763,13 @@ export class SupportPassportCardService {
|
|
|
648
763
|
predecessor: StoredSupportPassportCard,
|
|
649
764
|
lock: HeldFileLockController,
|
|
650
765
|
principal: string,
|
|
651
|
-
namespace: string
|
|
766
|
+
namespace: string,
|
|
767
|
+
onCommitted?: () => void
|
|
652
768
|
): Promise<void> {
|
|
653
769
|
const storedCards =
|
|
654
770
|
predecessor.card.status === "active"
|
|
655
|
-
? await this.readStoredCards(storage, lock, principal, namespace)
|
|
656
|
-
: await
|
|
771
|
+
? await this.readStoredCards(storage, lock, principal, namespace, onCommitted)
|
|
772
|
+
: await readCommittedSupportPassportCards(storage, namespace, principal);
|
|
657
773
|
const replacements = storedCards.filter((item) => {
|
|
658
774
|
if (item.card.status !== "pending_review") return false;
|
|
659
775
|
return predecessor.card.status === "pending_review"
|
|
@@ -678,44 +794,27 @@ export class SupportPassportCardService {
|
|
|
678
794
|
reasonCode: predecessor.card.status === "pending_review" ? "source-draft-rejected" : "predecessor-withdrawn",
|
|
679
795
|
}
|
|
680
796
|
);
|
|
681
|
-
if (rejected)
|
|
797
|
+
if (rejected) {
|
|
798
|
+
onCommitted?.();
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
682
801
|
const current = await storage.getMemoryById(replacement.card.cardId);
|
|
683
|
-
const currentCard = current ?
|
|
802
|
+
const currentCard = current ? projectOwnedCard(current, namespace, principal) : null;
|
|
684
803
|
if (currentCard?.card.status !== "rejected") {
|
|
685
804
|
throw new SupportPassportError("storage_conflict", "The pending support card edit could not be cancelled.", 409);
|
|
686
805
|
}
|
|
687
806
|
}
|
|
688
807
|
|
|
689
|
-
private async validatePriorForReplacement(
|
|
690
|
-
storage: StorageManager,
|
|
691
|
-
replacement: StoredSupportPassportCard,
|
|
692
|
-
namespace: string,
|
|
693
|
-
principal: string
|
|
694
|
-
): Promise<void> {
|
|
695
|
-
const priorId = replacement.memory.frontmatter.supersedes;
|
|
696
|
-
if (!priorId) return;
|
|
697
|
-
const priorMemory = await storage.getMemoryById(priorId);
|
|
698
|
-
const priorIsOwned = priorMemory ? this.ownsMemory(priorMemory, namespace, principal) : false;
|
|
699
|
-
const alreadyRetired =
|
|
700
|
-
priorIsOwned &&
|
|
701
|
-
priorMemory?.frontmatter.status === "superseded" &&
|
|
702
|
-
priorMemory.frontmatter.supersededBy === replacement.card.cardId;
|
|
703
|
-
const prior = priorMemory && priorIsOwned && !alreadyRetired ? projectSupportPassportCard(priorMemory) : null;
|
|
704
|
-
if (prior?.card.status === "rejected" || alreadyRetired) return;
|
|
705
|
-
if (!priorMemory || prior?.card.status !== "active") {
|
|
706
|
-
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
808
|
private async recoverReplacementTransition(
|
|
711
809
|
storage: StorageManager,
|
|
712
810
|
memory: MemoryFile,
|
|
713
811
|
lock: HeldFileLockController,
|
|
714
812
|
principal: string,
|
|
715
813
|
namespace: string,
|
|
716
|
-
options: { rollbackConflictedApproval?: boolean } = {}
|
|
814
|
+
options: { rollbackConflictedApproval?: boolean } = {},
|
|
815
|
+
onCommitted?: () => void
|
|
717
816
|
): Promise<MemoryFile> {
|
|
718
|
-
const replacement =
|
|
817
|
+
const replacement = projectOwnedCard(memory, namespace, principal);
|
|
719
818
|
if (replacement?.card.status !== "pending_review" && replacement?.card.status !== "active") return memory;
|
|
720
819
|
if (!replacement.replacesDraftId && !memory.frontmatter.supersedes) return memory;
|
|
721
820
|
if (
|
|
@@ -724,9 +823,9 @@ export class SupportPassportCardService {
|
|
|
724
823
|
) {
|
|
725
824
|
return memory;
|
|
726
825
|
}
|
|
727
|
-
await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace);
|
|
826
|
+
await this.recoverReplacedDraft(storage, replacement, lock, principal, namespace, onCommitted);
|
|
728
827
|
const currentMemory = (await storage.getMemoryById(replacement.card.cardId)) ?? memory;
|
|
729
|
-
const currentCard =
|
|
828
|
+
const currentCard = projectOwnedCard(currentMemory, namespace, principal);
|
|
730
829
|
if (currentCard?.card.status !== "pending_review" && currentCard?.card.status !== "active") {
|
|
731
830
|
return currentMemory;
|
|
732
831
|
}
|
|
@@ -736,7 +835,7 @@ export class SupportPassportCardService {
|
|
|
736
835
|
const prior = await storage.getMemoryById(priorId);
|
|
737
836
|
if (
|
|
738
837
|
prior &&
|
|
739
|
-
|
|
838
|
+
ownsMemory(prior, namespace, principal) &&
|
|
740
839
|
prior.frontmatter.status === "superseded" &&
|
|
741
840
|
prior.frontmatter.supersededBy === replacement.card.cardId
|
|
742
841
|
) {
|
|
@@ -746,17 +845,18 @@ export class SupportPassportCardService {
|
|
|
746
845
|
replacement.card.cardId,
|
|
747
846
|
lock,
|
|
748
847
|
principal,
|
|
749
|
-
namespace
|
|
848
|
+
namespace,
|
|
849
|
+
onCommitted
|
|
750
850
|
);
|
|
751
851
|
}
|
|
752
852
|
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
753
853
|
}
|
|
754
854
|
try {
|
|
755
|
-
await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace);
|
|
855
|
+
await this.completeReplacementAfterActivation(storage, currentCard, lock, principal, namespace, onCommitted);
|
|
756
856
|
} catch (error) {
|
|
757
857
|
if (!isStorageConflict(error)) throw error;
|
|
758
858
|
if (options.rollbackConflictedApproval === false) throw error;
|
|
759
|
-
await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace);
|
|
859
|
+
await this.rollbackConflictedApproval(storage, currentCard.card.cardId, lock, principal, namespace, onCommitted);
|
|
760
860
|
}
|
|
761
861
|
return (await storage.getMemoryById(replacement.card.cardId)) ?? currentMemory;
|
|
762
862
|
}
|
|
@@ -766,13 +866,33 @@ export class SupportPassportCardService {
|
|
|
766
866
|
replacement: StoredSupportPassportCard,
|
|
767
867
|
lock: HeldFileLockController,
|
|
768
868
|
principal: string,
|
|
769
|
-
namespace: string
|
|
869
|
+
namespace: string,
|
|
870
|
+
onCommitted?: () => void
|
|
770
871
|
): Promise<void> {
|
|
771
|
-
const priorId = await
|
|
772
|
-
|
|
872
|
+
const priorId = await prepareSupportPassportReplacementPrior({
|
|
873
|
+
storage,
|
|
874
|
+
replacement,
|
|
875
|
+
principal,
|
|
876
|
+
namespace,
|
|
877
|
+
now: this.now,
|
|
878
|
+
requireOwnerLock: () => this.requireOwnerLock(lock),
|
|
879
|
+
onCommitted,
|
|
880
|
+
});
|
|
881
|
+
if (
|
|
882
|
+
priorId &&
|
|
883
|
+
!(await completeSupportPassportReplacementPrior({
|
|
884
|
+
storage,
|
|
885
|
+
priorId,
|
|
886
|
+
replacement,
|
|
887
|
+
principal,
|
|
888
|
+
namespace,
|
|
889
|
+
requireOwnerLock: () => this.requireOwnerLock(lock),
|
|
890
|
+
}))
|
|
891
|
+
) {
|
|
773
892
|
throw new SupportPassportError("storage_conflict", "The prior support card changed before replacement.", 409);
|
|
774
893
|
}
|
|
775
|
-
|
|
894
|
+
if (priorId) onCommitted?.();
|
|
895
|
+
await this.markReplacementComplete(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
|
|
776
896
|
}
|
|
777
897
|
|
|
778
898
|
private async finishCommittedApproval(
|
|
@@ -780,15 +900,23 @@ export class SupportPassportCardService {
|
|
|
780
900
|
replacement: StoredSupportPassportCard,
|
|
781
901
|
lock: HeldFileLockController,
|
|
782
902
|
principal: string,
|
|
783
|
-
namespace: string
|
|
903
|
+
namespace: string,
|
|
904
|
+
onCommitted?: () => void
|
|
784
905
|
): Promise<SupportPassportCard> {
|
|
785
906
|
try {
|
|
786
907
|
if (replacement.replacesDraftId || replacement.memory.frontmatter.supersedes) {
|
|
787
|
-
await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace);
|
|
908
|
+
await this.completeReplacementAfterActivation(storage, replacement, lock, principal, namespace, onCommitted);
|
|
788
909
|
}
|
|
789
910
|
} catch (error) {
|
|
790
911
|
if (isStorageConflict(error)) {
|
|
791
|
-
await this.rollbackConflictedApproval(
|
|
912
|
+
await this.rollbackConflictedApproval(
|
|
913
|
+
storage,
|
|
914
|
+
replacement.card.cardId,
|
|
915
|
+
lock,
|
|
916
|
+
principal,
|
|
917
|
+
namespace,
|
|
918
|
+
onCommitted
|
|
919
|
+
);
|
|
792
920
|
throw error;
|
|
793
921
|
}
|
|
794
922
|
log.warn(
|
|
@@ -803,7 +931,8 @@ export class SupportPassportCardService {
|
|
|
803
931
|
replacementId: string,
|
|
804
932
|
lock: HeldFileLockController,
|
|
805
933
|
principal: string,
|
|
806
|
-
namespace: string
|
|
934
|
+
namespace: string,
|
|
935
|
+
onCommitted?: () => void
|
|
807
936
|
): Promise<void> {
|
|
808
937
|
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
809
938
|
if (replacement.card.status !== "active") return;
|
|
@@ -820,54 +949,19 @@ export class SupportPassportCardService {
|
|
|
820
949
|
409
|
|
821
950
|
);
|
|
822
951
|
}
|
|
952
|
+
onCommitted?.();
|
|
823
953
|
const priorId = replacement.memory.frontmatter.supersedes;
|
|
824
954
|
if (priorId) {
|
|
825
|
-
await this.restorePriorAfterApprovalFailure(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
principal: string,
|
|
835
|
-
namespace: string
|
|
836
|
-
): Promise<boolean> {
|
|
837
|
-
const replacementId = replacement.card.cardId;
|
|
838
|
-
const prior = await storage.getMemoryById(priorId);
|
|
839
|
-
if (
|
|
840
|
-
!prior ||
|
|
841
|
-
!this.ownsMemory(prior, namespace, principal) ||
|
|
842
|
-
prior.frontmatter.status !== "superseded" ||
|
|
843
|
-
prior.frontmatter.supersededBy !== replacementId
|
|
844
|
-
) {
|
|
845
|
-
return false;
|
|
955
|
+
await this.restorePriorAfterApprovalFailure(
|
|
956
|
+
storage,
|
|
957
|
+
priorId,
|
|
958
|
+
replacementId,
|
|
959
|
+
lock,
|
|
960
|
+
principal,
|
|
961
|
+
namespace,
|
|
962
|
+
onCommitted
|
|
963
|
+
);
|
|
846
964
|
}
|
|
847
|
-
await this.requireOwnerLock(lock);
|
|
848
|
-
const completed = await storage.supersedeMemory(
|
|
849
|
-
priorId,
|
|
850
|
-
replacementId,
|
|
851
|
-
"support-passport-replacement",
|
|
852
|
-
{ supersessionCause: "direct" },
|
|
853
|
-
{
|
|
854
|
-
actor: principal,
|
|
855
|
-
requireActive: true,
|
|
856
|
-
acceptExactReplay: true,
|
|
857
|
-
expectedSnapshot: prior,
|
|
858
|
-
audit: {
|
|
859
|
-
content: stripAttributesSuffix(prior.content),
|
|
860
|
-
tags: ["supersession", "auto-resolved", "support-passport-audit"],
|
|
861
|
-
structuredAttributes: {
|
|
862
|
-
...encodeSupportPassportNamespaceAttributes(namespace),
|
|
863
|
-
[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.owner]: computeSupportPassportOwnerKey(principal),
|
|
864
|
-
},
|
|
865
|
-
source: "support-passport",
|
|
866
|
-
},
|
|
867
|
-
}
|
|
868
|
-
);
|
|
869
|
-
if (!completed) log.warn("support passport could not complete replacement retirement side effects");
|
|
870
|
-
return completed;
|
|
871
965
|
}
|
|
872
966
|
|
|
873
967
|
private async recoverReplacedDraft(
|
|
@@ -875,11 +969,12 @@ export class SupportPassportCardService {
|
|
|
875
969
|
replacement: StoredSupportPassportCard,
|
|
876
970
|
lock: HeldFileLockController,
|
|
877
971
|
principal: string,
|
|
878
|
-
namespace: string
|
|
972
|
+
namespace: string,
|
|
973
|
+
onCommitted?: () => void
|
|
879
974
|
): Promise<void> {
|
|
880
975
|
if (!replacement.replacesDraftId) return;
|
|
881
976
|
const replacedDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
882
|
-
const projectedDraft = replacedDraft ?
|
|
977
|
+
const projectedDraft = replacedDraft ? projectOwnedCard(replacedDraft, namespace, principal) : null;
|
|
883
978
|
if (
|
|
884
979
|
replacedDraft &&
|
|
885
980
|
projectedDraft?.card.status === "pending_review" &&
|
|
@@ -892,27 +987,42 @@ export class SupportPassportCardService {
|
|
|
892
987
|
{ actor: principal, reasonCode: "complete-draft-replacement" }
|
|
893
988
|
);
|
|
894
989
|
if (rejected) {
|
|
895
|
-
|
|
990
|
+
onCommitted?.();
|
|
991
|
+
await this.finishPreparedDraftReplacement(
|
|
992
|
+
storage,
|
|
993
|
+
replacement.card.cardId,
|
|
994
|
+
lock,
|
|
995
|
+
principal,
|
|
996
|
+
namespace,
|
|
997
|
+
onCommitted
|
|
998
|
+
);
|
|
896
999
|
return;
|
|
897
1000
|
}
|
|
898
1001
|
}
|
|
899
1002
|
const currentDraft = await storage.getMemoryById(replacement.replacesDraftId);
|
|
900
1003
|
if (
|
|
901
1004
|
currentDraft &&
|
|
902
|
-
|
|
1005
|
+
ownsMemory(currentDraft, namespace, principal) &&
|
|
903
1006
|
currentDraft.frontmatter.status === "rejected"
|
|
904
1007
|
) {
|
|
905
|
-
await this.finishPreparedDraftReplacement(
|
|
1008
|
+
await this.finishPreparedDraftReplacement(
|
|
1009
|
+
storage,
|
|
1010
|
+
replacement.card.cardId,
|
|
1011
|
+
lock,
|
|
1012
|
+
principal,
|
|
1013
|
+
namespace,
|
|
1014
|
+
onCommitted
|
|
1015
|
+
);
|
|
906
1016
|
return;
|
|
907
1017
|
}
|
|
908
1018
|
if (
|
|
909
1019
|
currentDraft &&
|
|
910
|
-
|
|
1020
|
+
ownsMemory(currentDraft, namespace, principal) &&
|
|
911
1021
|
currentDraft.frontmatter.status === "pending_review"
|
|
912
1022
|
) {
|
|
913
1023
|
throw new SupportPassportError("storage_conflict", "The replaced draft changed during recovery.", 409);
|
|
914
1024
|
}
|
|
915
|
-
await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace);
|
|
1025
|
+
await this.rejectOrphanedReplacement(storage, replacement.card.cardId, lock, principal, namespace, onCommitted);
|
|
916
1026
|
}
|
|
917
1027
|
|
|
918
1028
|
private async finishPreparedDraftReplacement(
|
|
@@ -920,11 +1030,14 @@ export class SupportPassportCardService {
|
|
|
920
1030
|
replacementId: string,
|
|
921
1031
|
lock: HeldFileLockController,
|
|
922
1032
|
principal: string,
|
|
923
|
-
namespace: string
|
|
1033
|
+
namespace: string,
|
|
1034
|
+
onCommitted?: () => void
|
|
924
1035
|
): Promise<SupportPassportCard> {
|
|
925
1036
|
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
926
1037
|
if (!replacement.draftReplacementPrepared) return replacement.card;
|
|
927
|
-
const structuredAttributes = {
|
|
1038
|
+
const structuredAttributes = {
|
|
1039
|
+
...replacement.memory.frontmatter.structuredAttributes,
|
|
1040
|
+
};
|
|
928
1041
|
delete structuredAttributes[SUPPORT_PASSPORT_ATTRIBUTE_KEYS.draftReplacementPrepared];
|
|
929
1042
|
await this.requireOwnerLock(lock);
|
|
930
1043
|
const finished = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
@@ -933,8 +1046,15 @@ export class SupportPassportCardService {
|
|
|
933
1046
|
{ actor: principal, reasonCode: "draft-replacement-complete" }
|
|
934
1047
|
);
|
|
935
1048
|
if (finished) {
|
|
936
|
-
|
|
937
|
-
|
|
1049
|
+
onCommitted?.();
|
|
1050
|
+
return projectRequiredCard(
|
|
1051
|
+
{
|
|
1052
|
+
...replacement.memory,
|
|
1053
|
+
frontmatter: {
|
|
1054
|
+
...replacement.memory.frontmatter,
|
|
1055
|
+
structuredAttributes,
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
938
1058
|
namespace,
|
|
939
1059
|
principal
|
|
940
1060
|
).card;
|
|
@@ -949,18 +1069,22 @@ export class SupportPassportCardService {
|
|
|
949
1069
|
replacementId: string,
|
|
950
1070
|
lock: HeldFileLockController,
|
|
951
1071
|
principal: string,
|
|
952
|
-
namespace: string
|
|
1072
|
+
namespace: string,
|
|
1073
|
+
onCommitted?: () => void
|
|
953
1074
|
): Promise<void> {
|
|
954
1075
|
const currentReplacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
955
1076
|
if (currentReplacement.card.status === "rejected") return;
|
|
956
|
-
|
|
1077
|
+
requireStatus(currentReplacement, "pending_review");
|
|
957
1078
|
await this.requireOwnerLock(lock);
|
|
958
1079
|
const rejected = await storage.writeMemoryFrontmatterIfUnchanged(
|
|
959
1080
|
currentReplacement.memory,
|
|
960
1081
|
{ status: "rejected", updated: this.now().toISOString() },
|
|
961
1082
|
{ actor: principal, reasonCode: "replaced-draft-approved" }
|
|
962
1083
|
);
|
|
963
|
-
if (rejected)
|
|
1084
|
+
if (rejected) {
|
|
1085
|
+
onCommitted?.();
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
964
1088
|
const latest = await storage.getMemoryById(replacementId);
|
|
965
1089
|
if (latest?.frontmatter.status !== "rejected") {
|
|
966
1090
|
throw new SupportPassportError("storage_conflict", "The orphaned replacement could not be rejected.", 409);
|
|
@@ -973,15 +1097,16 @@ export class SupportPassportCardService {
|
|
|
973
1097
|
replacementId: string,
|
|
974
1098
|
lock: HeldFileLockController,
|
|
975
1099
|
principal: string,
|
|
976
|
-
namespace: string
|
|
1100
|
+
namespace: string,
|
|
1101
|
+
onCommitted?: () => void
|
|
977
1102
|
): Promise<void> {
|
|
978
1103
|
const replacement = await storage.getMemoryById(replacementId);
|
|
979
|
-
if (replacement && !
|
|
1104
|
+
if (replacement && !ownsMemory(replacement, namespace, principal)) {
|
|
980
1105
|
throw new SupportPassportError("storage_conflict", "The replacement support card changed ownership.", 409);
|
|
981
1106
|
}
|
|
982
1107
|
if (replacement?.frontmatter.status === "active") return;
|
|
983
1108
|
const prior = await storage.getMemoryById(priorId);
|
|
984
|
-
if (prior && !
|
|
1109
|
+
if (prior && !ownsMemory(prior, namespace, principal)) {
|
|
985
1110
|
throw new SupportPassportError("storage_conflict", "The prior support card changed ownership.", 409);
|
|
986
1111
|
}
|
|
987
1112
|
if (prior?.frontmatter.status === "active" && prior.frontmatter.supersededBy === undefined) return;
|
|
@@ -1001,7 +1126,10 @@ export class SupportPassportCardService {
|
|
|
1001
1126
|
},
|
|
1002
1127
|
{ actor: principal, reasonCode: "replacement-activation-failed" }
|
|
1003
1128
|
);
|
|
1004
|
-
if (restored)
|
|
1129
|
+
if (restored) {
|
|
1130
|
+
onCommitted?.();
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1005
1133
|
const currentPrior = await storage.getMemoryById(priorId);
|
|
1006
1134
|
if (currentPrior?.frontmatter.status === "active" && currentPrior.frontmatter.supersededBy === undefined) return;
|
|
1007
1135
|
throw new SupportPassportError("storage_conflict", "The prior support card could not be restored.", 409);
|
|
@@ -1012,7 +1140,8 @@ export class SupportPassportCardService {
|
|
|
1012
1140
|
replacementId: string,
|
|
1013
1141
|
lock: HeldFileLockController,
|
|
1014
1142
|
principal: string,
|
|
1015
|
-
namespace: string
|
|
1143
|
+
namespace: string,
|
|
1144
|
+
onCommitted?: () => void
|
|
1016
1145
|
): Promise<void> {
|
|
1017
1146
|
const replacement = await this.requireCard(storage, replacementId, namespace, principal);
|
|
1018
1147
|
if (
|
|
@@ -1032,11 +1161,24 @@ export class SupportPassportCardService {
|
|
|
1032
1161
|
},
|
|
1033
1162
|
{ actor: principal, reasonCode: "replacement-side-effects-complete" }
|
|
1034
1163
|
);
|
|
1035
|
-
if (
|
|
1164
|
+
if (marked) onCommitted?.();
|
|
1165
|
+
else log.warn("support passport could not mark replacement side effects complete");
|
|
1036
1166
|
}
|
|
1037
1167
|
|
|
1038
1168
|
private async requireOwnerLock(lock: HeldFileLockController): Promise<void> {
|
|
1039
1169
|
if (await lock.refresh()) return;
|
|
1040
1170
|
throw new SupportPassportError("storage_conflict", "The support passport lock changed. Try again.", 409);
|
|
1041
1171
|
}
|
|
1172
|
+
|
|
1173
|
+
private async requireCard(
|
|
1174
|
+
storage: StorageManager,
|
|
1175
|
+
cardId: string,
|
|
1176
|
+
namespace: string,
|
|
1177
|
+
principal: string
|
|
1178
|
+
): Promise<StoredSupportPassportCard> {
|
|
1179
|
+
const memory = await storage.getMemoryById(cardId);
|
|
1180
|
+
const card = memory ? projectOwnedCard(memory, namespace, principal) : null;
|
|
1181
|
+
if (card && (await isCommittedGeneratedCard(storage, card))) return card;
|
|
1182
|
+
throw new SupportPassportError("card_not_found", "The support card was not found.", 404);
|
|
1183
|
+
}
|
|
1042
1184
|
}
|