@ai-matrx/vault 0.1.54 → 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,9 +1,12 @@
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
+
3
7
  ## 0.1.54
4
8
 
5
- UNVERIFIED: this release has no Unreleased entry and the package-input comparison could not be made (the caller supplied no source comparison). Whether package inputs changed is unknown; consumer action is unknown.
6
- Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault`.
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.
7
10
 
8
11
  ## 0.1.53
9
12
 
@@ -359,8 +362,6 @@ Changed shipped source files (8):
359
362
  ## 0.1.7
360
363
  ### Unreleased
361
364
 
362
- - 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.
363
-
364
365
  - Host-held lock: `host.lock` (`VaultLock`: `locked`, `subscribe`, `unlock`, optional `lock` and
365
366
  `unlockLabel`). While locked the vault shows only "Vault locked" with Unlock, and every port call
366
367
  (areas included) is refused at call time (`guardPort`, `VaultLockedError`). "Lock" joins the
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)(requestId || executionId ? "Checking status" : "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
  };
@@ -740,6 +744,27 @@ function PasswordHealthView(props) {
740
744
  }
741
745
  writeReference(request, id);
742
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
+ }
743
768
  if (!id || !owns(controller)) return;
744
769
  const first = await read(id, controller);
745
770
  if (!owns(controller) || terminal(first.execution_status)) return;
@@ -749,7 +774,7 @@ function PasswordHealthView(props) {
749
774
  if (!owns(controller)) return;
750
775
  const detail = event.detail;
751
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) {
752
- setProgress({ checked: detail.checked, skipped: detail.skipped });
777
+ setProgress({ checked: detail.checked, skipped: detail.skipped, port });
753
778
  }
754
779
  const lifecycle = event.kind === "started" || event.kind === "resumed" ? "running" : event.kind === "created" ? "pending" : event.kind;
755
780
  const label = STATUS_LABEL[lifecycle];
@@ -764,14 +789,16 @@ function PasswordHealthView(props) {
764
789
  if (!inventoryReady) {
765
790
  ownedAddress.current = null;
766
791
  setResult(null);
767
- if (!starting.current) active.current?.abort();
792
+ if (!starting.current || !owns(starting.current.controller)) active.current?.abort();
768
793
  return;
769
794
  }
770
- 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;
771
798
  const own = ownedAddress.current;
772
799
  if (own && own.request === requestId && own.execution === executionId && own.items === items && own.retry === retry && active.current && !active.current.signal.aborted) return;
773
800
  const controller = fresh();
774
- setStatus(requestId || executionId ? "Checking status" : "No report");
801
+ setStatus("Checking status");
775
802
  void recover(requestId, executionId, controller).catch((e) => {
776
803
  if (owns(controller)) {
777
804
  setResult(null);
@@ -793,7 +820,7 @@ function PasswordHealthView(props) {
793
820
  const run = async (resume2) => {
794
821
  if (!port) return;
795
822
  const controller = fresh();
796
- starting.current = true;
823
+ starting.current = { controller, request: "", execution: "" };
797
824
  const request = crypto.randomUUID();
798
825
  const options = { check_breaches: checkBreaches };
799
826
  let actualRequest = request;
@@ -822,7 +849,7 @@ function PasswordHealthView(props) {
822
849
  if (data && typeof data === "object" && "type" in data) {
823
850
  if (data.type === "vault_password_health_scan_reference") accept(data);
824
851
  if (data.type === "vault_password_health_progress") {
825
- setProgress(data);
852
+ setProgress({ ...data, port });
826
853
  setStatus("Running");
827
854
  }
828
855
  }
@@ -866,20 +893,22 @@ function PasswordHealthView(props) {
866
893
  setError(problem(e));
867
894
  setStatus((value) => value === "Checking status" ? "Unavailable" : value);
868
895
  } finally {
869
- starting.current = false;
896
+ if (starting.current?.controller === controller) starting.current = null;
870
897
  if (owns(controller)) {
871
898
  setBusy(false);
872
899
  if (startedItems !== latest.current.items) setRetry((v) => v + 1);
873
900
  }
874
901
  }
875
902
  };
876
- const current = inventoryReady && result?.items === items && result.value.availability === "current" ? result.value.snapshot : null;
877
- 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;
878
907
  const outcomes = current?.check_breaches ? current.breach_outcomes ?? [] : [];
879
908
  const compromised = outcomes.filter((outcome) => outcome.status === "compromised").length;
880
909
  const notFound = outcomes.filter((outcome) => outcome.status === "not_found").length;
881
910
  const unknown = outcomes.filter((outcome) => outcome.status === "unknown").length;
882
- const resume = result && ["failed", "cancelled"].includes(result.value.execution_status);
911
+ const resume = ownedResult && ["failed", "cancelled"].includes(ownedResult.value.execution_status);
883
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: [
884
913
  props.header,
885
914
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
@@ -913,9 +942,9 @@ function PasswordHealthView(props) {
913
942
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.InfoHint, { text: "The breach lookup could not establish a result." })
914
943
  ] }), children: unknown })
915
944
  ] }) : null,
916
- !current && progress ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
917
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Checked", children: progress.checked }),
918
- /* @__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 })
919
948
  ] }) : null,
920
949
  current ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
921
950
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VaultRow, { label: "Checked", children: current.checked_subjects.length }),
@@ -9687,6 +9716,30 @@ var import_account = require("@ai-matrx/api/account");
9687
9716
  var import_architecture = require("@ai-matrx/api/architecture");
9688
9717
  function createHealthPort(api) {
9689
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
+ },
9690
9743
  start: (requestId, signal, options) => (0, import_account.startPasswordHealthScan)(api, { body: options.check_breaches ? { check_breaches: true } : {} }, { requestId, signal, totalTimeoutMs: null }),
9691
9744
  resume: (executionId, requestId, signal) => (0, import_account.resumePasswordHealthScan)(api, { path: { execution_id: executionId } }, { requestId, signal, totalTimeoutMs: null }),
9692
9745
  read: (executionId, signal) => (0, import_account.getPasswordHealthScan)(api, { path: { execution_id: executionId } }, { signal }),