strom-research 1.23.0-beta.2 → 1.23.0-beta.3

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.cs.md CHANGED
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.23.0
9
9
 
10
- *Připravuje se: bety do 1.23.0-beta.2. Oddíl bude úplný s vydáním.*
10
+ *Připravuje se: bety do 1.23.0-beta.3. Oddíl bude úplný s vydáním.*
11
11
 
12
12
  - **Osoba ukázaná zvoleným jménem.** Žena narozená pod jedním jménem, provdaná pod druhým a po rozvodu zase u prvního
13
13
  se dá ukazovat tímto jménem znovu, aniž by se přidávalo podruhé: `strom name primary <osoba> "<jméno>"` (a
@@ -20,8 +20,16 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
20
20
  „nikdy `strom repair`“ a `strom repair` vrátil zpět práci zapsanou klíčem terminálu. `strom repair` teď vrací jen
21
21
  to, co se udělalo mimo strom, práce obou klíčů zůstává; potom klíče sjednotí `strom seal adopt` ve vlastním
22
22
  terminálu – `strom check` říká oba kroky v tomto pořadí. Spuštěný z aplikace agenta balené pro Windows nechá `strom
23
- repair` takový výzkum beze změny a odkáže na terminál. Po commitu gitem, který nezměnil žádný záznam, `strom repair`
24
- řekne, že výzkum znovu zapečetil, už ne „nothing to repair“.
23
+ repair` takový výzkum beze změny a odkáže na terminál; odmítnutý zápis tam odkáže na terminál také, už ne nejdřív
24
+ na `strom repair`. Po commitu gitem, který nezměnil žádný záznam, `strom repair` řekne, že výzkum znovu zapečetil,
25
+ už ne „nothing to repair“.
26
+ - **Pečetní klíč i tam, kde ho hledá starší strom (Windows).** Klíč převzatý z kopie aplikace balené pro Windows se
27
+ teď ukládá i do `%APPDATA%\strom\keys`, kde ho hledá strom 1.22.1 nebo starší v terminálu: po návratu ke starší verzi
28
+ zůstává výzkum na tomto počítači zapečetěný. Klíč, který tam už je, se nikdy nepřepíše.
29
+ - **Vyprávění páru schválené z aplikace Strom.** Dotaz v terminálu teď mluví o vyprávění páru, už ne o vyprávění
30
+ osoby.
31
+ - **`strom repair` jedním jazykem.** Každý jeho řádek („obnoveno“, „není co opravit“ …) je pro člověka v jazyce
32
+ výzkumu; agent je čte anglicky jako dřív.
25
33
 
26
34
  ## 1.22.2
27
35
 
package/CHANGELOG.de.md CHANGED
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
7
7
 
8
8
  ## 1.23.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.23.0-beta.2. Der Abschnitt ist mit dem Release vollständig.*
10
+ *In Vorbereitung: die Betas bis 1.23.0-beta.3. Der Abschnitt ist mit dem Release vollständig.*
11
11
 
12
12
  - **Eine Person unter dem gewählten Namen.** Eine Frau, unter einem Namen geboren, unter einem anderen verheiratet und
13
13
  nach einer Scheidung wieder beim ersten, lässt sich wieder unter diesem Namen zeigen, ohne dass er ein zweites Mal
@@ -25,7 +25,16 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
25
25
  check` nennt beide Schritte in dieser Reihenfolge. Aus der für Windows paketierten Desktop-App eines Agenten
26
26
  gestartet, lässt `strom repair` eine solche Forschung unverändert und verweist auf das Terminal. Nach einem Commit
27
27
  mit git, der keinen Eintrag änderte, sagt `strom repair`, dass es die Forschung neu versiegelt hat, nicht mehr
28
- „nothing to repair“.
28
+ „nothing to repair“. Ein dort abgelehnter Schreibvorgang verweist ebenfalls auf das Terminal, nicht mehr zuerst auf
29
+ `strom repair`.
30
+ - **Der Siegelschlüssel auch dort, wo ein älteres strom sucht (Windows).** Ein aus der Kopie einer für Windows
31
+ paketierten App übernommener Schlüssel liegt jetzt auch in `%APPDATA%\strom\keys`, wo ihn strom 1.22.1 oder älter
32
+ im Terminal sucht: zurück bei einer älteren Version bleibt die Forschung auf diesem Computer versiegelt. Ein Schlüssel,
33
+ der dort schon liegt, wird nie ersetzt.
34
+ - **Die Erzählung eines Paares, aus der Strom-App freigegeben.** Die Frage im Terminal nennt jetzt die Erzählung des
35
+ Paares, nicht mehr die einer Person.
36
+ - **`strom repair` in einer Sprache.** Jede Zeile („wiederhergestellt“, „nichts zu reparieren“ …) steht für eine
37
+ Person in der Sprache der Forschung; ein Agent liest sie wie bisher auf Englisch.
29
38
 
30
39
  ## 1.22.2
31
40
 
package/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ The changes of each version in short. The full notes and the install lines are o
7
7
 
8
8
  ## 1.23.0
9
9
 
10
- *In preparation: the betas up to 1.23.0-beta.2. The section is complete with the release.*
10
+ *In preparation: the betas up to 1.23.0-beta.3. The section is complete with the release.*
11
11
 
12
12
  - **A person shown by the name chosen.** A woman born under one name, married under another and back at the first
13
13
  after a divorce can be shown by that name again without it being added twice: `strom name primary <person>
@@ -20,8 +20,16 @@ The changes of each version in short. The full notes and the install lines are o
20
20
  and never `strom repair` at once, and `strom repair` undid the work written with the terminal's key. `strom repair`
21
21
  now undoes only what was done outside strom, the work of both keys stays; then `strom seal adopt` in a terminal
22
22
  unites the keys — `strom check` says the two steps in this order. Started by an agent's desktop app packaged for
23
- Windows, `strom repair` leaves such a research as it is and points to the terminal. After a commit made with git
24
- that changed no record, `strom repair` says it sealed the research again, no longer "nothing to repair".
23
+ Windows, `strom repair` leaves such a research as it is and points to the terminal; a write refused there points to
24
+ the terminal too, no longer to `strom repair` first. After a commit made with git that changed no record, `strom
25
+ repair` says it sealed the research again, no longer "nothing to repair".
26
+ - **The seal key also where an older strom looks (Windows).** A key taken over from the copy of an app packaged for
27
+ Windows is now kept in `%APPDATA%\strom\keys` too, where a terminal's strom 1.22.1 or older looks for it: back on an
28
+ older version, the research is still sealed on this computer. A key there already is never replaced.
29
+ - **A couple's story approved from the Strom app.** The question in the terminal now names the story of the couple,
30
+ no longer of a person.
31
+ - **`strom repair` in one language.** Every line it writes ("restored", "nothing to repair", …) is in the research's
32
+ language for a person; an agent reads them in English as before.
25
33
 
26
34
  ## 1.22.2
27
35
 
@@ -1956,6 +1956,7 @@
1956
1956
  "ui.link.story.none": "Vyprávění o {person} zatím není. Nic se nestalo.",
1957
1957
  "ui.link.story.final": "Vyprávění o {person} už je schválené.",
1958
1958
  "ui.link.what.story": "schválit vyprávění osoby {person}, jak je (už nebude návrh).",
1959
+ "ui.link.what.story.couple": "schválit vyprávění páru {couple}, jak je (už nebude návrh).",
1959
1960
  "ui.link.story.done": "✓ Vyprávění je schválené. Po novém otevření výzkumu v aplikaci Strom tam bude bez štítku Návrh.",
1960
1961
  "ui.link.sync.none": "{input} není v tomto výzkumu poslání z aplikace Strom. Nic se nestalo.",
1961
1962
  "ui.link.sync.was": "Toto poslání už bylo vráceno.",
@@ -3112,6 +3113,11 @@
3112
3113
  "ui.check.otherkey.afterrepair.hint": "po strom repair sjednotit ve vlastním terminálu: strom seal adopt",
3113
3114
  "ui.repair.resealed": "znovu zapečetěno – commit udělaný mimo strom nezměnil žádný záznam",
3114
3115
  "ui.repair.unite": "dál: sjednotit klíče stromu na tomto počítači ve vlastním terminálu: strom seal adopt",
3116
+ "ui.repair.nothing": "není co opravit – každý záznam odpovídá své pečeti",
3117
+ "ui.repair.restored": "obnoveno {file}",
3118
+ "ui.repair.removed": "odebráno {file}",
3119
+ "ui.repair.from": "zapečetěný stav: {commit} · kopie nahrazených souborů: .strom/quarantine/",
3120
+ "ui.repair.still": "{n, plural, one {zbývá # problém} few {zbývají # problémy} other {zbývá # problémů}} → strom verify",
3115
3121
  "ui.error.seal.repair-in-terminal": "výzkum „{name}“ obsahuje práci zapečetěnou klíčem stromu z terminálu, který strom spuštěný z aplikace nevidí: nic se neopravilo",
3116
3122
  "ui.error.seal.repair-in-terminal.hint": "ve vlastním terminálu: strom repair, pak strom seal adopt – ten vidí oba klíče a vrátí jen to, co se udělalo mimo strom",
3117
3123
  "ui.menu.seal": "Zapečetěno na jiném počítači: zapisovat tu jde až po převzetí (strom seal adopt).",
@@ -1956,6 +1956,7 @@
1956
1956
  "ui.link.story.none": "{person} hat noch keine Erzählung. Es ist nichts geschehen.",
1957
1957
  "ui.link.story.final": "Die Erzählung von {person} ist bereits freigegeben.",
1958
1958
  "ui.link.what.story": "die Erzählung von {person} so freigeben, wie sie ist (dann kein Entwurf mehr).",
1959
+ "ui.link.what.story.couple": "die Erzählung des Paares {couple} so freigeben, wie sie ist (dann kein Entwurf mehr).",
1959
1960
  "ui.link.story.done": "✓ Die Erzählung ist freigegeben. Wird die Forschung in der Strom-App erneut geöffnet, steht sie ohne das Etikett Entwurf da.",
1960
1961
  "ui.link.sync.none": "{input} ist in dieser Forschung keine Sendung aus der Strom-App. Es ist nichts geschehen.",
1961
1962
  "ui.link.sync.was": "Diese Sendung wurde bereits zurückgenommen.",
@@ -3112,6 +3113,11 @@
3112
3113
  "ui.check.otherkey.afterrepair.hint": "nach strom repair vereinen in einem eigenen Terminal: strom seal adopt",
3113
3114
  "ui.repair.resealed": "neu versiegelt – der Commit außerhalb von strom änderte keinen Eintrag",
3114
3115
  "ui.repair.unite": "weiter: die Schlüssel von strom auf diesem Computer vereinen in einem eigenen Terminal: strom seal adopt",
3116
+ "ui.repair.nothing": "nichts zu reparieren – jeder Eintrag stimmt mit seiner Versiegelung überein",
3117
+ "ui.repair.restored": "wiederhergestellt {file}",
3118
+ "ui.repair.removed": "entfernt {file}",
3119
+ "ui.repair.from": "versiegelter Stand: {commit} · Kopien ersetzter Dateien: .strom/quarantine/",
3120
+ "ui.repair.still": "noch {n, plural, one {# Problem} other {# Probleme}} → strom verify",
3115
3121
  "ui.error.seal.repair-in-terminal": "die Forschung „{name}“ enthält Arbeit, versiegelt mit dem Schlüssel des strom aus dem Terminal, den ein aus einer App gestartetes strom nicht sieht: nichts wurde repariert",
3116
3122
  "ui.error.seal.repair-in-terminal.hint": "in einem eigenen Terminal: strom repair, dann strom seal adopt – es sieht beide Schlüssel und macht nur rückgängig, was außerhalb von strom geschah",
3117
3123
  "ui.menu.seal": "Auf einem anderen Computer versiegelt: schreiben hier erst nach der Übernahme (strom seal adopt).",
@@ -442,7 +442,9 @@ export async function approveStory(ctx, run, lang, root, person, who, how = "fin
442
442
  if (st.status === "final")
443
443
  return stop(ctx, t("ui.link.story.final", { person: who }));
444
444
  out(ctx, lines(st.title ? `„${st.title}“` : undefined, "", st.text.replace(/\*\*/g, ""), ""));
445
- if (!(await asks(ctx, lang, root, t("ui.link.what.story", { person: who }))))
445
+ // a couple's story said as the couple's, never as a person's
446
+ const what = person.type === "family" ? t("ui.link.what.story.couple", { couple: who }) : t("ui.link.what.story", { person: who });
447
+ if (!(await asks(ctx, lang, root, what)))
446
448
  return false;
447
449
  if ((await run(["story", "approve", person.id], true)) !== 0)
448
450
  return false;
package/dist/cli/ui.js CHANGED
@@ -1930,6 +1930,7 @@ export const UI = {
1930
1930
  "ui.link.story.none": "{person} has no story yet. Nothing was done.",
1931
1931
  "ui.link.story.final": "The story of {person} is approved already.",
1932
1932
  "ui.link.what.story": "approve the story of {person} as it is (it will no longer be a draft).",
1933
+ "ui.link.what.story.couple": "approve the story of the couple {couple} as it is (it will no longer be a draft).",
1933
1934
  "ui.link.story.done": "✓ The story is approved. Open the research in the Strom app again and it is there without the draft label.",
1934
1935
  "ui.link.sync.none": "{input} is not a sending from the Strom app in this research. Nothing was done.",
1935
1936
  "ui.link.sync.was": "This sending was taken back already.",
@@ -2804,6 +2805,11 @@ export const UI = {
2804
2805
  "ui.check.otherkey.afterrepair.hint": "after strom repair, united in a terminal of its own: strom seal adopt",
2805
2806
  "ui.repair.resealed": "sealed again — the commit made outside strom changed no record",
2806
2807
  "ui.repair.unite": "next: the keys of strom on this computer united in a terminal of its own: strom seal adopt",
2808
+ "ui.repair.nothing": "nothing to repair — every record matches its seal",
2809
+ "ui.repair.restored": "restored {file}",
2810
+ "ui.repair.removed": "removed {file}",
2811
+ "ui.repair.from": "sealed state: {commit} · copies of replaced files: .strom/quarantine/",
2812
+ "ui.repair.still": "still {n, plural, one {# problem} other {# problems}} → strom verify",
2807
2813
  "ui.error.seal.repair-in-terminal": "\"{name}\" holds work sealed with the key of the terminal's strom, which a strom started by an app cannot see: nothing repaired",
2808
2814
  "ui.error.seal.repair-in-terminal.hint": "in a terminal of its own: strom repair, then strom seal adopt — it sees both keys and undoes only what was done outside strom",
2809
2815
  "ui.menu.seal": "Sealed on another computer: writing here only once taken over (strom seal adopt).",
@@ -8,9 +8,8 @@ import { counted, lines, table } from "../cli/format.js";
8
8
  import { check, SHARED_MISSING, sharedMissing } from "../core/check.js";
9
9
  import { guard } from "../core/guard.js";
10
10
  import { unprovenPeople } from "../core/review.js";
11
- import { AFTER_REPAIR_HINT, NOT_DOWNLOADED, OTHER_KEY, SEALED_PATHS, notDownloaded, notDownloadedError, otherKeyError, verifyFast, verifyFull } from "../core/integrity.js";
12
- import { otherKeys, readCommitSeal, verifyCommitSeal } from "../core/seal.js";
13
- import { settingsHidden } from "../core/oldsettings.js";
11
+ import { AFTER_REPAIR_HINT, NOT_DOWNLOADED, OTHER_KEY, REPAIR_IN_TERMINAL_HINT, SEALED_PATHS, lastSealed, notDownloaded, notDownloadedError, otherKeyError, verifyFast, verifyFull } from "../core/integrity.js";
12
+ import { otherKeys } from "../core/seal.js";
14
13
  import { adoptSeal, keysToUnite } from "../cli/seal.js";
15
14
  import { NeedsConsentError, StromError } from "../core/errors.js";
16
15
  import * as git from "../core/git.js";
@@ -157,18 +156,14 @@ function repair(tree) {
157
156
  const key = tree.requireKey();
158
157
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
159
158
  const head = git.headInfo(tree.root);
160
- // the newest commit any key of this computer sealed (strom of an app packaged for Windows and of a terminal each had
161
- // their own): only what was done outside strom undone, the other key's work kept — strom seal adopt unites them after
162
- const keys = [key, ...otherKeys(tree.env, tree.config.id, key).map((o) => o.key)];
163
- const above = [];
164
- const sealed = git.findCommit(tree.root, (t, m) => keys.some((k) => verifyCommitSeal(k, t, m)) || (above.push(m), false));
159
+ // the newest commit any key of this computer sealed: only what was done outside strom undone, the other key's work
160
+ // kept — strom seal adopt unites them after
161
+ const { sealed, inTerminal } = lastSealed(tree, key);
165
162
  if (!sealed)
166
163
  throw new StromError("no sealed commit found — nothing to restore from", { hint: "ask the user; the history may have been rewritten" });
167
- // started by an app packaged for Windows: a commit above it with strom's seal may be the terminal's key out of its
168
- // sight — never undone from here, the terminal's strom sees both keys
169
- if (above.some((m) => readCommitSeal(m) !== undefined) && settingsHidden(tree.env))
164
+ if (inTerminal)
170
165
  throw new StromError(`"${tree.config.name}" holds work sealed with the key of the terminal's strom, which this strom (started by an app packaged for Windows) does not see — nothing repaired`, {
171
- hint: "the user runs in their terminal: strom repair, then strom seal adopt (it sees both keys and undoes only what was done outside strom)",
166
+ hint: REPAIR_IN_TERMINAL_HINT,
172
167
  code: "seal.repair-in-terminal",
173
168
  params: { name: tree.config.name },
174
169
  });
@@ -230,8 +225,8 @@ register({
230
225
  const text = r.restored.length + r.quarantined.length === 0
231
226
  ? r.resealed
232
227
  ? lines(ui(lang, "ui.repair.resealed"), next)
233
- : lines("nothing to repair — every record matches its seal", next)
234
- : lines(...r.restored.map((f) => `restored ${f}`), ...r.quarantined.map((f) => `removed ${f}`), `sealed state: ${r.from.slice(0, 8)} · copies of replaced files: .strom/quarantine/`, after.length ? `still ${counted(after.length, "problem")} → strom verify` : next);
228
+ : lines(ui(lang, "ui.repair.nothing"), next)
229
+ : lines(...r.restored.map((file) => ui(lang, "ui.repair.restored", { file })), ...r.quarantined.map((file) => ui(lang, "ui.repair.removed", { file })), ui(lang, "ui.repair.from", { commit: r.from.slice(0, 8) }), after.length ? ui(lang, "ui.repair.still", { n: after.length }) : next);
235
230
  return { text, data: { ...r, ok: after.length === 0, ...(unite ? { unite: "strom seal adopt" } : {}) }, exitCode: after.length ? 1 : 0 };
236
231
  },
237
232
  }, {
@@ -484,6 +484,19 @@ export function writableUnasked(tree) {
484
484
  return false;
485
485
  }
486
486
  }
487
+ /**
488
+ * The newest commit any key of this computer sealed (strom of an app packaged for Windows and of a terminal each had
489
+ * their own), and whether strom repair is the terminal's: started by such an app, a commit above it with strom's seal may
490
+ * be the terminal's key out of its sight — never undone from here, the terminal's strom sees both keys.
491
+ */
492
+ export function lastSealed(tree, key) {
493
+ const keys = [key, ...otherKeys(tree.env, tree.config.id, key).map((o) => o.key)];
494
+ const above = [];
495
+ const sealed = git.findCommit(tree.root, (t, m) => keys.some((k) => verifyCommitSeal(k, t, m)) || (above.push(m), false));
496
+ return { sealed, inTerminal: !!sealed && above.some((m) => readCommitSeal(m) !== undefined) && settingsHidden(tree.env) };
497
+ }
498
+ /** What strom repair started by an app packaged for Windows says instead (lastSealed inTerminal). */
499
+ export const REPAIR_IN_TERMINAL_HINT = "the user runs in their terminal: strom repair, then strom seal adopt (it sees both keys and undoes only what was done outside strom)";
487
500
  /** Refuse to write on top of data that was changed outside strom. */
488
501
  export function assertIntact(tree) {
489
502
  const { findings } = verifyFast(tree);
@@ -498,8 +511,15 @@ export function assertIntact(tree) {
498
511
  // files only in iCloud: downloaded first — what else is wrong is judged then
499
512
  if (errors.some((f) => f.code === NOT_DOWNLOADED))
500
513
  throw notDownloadedError(notDownloaded(tree.root));
514
+ // with another key of this computer: the one way strom check says (oneWay); started by an app packaged for Windows
515
+ // above the terminal's work, strom repair in the terminal — never first the app's own, which refuses (found 2026-10-30)
516
+ const way = errors.some((f) => f.code === OTHER_KEY)
517
+ ? errors.find((f) => f.code !== OTHER_KEY && f.hint?.endsWith("then strom seal adopt"))?.hint
518
+ : tree.key && errors.some((f) => f.code === "foreign-commit") && lastSealed(tree, tree.key).inTerminal
519
+ ? REPAIR_IN_TERMINAL_HINT
520
+ : undefined;
501
521
  throw new StromError(`data was changed outside strom (${counted(errors.length, "problem")}) — refusing to write`, {
502
- hint: "strom verify (details)\nstrom repair (restore the last sealed state)",
522
+ hint: `strom verify (details)\n${way ?? "strom repair (restore the last sealed state)"}`,
503
523
  details: errors,
504
524
  });
505
525
  }
@@ -12,6 +12,18 @@ import path from "node:path";
12
12
  import { configDir, userHome } from "./paths.js";
13
13
  import { writesPrivately } from "./packaged.js";
14
14
  import { installation } from "./self.js";
15
+ /**
16
+ * %APPDATA%\strom alone, as this process sees it (oldSettingsDirs' first), without looking through the packaged apps:
17
+ * where an older strom looks for its keys.
18
+ */
19
+ export function oldSettingsDir(env, platform = process.platform, inst = installation()) {
20
+ if (inst.kind === "source" && env.STROM_TEST_OLD_SETTINGS !== undefined)
21
+ return env.STROM_TEST_OLD_SETTINGS.split(path.delimiter).filter(Boolean)[0];
22
+ if (env.STROM_CONFIG_DIR || platform !== "win32")
23
+ return undefined;
24
+ const home = env.USERPROFILE || userHome(env);
25
+ return path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "strom");
26
+ }
15
27
  /**
16
28
  * The folders strom kept its settings in before, as this process sees them: %APPDATA%\strom first (in a packaged app
17
29
  * its private copy where there is one, else the real one), then each packaged app's copy, read by its path. None with
@@ -21,10 +33,10 @@ import { installation } from "./self.js";
21
33
  export function oldSettingsDirs(env, platform = process.platform, inst = installation()) {
22
34
  if (inst.kind === "source" && env.STROM_TEST_OLD_SETTINGS !== undefined)
23
35
  return env.STROM_TEST_OLD_SETTINGS.split(path.delimiter).filter(Boolean);
24
- if (env.STROM_CONFIG_DIR || platform !== "win32")
36
+ const roaming = oldSettingsDir(env, platform, inst);
37
+ if (!roaming)
25
38
  return [];
26
39
  const home = env.USERPROFILE || userHome(env);
27
- const roaming = path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "strom");
28
40
  const packages = path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Packages");
29
41
  let apps = [];
30
42
  try {
package/dist/core/seal.js CHANGED
@@ -16,7 +16,7 @@ import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  import { configDir } from "./paths.js";
18
18
  import { canonicalize } from "./json.js";
19
- import { oldKeys, oldSettingsDirs } from "./oldsettings.js";
19
+ import { oldKeys, oldSettingsDir } from "./oldsettings.js";
20
20
  export function sha256(content) {
21
21
  return crypto.createHash("sha256").update(content).digest("hex");
22
22
  }
@@ -51,8 +51,10 @@ export function loadKey(env, treeId) {
51
51
  */
52
52
  export function settleKey(env, treeId, prefer) {
53
53
  const own = readKey(keyFile(env, treeId));
54
- if (own)
54
+ if (own) {
55
+ mirrorKey(env, treeId, own);
55
56
  return own;
57
+ }
56
58
  const old = oldKeys(env, treeId);
57
59
  if (!old.length)
58
60
  return undefined;
@@ -65,8 +67,29 @@ export function settleKey(env, treeId, prefer) {
65
67
  catch {
66
68
  // taken over next time
67
69
  }
70
+ mirrorKey(env, treeId, key);
68
71
  return key;
69
72
  }
73
+ /**
74
+ * The key also where an older strom looks (Windows: %APPDATA%\strom\keys), made if need be — back on an older version,
75
+ * the research is still sealed here. Only added: a key of its own there already stays (found 2026-10-30: a key taken
76
+ * from a packaged app's copy, never in the folder the terminal's older strom reads).
77
+ */
78
+ function mirrorKey(env, treeId, key) {
79
+ const old = oldSettingsDir(env);
80
+ if (!old)
81
+ return;
82
+ const file = path.join(old, "keys", `${treeId}.key`);
83
+ if (fs.existsSync(file))
84
+ return;
85
+ try {
86
+ fs.mkdirSync(path.dirname(file), { recursive: true });
87
+ fs.writeFileSync(file, key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
88
+ }
89
+ catch {
90
+ // a key of its own there already: the older strom keeps it
91
+ }
92
+ }
70
93
  /** The tree's other keys on this computer: those of the old settings folders that are not `key` (strom seal adopt unites them). */
71
94
  export function otherKeys(env, treeId, key) {
72
95
  return oldKeys(env, treeId).filter((o) => !o.key.equals(key));
@@ -80,18 +103,7 @@ export function createKey(env, treeId) {
80
103
  fs.mkdirSync(path.dirname(file), { recursive: true });
81
104
  const key = crypto.randomBytes(32);
82
105
  fs.writeFileSync(file, key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
83
- // also where an older strom looks (Windows: %APPDATA%\strom), made if need be — back on an older version, the research
84
- // is still sealed here
85
- const old = oldSettingsDirs(env)[0];
86
- if (old) {
87
- try {
88
- fs.mkdirSync(path.join(old, "keys"), { recursive: true });
89
- fs.writeFileSync(path.join(old, "keys", `${treeId}.key`), key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
90
- }
91
- catch {
92
- // a key of its own there already: the older strom keeps it
93
- }
94
- }
106
+ mirrorKey(env, treeId, key);
95
107
  return key;
96
108
  }
97
109
  /** Short public fingerprint of a key, stored on each operation. */
package/dist/core/tree.js CHANGED
@@ -29,7 +29,7 @@ export const TREE_FILE = "strom.json";
29
29
  export const SEAL_FOREIGN = "seal.foreign";
30
30
  // between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
31
31
  // at a release the release's own (package.json)
32
- export const VERSION = "1.23.0-beta.2";
32
+ export const VERSION = "1.23.0-beta.3";
33
33
  const GITIGNORE = `# Strom Research — local state and large files are not versioned
34
34
  .strom/
35
35
  /media/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strom-research",
3
- "version": "1.23.0-beta.2",
3
+ "version": "1.23.0-beta.3",
4
4
  "description": "Genealogical research toolkit for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {