@ai-matrx/vault 0.1.53 → 0.1.55

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.55
4
+
5
+ - Password Health can discover a prior scan when neither request nor execution handle is available, and binds recovered results and progress to the originating health port. Consumer action: custom `HealthPort` implementations must provide `discover(cursor, signal)`; `createApiVaultPort` wires it automatically. Discovery returns authorized request/execution references only.
6
+
7
+ ## 0.1.54
8
+
9
+ - Saved health recovery shows Checking status while its read is pending. A missing execution or failed initial recovery read settles as Unavailable and keeps Retry available. Consumer action: none; the existing request/execution handle round trip is unchanged.
10
+
3
11
  ## 0.1.53
4
12
 
5
13
  - Added the shared Password health view, durable scan recovery, explicit unchecked breach lookup, current-result accounting and existing editor remediation. Consumer action: adopt the compatible generated API with health stream types and runtime follower before this Vault package, and verify server support. Breach checks require explicit selection; unknown coverage remains visible. Installed and native acceptance remain separate.
package/dist/index.cjs CHANGED
@@ -682,7 +682,7 @@ function PasswordHealthView(props) {
682
682
  const latest = (0, import_react4.useRef)(props);
683
683
  latest.current = props;
684
684
  const [result, setResult] = (0, import_react4.useState)(null);
685
- const [status, setStatus] = (0, import_react4.useState)("No report");
685
+ const [status, setStatus] = (0, import_react4.useState)("Checking status");
686
686
  const [error, setError] = (0, import_react4.useState)(null);
687
687
  const [progress, setProgress] = (0, import_react4.useState)(null);
688
688
  const [busy, setBusy] = (0, import_react4.useState)(false);
@@ -692,9 +692,13 @@ function PasswordHealthView(props) {
692
692
  }, [port]);
693
693
  const [retry, setRetry] = (0, import_react4.useState)(0);
694
694
  const active = (0, import_react4.useRef)(null);
695
- const starting = (0, import_react4.useRef)(false);
695
+ const starting = (0, import_react4.useRef)(null);
696
696
  const ownedAddress = (0, import_react4.useRef)(null);
697
697
  const writeReference = (request, execution) => {
698
+ if (starting.current?.controller === active.current) {
699
+ starting.current.request = request;
700
+ starting.current.execution = execution;
701
+ }
698
702
  ownedAddress.current = { request, execution, items: latest.current.items, retry };
699
703
  latest.current.onReference(request, execution);
700
704
  };
@@ -722,7 +726,7 @@ function PasswordHealthView(props) {
722
726
  const value = await port.read(id, controller.signal);
723
727
  if (owns(controller)) {
724
728
  setStatus(STATUS_LABEL[value.execution_status]);
725
- setResult({ value, items: inventory });
729
+ setResult({ value, items: inventory, port });
726
730
  }
727
731
  return value;
728
732
  };
@@ -734,11 +738,33 @@ function PasswordHealthView(props) {
734
738
  if (!owns(controller)) return;
735
739
  id = known.operations[0]?.execution_id;
736
740
  if (!id) {
741
+ setStatus("Unavailable");
737
742
  setError("Status unavailable");
738
743
  return;
739
744
  }
740
745
  writeReference(request, id);
741
746
  }
747
+ if (!id && !request) {
748
+ let cursor;
749
+ const seen = /* @__PURE__ */ new Set();
750
+ do {
751
+ const page = await port.discover(cursor, controller.signal);
752
+ if (!owns(controller)) return;
753
+ const reference = page.references[0];
754
+ if (reference) {
755
+ id = reference.execution_id;
756
+ writeReference(reference.request_id, reference.execution_id);
757
+ break;
758
+ }
759
+ cursor = page.continuation ?? void 0;
760
+ if (cursor && seen.has(cursor)) throw new Error("Report unavailable");
761
+ if (cursor) seen.add(cursor);
762
+ } while (cursor);
763
+ if (!id) {
764
+ setStatus("No report");
765
+ return;
766
+ }
767
+ }
742
768
  if (!id || !owns(controller)) return;
743
769
  const first = await read(id, controller);
744
770
  if (!owns(controller) || terminal(first.execution_status)) return;
@@ -748,7 +774,7 @@ function PasswordHealthView(props) {
748
774
  if (!owns(controller)) return;
749
775
  const detail = event.detail;
750
776
  if (event.kind === "note" && detail?.label === "password_health_progress" && detail.__kind === "password_health_progress" && typeof detail.checked === "number" && Number.isSafeInteger(detail.checked) && detail.checked >= 0 && typeof detail.skipped === "number" && Number.isSafeInteger(detail.skipped) && detail.skipped >= 0) {
751
- setProgress({ checked: detail.checked, skipped: detail.skipped });
777
+ setProgress({ checked: detail.checked, skipped: detail.skipped, port });
752
778
  }
753
779
  const lifecycle = event.kind === "started" || event.kind === "resumed" ? "running" : event.kind === "created" ? "pending" : event.kind;
754
780
  const label = STATUS_LABEL[lifecycle];
@@ -763,17 +789,21 @@ function PasswordHealthView(props) {
763
789
  if (!inventoryReady) {
764
790
  ownedAddress.current = null;
765
791
  setResult(null);
766
- if (!starting.current) active.current?.abort();
792
+ if (!starting.current || !owns(starting.current.controller)) active.current?.abort();
767
793
  return;
768
794
  }
769
- if (!port || starting.current) return;
795
+ if (!port) return;
796
+ const start = starting.current;
797
+ if (start && owns(start.controller) && start.request === (requestId ?? "") && start.execution === (executionId ?? "")) return;
770
798
  const own = ownedAddress.current;
771
799
  if (own && own.request === requestId && own.execution === executionId && own.items === items && own.retry === retry && active.current && !active.current.signal.aborted) return;
772
800
  const controller = fresh();
801
+ setStatus("Checking status");
773
802
  void recover(requestId, executionId, controller).catch((e) => {
774
803
  if (owns(controller)) {
775
804
  setResult(null);
776
805
  setError(problem(e));
806
+ setStatus((value) => value === "Checking status" ? "Unavailable" : value);
777
807
  }
778
808
  }).finally(() => {
779
809
  if (owns(controller)) setBusy(false);
@@ -790,7 +820,7 @@ function PasswordHealthView(props) {
790
820
  const run = async (resume2) => {
791
821
  if (!port) return;
792
822
  const controller = fresh();
793
- starting.current = true;
823
+ starting.current = { controller, request: "", execution: "" };
794
824
  const request = crypto.randomUUID();
795
825
  const options = { check_breaches: checkBreaches };
796
826
  let actualRequest = request;
@@ -819,7 +849,7 @@ function PasswordHealthView(props) {
819
849
  if (data && typeof data === "object" && "type" in data) {
820
850
  if (data.type === "vault_password_health_scan_reference") accept(data);
821
851
  if (data.type === "vault_password_health_progress") {
822
- setProgress(data);
852
+ setProgress({ ...data, port });
823
853
  setStatus("Running");
824
854
  }
825
855
  }
@@ -861,21 +891,24 @@ function PasswordHealthView(props) {
861
891
  }
862
892
  setResult(null);
863
893
  setError(problem(e));
894
+ setStatus((value) => value === "Checking status" ? "Unavailable" : value);
864
895
  } finally {
865
- starting.current = false;
896
+ if (starting.current?.controller === controller) starting.current = null;
866
897
  if (owns(controller)) {
867
898
  setBusy(false);
868
899
  if (startedItems !== latest.current.items) setRetry((v) => v + 1);
869
900
  }
870
901
  }
871
902
  };
872
- const current = inventoryReady && result?.items === items && result.value.availability === "current" ? result.value.snapshot : null;
873
- const available = result?.value.availability;
903
+ const ownedResult = port && result?.port === port ? result : null;
904
+ const shownProgress = port && progress?.port === port ? progress : null;
905
+ const current = inventoryReady && ownedResult?.items === items && ownedResult.value.availability === "current" ? ownedResult.value.snapshot : null;
906
+ const available = ownedResult?.value.availability;
874
907
  const outcomes = current?.check_breaches ? current.breach_outcomes ?? [] : [];
875
908
  const compromised = outcomes.filter((outcome) => outcome.status === "compromised").length;
876
909
  const notFound = outcomes.filter((outcome) => outcome.status === "not_found").length;
877
910
  const unknown = outcomes.filter((outcome) => outcome.status === "unknown").length;
878
- const resume = result && ["failed", "cancelled"].includes(result.value.execution_status);
911
+ const resume = ownedResult && ["failed", "cancelled"].includes(ownedResult.value.execution_status);
879
912
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex h-full min-h-0 flex-col gap-4 overflow-auto p-4", "aria-label": "Password health", children: [
880
913
  props.header,
881
914
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
@@ -909,9 +942,9 @@ function PasswordHealthView(props) {
909
942
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.InfoHint, { text: "The breach lookup could not establish a result." })
910
943
  ] }), children: unknown })
911
944
  ] }) : null,
912
- !current && progress ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
913
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Checked", children: progress.checked }),
914
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Skipped", children: progress.skipped })
945
+ !current && shownProgress ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Checked", children: shownProgress.checked }),
947
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Skipped", children: shownProgress.skipped })
915
948
  ] }) : null,
916
949
  current ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
917
950
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Checked", children: current.checked_subjects.length }),
@@ -9683,6 +9716,30 @@ var import_account = require("@ai-matrx/api/account");
9683
9716
  var import_architecture = require("@ai-matrx/api/architecture");
9684
9717
  function createHealthPort(api) {
9685
9718
  return {
9719
+ discover: async (cursor, signal) => {
9720
+ const stream = await (0, import_account.discoverPasswordHealthScans)(api, cursor ? { query: { cursor } } : void 0, { signal, totalTimeoutMs: null });
9721
+ let page = null;
9722
+ let ended = false;
9723
+ try {
9724
+ for await (const event of stream) {
9725
+ if (signal.aborted || ended || event.event === "error") throw new Error("Report unavailable");
9726
+ const data = event.data;
9727
+ if (event.event === "completion" && data && typeof data === "object" && "status" in data && data.status !== "success") throw new Error("Report unavailable");
9728
+ if (event.event === "end") {
9729
+ if (!data || typeof data !== "object" || !("reason" in data) || data.reason !== "complete") throw new Error("Report unavailable");
9730
+ ended = true;
9731
+ }
9732
+ if (event.event !== "data") continue;
9733
+ if (!data || typeof data !== "object" || !("type" in data) || data.type !== "vault_password_health_discovery_page") continue;
9734
+ if (page || !("__kind" in data) || data.__kind !== "password_health_discovery_page" || !("references" in data) || !Array.isArray(data.references) || data.references.some((ref) => !ref || typeof ref !== "object" || ref.__kind !== "password_health_discovery_reference" || typeof ref.request_id !== "string" || !ref.request_id || typeof ref.execution_id !== "string" || !ref.execution_id) || !("continuation" in data) || data.continuation !== null && (typeof data.continuation !== "string" || !data.continuation)) throw new Error("Report unavailable");
9735
+ page = data;
9736
+ }
9737
+ if (signal.aborted || !ended || !page) throw new Error("Report unavailable");
9738
+ return page;
9739
+ } finally {
9740
+ stream.cancel();
9741
+ }
9742
+ },
9686
9743
  start: (requestId, signal, options) => (0, import_account.startPasswordHealthScan)(api, { body: options.check_breaches ? { check_breaches: true } : {} }, { requestId, signal, totalTimeoutMs: null }),
9687
9744
  resume: (executionId, requestId, signal) => (0, import_account.resumePasswordHealthScan)(api, { path: { execution_id: executionId } }, { requestId, signal, totalTimeoutMs: null }),
9688
9745
  read: (executionId, signal) => (0, import_account.getPasswordHealthScan)(api, { path: { execution_id: executionId } }, { signal }),