From e91d5d0be2355c4be78b8c455ddf87b66d795036 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 13 Aug 2026 21:31:32 -0400 Subject: [PATCH] TS port slice 5: agent-blind provisioning, entry-secret parity, nostr app fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - core entryOps: add* functions for all creatable kinds rebuild the Python EntryManager fixture index byte-for-byte under a pinned clock — field shapes, id allocation, independent TOTP derivation-index allocation, and Python's ISO timestamp format all verified. - CLI 'entry add password/totp/key-value/document/seed/managed-account/ nostr': provision-blind — returns the reference and metadata, never the secret the entry represents; tests assert the derived password / imported secret / child mnemonic appear nowhere in provisioning output. - Parity bug found and fixed by the new entry_secrets fixtures: nostr ENTRY keys derive via the default BIP-85 app 39 path (EntryManager), not app 1237 (sync client identity); the CLI had used 1237. Reveal values for password/TOTP/nostr/seed/managed-account now match Python retrieval. - 129 core parity tests (Node + jsdom) + 16 CLI tests green. Co-Authored-By: Claude Fable 5 --- docs/typescript_port_compatibility_matrix.md | 8 +- js/packages/cli/src/program.ts | 161 +++++++- js/packages/cli/src/secrets.ts | 13 +- js/packages/cli/test/cli.test.ts | 81 ++++ js/packages/core/src/index.ts | 17 + js/packages/core/src/vault/entryOps.ts | 353 ++++++++++++++++++ js/packages/core/test/entryOps.test.ts | 85 +++++ .../test-vectors/fixtures/entry_secrets.json | 15 + .../test-vectors/fixtures/manifest.json | 3 +- js/packages/test-vectors/src/index.ts | 10 + scripts/generate_ts_port_fixtures.py | 48 +++ 11 files changed, 785 insertions(+), 9 deletions(-) create mode 100644 js/packages/core/src/vault/entryOps.ts create mode 100644 js/packages/core/test/entryOps.test.ts create mode 100644 js/packages/test-vectors/fixtures/entry_secrets.json diff --git a/docs/typescript_port_compatibility_matrix.md b/docs/typescript_port_compatibility_matrix.md index 015f4b20..b1cd1d9f 100644 --- a/docs/typescript_port_compatibility_matrix.md +++ b/docs/typescript_port_compatibility_matrix.md @@ -36,14 +36,16 @@ until a later milestone. | Tombstones / delta replay | P0 | green | `delta_replay.json` | Encrypted V3 deltas decrypted and merged in order with `source_tag = sha256(payload)[:16]`, final state matches Python. | | index0 content merge (`_system.index0`) | P2 | partial | — | Empty-skeleton normalization matches Python; merging populated index0 event logs throws loudly instead of silently dropping data. Full port belongs to the atlas milestone. | | Portable backup import/export | P0 | green | `portable_backup.json` | format_version 1, seed-only and plaintext modes; TS imports the Python export, re-exports byte-identically under pinned nonce/timestamp, verifies canonical checksum, rejects tampering/unknown versions. | -| CLI (Milestone 5, agent-blind MVP) | P1 | partial | — | `packages/cli` seedpass-js: capabilities, entry list/get/search (reference-first, `sp://entry/`, secrets replaced by `has_*` flags), `entry reveal` as the only plaintext egress, `use --clipboard/--exec/--stdin-to` sinks (env-var injection, never argv/stdout), vault export/import. Tests assert secrets never appear in default output. Not yet: add/modify entries, profiles, relay sync, leases/tokens. | +| Entry creation (provision) | P0 | green | `entries_index.json` | TS `add*Entry` ops rebuild the Python `EntryManager` fixture index byte-for-byte (pinned clock): field shapes, id allocation, independent TOTP derivation-index allocation, ISO timestamp format. Not ported: index0 event emission, modify/archive ops. | +| Entry secret retrieval | P0 | green | `entry_secrets.json` | Reveal values match Python retrieval for password (v2), TOTP-at-time, nostr entry nsec (BIP-85 app 39 — NOT the sync client's 1237), seed and managed-account mnemonics. | +| CLI (Milestone 5, agent-blind MVP) | P1 | partial | — | `packages/cli` seedpass-js: capabilities, entry list/get/search (reference-first, `sp://entry/`, secrets replaced by `has_*` flags), `entry add password/totp/key-value/document/seed/managed-account/nostr` (provision-blind: returns refs, never the created secret), `entry reveal` as the only plaintext egress, `use --clipboard/--exec/--stdin-to` sinks (env-var injection, never argv/stdout), vault export/import. Tests assert secrets never appear in default or provisioning output. Not yet: modify/archive, profiles, relay sync, leases/tokens. | ## Environment coverage | Environment | Status | |---|---| -| Node 22 (vitest) | green — 125/125 core + 11 CLI | -| jsdom browser-like env | green — 125/125 core | +| Node 22 (vitest) | green — 129/129 core + 16 CLI | +| jsdom browser-like env | green — 129/129 core | | Real Chromium/Firefox (vitest browser mode) | todo — lands with Milestone 6 web app CI | ## Dependency notes (to expand into docs/typescript_dependency_review.md) diff --git a/js/packages/cli/src/program.ts b/js/packages/cli/src/program.ts index ff17dfd6..fe49ea08 100644 --- a/js/packages/cli/src/program.ts +++ b/js/packages/cli/src/program.ts @@ -13,8 +13,20 @@ import { entryMetadata, resolveEntry } from "./refs.js"; import { materializeSecret } from "./secrets.js"; import { clipboardSink, execSink, stdinSink } from "./sinks.js"; import { capabilities } from "./capabilities.js"; -import { openVault, type OpenedVault } from "./vaultFile.js"; -import { importBackup, exportBackup, generateFingerprint } from "@seedpass/core"; +import { openVault, saveVault, type OpenedVault } from "./vaultFile.js"; +import { + importBackup, + exportBackup, + generateFingerprint, + addPasswordEntry, + addTotpDeterministic, + addTotpImported, + addKeyValueEntry, + addDocumentEntry, + addSeedEntry, + addManagedAccountEntry, + addNostrKeyEntry, +} from "@seedpass/core"; import { readFile, writeFile } from "node:fs/promises"; export interface ProgramIo { @@ -99,6 +111,151 @@ export function buildProgram(io: ProgramIo = defaultIo): Command { io.out(JSON.stringify(rows, null, 2)); }); + // Provisioning (plan section 9.3): create entries, get back a reference + // and metadata — never the secret the entry represents. Deterministic + // artifacts are recoverable from the seed; nothing extra to back up. + const add = entry.command("add").description("create entries (returns references, not secrets)"); + + const commonAddOptions = (cmd: T): T => { + cmd + .option("--notes ", "notes", "") + .option("--tags ", "tags") + .option("--archived", "create archived"); + return cmd; + }; + + async function finishAdd(vault: OpenedVault, id: string): Promise { + await saveVault(vault); + const e = vault.index.entries[id]!; + io.out(JSON.stringify(entryMetadata(id, e), null, 2)); + } + + const commonOpts = (o: { notes?: string; tags?: string[]; archived?: boolean }) => ({ + notes: o.notes ?? "", + ...(o.tags !== undefined && { tags: o.tags }), + ...(o.archived !== undefined && { archived: o.archived }), + }); + + commonAddOptions( + add + .command("password