TS port slice 7: entry modify/archive/links + util generate-password

- core entryMod: modify_entry port with the per-kind allowed-field matrix,
  policy merging, blacklisted-field cleanup and timestamp touching;
  archive/restore; typed links (add/remove/get with Python's normalization,
  dedupe on target/relation/note, self-link and missing-target refusal).
- Parity fixture entry_mods.json: the real Python EntryManager applies a
  10-op modification sequence at a later pinned clock; TS replays the same
  ops from the creation state and matches byte-for-byte.
- CLI: entry modify/archive/unarchive/links/link-add/link-remove,
  entry totp-codes (explicit egress, honors --at and archived state),
  util generate-password (Python parity: index 0 + gen v1 defaults, full
  policy flags — v1/v2 outputs verified against password fixtures).
- capabilities: egress command list updated.
- 148 core parity tests (Node + jsdom) + 22 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-08-14 13:14:25 -04:00
1 parent 774c992153
commit 4f2b8a79bf
11 files changed
+868 -9

No files matched your search

+55 -2
View File
@@ -335,8 +335,12 @@ def gen_index_keys() -> dict:
}
def _build_entries_index() -> dict:
"""Build a real entries index with one entry of each kind, timestamps pinned."""
def _build_entries_index(post_add=None) -> dict:
"""Build a real entries index with one entry of each kind, timestamps pinned.
``post_add(em)`` runs extra EntryManager operations before the index is
read back (used by the modification-parity fixture).
"""
from seedpass.core.backup import BackupManager
from seedpass.core.config_manager import ConfigManager
from seedpass.core.entry_management import EntryManager
@@ -385,6 +389,9 @@ def _build_entries_index() -> dict:
em.add_managed_account("example-managed", mnemonic)
em.add_pgp_key("example-pgp", mnemonic, user_id="[email protected]")
if post_add is not None:
post_add(em)
index = vault.load_index()
entries = {
k: v for k, v in index.items() if k != "_system"
@@ -978,6 +985,51 @@ def gen_nostr_events(snapshot: dict, delta_replay: dict) -> dict:
}
MOD_UNIX = FIXED_UNIX + 100
def gen_entry_mods() -> dict:
"""Apply a modification sequence via the real EntryManager and capture
the result; TS replicates the same ops and must match byte-for-byte."""
from seedpass.core.entry_management import EntryManager
def mods(em: EntryManager) -> None:
# Later timestamp so touched entries visibly differ from creation
EntryManager._now_unix = staticmethod(lambda: MOD_UNIX)
em.modify_entry(
0,
username="alice2",
url="https://example.org",
notes="updated note",
tags=["web", "prod"],
min_digits=3,
special_mode="safe",
)
em.modify_entry(1, period=60, digits=8)
em.modify_entry(5, label="api-token-renamed", value="rotated-value")
em.archive_entry(6)
em.restore_entry(6)
em.archive_entry(3)
em.add_link(0, 1, relation="totp", note="2fa for site")
em.add_link(0, 5, relation="related_to")
em.add_link(5, 0)
em.remove_link(0, 5)
entries = _build_entries_index(post_add=mods)
return {
"description": (
"Entries index after a modification sequence (modify_entry, "
"archive/restore, add_link/remove_link) applied by the real "
"EntryManager at MOD_UNIX. TS replays the same ops from the "
"creation state and must match."
),
"fixed_unix": FIXED_UNIX,
"mod_unix": MOD_UNIX,
"mnemonic_id": PRIMARY,
"entries": entries,
}
def gen_kdf_metadata() -> dict:
return {
"description": (
@@ -1029,6 +1081,7 @@ def main() -> None:
files["nostr_snapshot.json"] = gen_nostr_snapshot(vault_fixture["payload_b64"])
files["portable_backup.json"] = gen_portable_backup(entries_fixture["entries"])
files["entry_secrets.json"] = gen_entry_secrets()
files["entry_mods.json"] = gen_entry_mods()
files["sync_merge.json"] = gen_sync_merge()
files["delta_replay.json"] = gen_delta_replay()
files["nostr_events.json"] = gen_nostr_events(