Skip to content

fix(identity): getIdentity/getPrincipal never read the names LIFEOS_CONFIG.toml requires - #1781

Open
anikinsasha wants to merge 1 commit into
danielmiessler:mainfrom
anikinsasha:fix/identity-config-resolution
Open

fix(identity): getIdentity/getPrincipal never read the names LIFEOS_CONFIG.toml requires#1781
anikinsasha wants to merge 1 commit into
danielmiessler:mainfrom
anikinsasha:fix/identity-config-resolution

Conversation

@anikinsasha

Copy link
Copy Markdown

LifeosConfig refuses to load without a non-empty [da].name (LIFEOS/TOOLS/LifeosConfig.ts:160), and LIFEOS_CONFIG.toml labels the field "required — your DA's name (rename to anything)". getIdentity() never reads it. identity.ts already imports loadLifeosConfig — at :31, to resolve paths.userDir, and for nothing else.

So the loader treats the field as mandatory and the resolver behaves as though it does not exist. Same pair for [principal].name/timezone (:152, :155) and getPrincipal().

This is #1459 finished at the root. That PR taught Pulse to defer to LIFEOS_CONFIG.toml [da]; the central identity loader, which 14 files reach through getDAName() and 11 through getIdentity(), was never given the same treatment.

What changes

File Change
hooks/lib/identity.ts getIdentity()/getPrincipal() overlay LIFEOS_CONFIG.toml onto the existing chain; that chain moves verbatim into legacyIdentity()/legacyPrincipal()
test/hooks/Identity.test.ts new — falsifier
everything else unchanged, on purpose — no new exports, no call-site edits, no installer or template changes. Stated here so this is checkable against scope creep.

Three ways it bites today

1. A rename does nothing. The field says "rename to anything". Rename your DA there and every getDAName() call site keeps the old answer, because resolution stops at the core.name frontmatter DAInterview.ts wrote once. There is no path by which editing the required field changes the resolved name.

2. A fresh install resolves the placeholder. The shipped USER/DIGITAL_ASSISTANT/DA_IDENTITY.md carries only provenance/last_updated/convention frontmatter — no core: block. Nothing writes settings.daidentity. So before /interview runs, getIdentity() returns LifeOS / Personal AI / #3B82F6 / empty voice id / zeroed prosody, and getPrincipal() returns User, no matter what LIFEOS_CONFIG.toml says. An install whose identity file was authored by hand rather than by the interview stays there permanently.

3. [da].color is dead config. LifeosConfig.ts:179 normalises it and, before this change, nothing in the payload read it — grep -rn 'da\.color' returns the normaliser and nothing else. DAInterview.ts:445 writes color: "#3B82F6" as a literal into the frontmatter that wins, so a configured colour has never had an effect.

The sharpest symptom is that one instance answers two ways: getConfiguredDAName()-style config readers report the configured name while getDAName() reports the placeholder, and which one a given surface shows depends on nothing more than which helper its author happened to reach for.

Precedence, and the fallback

Config wins for the fields it carries: name, full name, display name, colour, main voice id and prosody for [da]; name, pronunciation, timezone for [principal]. Optional fields fall back to the configured name rather than to the placeholder, so a config with only name set still resolves coherently.

Everything else is untouched. loadLifeosConfig() throwing — no config file yet, or a malformed one — returns the legacy result unchanged, which is the pre-existing behaviour of paiUserDir() directly above. An install with no LIFEOS_CONFIG.toml resolves exactly as it does today.

Falsifier

test/hooks/Identity.test.ts. Five cases; the four asserting config precedence fail against the unpatched file, and the fifth — no config present — passes both ways by design, which is the no-regression claim.

bun test test/hooks/Identity.test.ts

Resolution is exercised in subprocesses because identity.ts derives its identity-file paths at module load, so an in-process env change would not reproduce a real session.

Placed at test/hooks/ per TestingDoctrine's parallel-tree rule rather than co-located, which that doc lists as an anti-pattern. The payload ships no test tree today, so drop the file if tests are not meant to ship publicly — the fix stands without it, and the reproduction is:

printf '[principal]\nname = "P"\ntimezone = "UTC"\n\n[da]\nname = "Testbot"\ncolor = "#123456"\n\n[da.voices.main]\nvoice_id = "v"\n' > /tmp/c.toml
LIFEOS_CONFIG_PATH=/tmp/c.toml bun -e 'import{getIdentity}from"./hooks/lib/identity";console.log(getIdentity())'

LifeosConfig refuses to load without a non-empty [da].name and
[principal].name/timezone, and setup points the principal at those fields.
getIdentity()/getPrincipal() read neither: settings.daidentity -> identity-file
frontmatter -> DEFAULT_IDENTITY. identity.ts already imports loadLifeosConfig,
but only to resolve paths.userDir.

Config now wins for the fields it carries. The existing chain is preserved as
legacyIdentity()/legacyPrincipal(), so an install with no config resolves
exactly as it did before.

test/hooks/Identity.test.ts is the falsifier: four of its five cases fail
against the unpatched file, and the fifth (no config present) passes both
ways by design.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant