@tpsdev-ai/flair 0.46.0 → 0.47.0

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.
@@ -30,6 +30,8 @@ flair agent add my-project
30
30
  flair status
31
31
  ```
32
32
 
33
+ > **`flair: command not found` right after installing?** Your npm global prefix's bin dir isn't on PATH (common with a user prefix like `~/.npm-global`) — run `export PATH="$(npm prefix -g)/bin:$PATH"`, persist that line in your shell profile, and `flair doctor` will print the exact line for your shell any time.
34
+
33
35
  Flair runs as a local server at `http://127.0.0.1:19926` by default. The MCP server connects to it on demand via Ed25519-signed requests; nothing leaves your machine unless you explicitly route to a remote Flair instance.
34
36
 
35
37
  ---
@@ -244,6 +246,8 @@ Which memories are non-private is decided at write time, and the default is not
244
246
 
245
247
  `bootstrap` returns the canonical structured containers — `soul`, `memories`, `predicted`, `teammateFindings`, `events` — plus counts and a `tokenEstimate`. The containers are **always present** (empty `[]`/`{}` when there's nothing), so an empty container is distinguishable from an unsupported one.
246
248
 
249
+ **The token ledger reconciles `tokenEstimate` from the payload alone (flair#1270).** Every token-charged content class carries a counter — `soulTokens`, `memoryTokens`, `trustTokens`, `eventsTokens` — plus a measured `scaffoldTokens` for the fixed JSON frame, and `tokenEstimate ≈ scaffoldTokens + soulTokens + memoryTokens + trustTokens + eventsTokens`. The remaining ≈ gap is the bounded per-item difference between the prose lines the memory counters measure and the heavier structured objects the containers ship. A payload whose estimate an agent can't decompose from the reported figures is a bug, not an accounting convention.
250
+
247
251
  **Empty containers say why they're empty (flair#1182).** When a structured container ships empty, the payload carries a short hint naming the reason and what fills it — `eventsHint`, `teammateFindingsHint`, `predictedHint`. This is present *only* when the container is empty, so a deliberately-empty container is never confused with a silent drop (a connector never has to diff against a previous payload to tell the two apart).
248
252
 
249
253
  **`matchQuality` is null on lifecycle sections — by design (flair#1225).** With `includeTrust: true`, each included memory carries a per-memory trust block, section-tagged, whose `matchQuality` is a `strong`/`moderate`/`breadcrumb` confidence band. On the **lifecycle sections** (`permanent`, `recent`, `predicted`) `matchQuality` is `null`: those are a lifecycle-window *load*, not a retrieval surface, so there is no relevance score to band. This is **correct, not a scoring failure** — an own-recent `null` next to a teammate's band does not mean your own records "scored worse". A retrieval band is only meaningful on the retrieval sections (`relevant`, `teammate`). The entry's `section` field makes this legible, and a `matchQualityNote` on any null entry states the reason inline.
@@ -46,6 +46,48 @@ context, so the wrapped handler scopes to the verified agent exactly as an
46
46
  Ed25519-signed REST call would. Identity always comes from the resolved agent,
47
47
  never from the tool arguments (no forging of agentId / authorId).
48
48
 
49
+ ### Which Agent is my connector? (distinct-by-default — flair#1280)
50
+
51
+ **The connector's Agent is whatever the `Credential(kind:"idp")` mapping says
52
+ — and that is NOT constrained to be your CLI agent.** Distinct identities are
53
+ the ruled default (flair#1280): per-purpose connector identities are the
54
+ product pattern for org/service installs, and same-identity is a deliberate
55
+ opt-in, never something the server infers. The practical consequences:
56
+
57
+ - **Where the mapping is decided.** `flair mcp enable --principal <agent-id>
58
+ --idp-subject <sub>` (the identity-mapping step, backed by
59
+ `provisionIdpIdentityMapping`) writes the mapping. The `--principal` you pass
60
+ is the Agent every `/mcp` call will read and write as. Pass an EXISTING
61
+ agent id to attach the sub to it; the step's output states the resulting
62
+ `sub → Agent` mapping in as many words.
63
+ - **Linking a sub to an existing Agent (the same-identity opt-in).** Re-run
64
+ `flair mcp enable` with the SAME `--idp-provider`/`--idp-subject` and
65
+ `--principal <your-cli-agent-id>`. The existing `(provider, subject)`
66
+ Credential is RE-POINTED to that principal — one Credential row per subject,
67
+ so resolution stays deterministic. The link *replaces* the mapping; it does
68
+ not merge the two agents' memories.
69
+ - **First diagnostic: ask the server who you are.** The `bootstrap` tool's
70
+ response always carries the resolved `agentId` and a `scope` descriptor
71
+ (`scope.agentId` / `scope.isAdmin` / `scope.reads`, flair#1182). "My memory
72
+ is empty over the connector" + a `bootstrap.agentId` you don't recognize =
73
+ the sub resolved to a different (often JIT-provisioned) Agent — link it as
74
+ above.
75
+ - **JIT caveat.** A JIT-provisioned mapping (`FLAIR_MCP_JIT_PROVISION=1`)
76
+ stamps `idpProvider: "mcp-oauth"`. Runtime resolution matches on
77
+ `(kind, idpSubject)` only, but the *linking* upsert matches on
78
+ `(kind, idpProvider, idpSubject)` — so when re-linking a JIT-provisioned
79
+ sub, pass `--idp-provider mcp-oauth` (matching the JIT stamp), or first
80
+ revoke the JIT credential (`status: "revoked"`). Linking under a different
81
+ provider name creates a SECOND active credential for the same subject, and
82
+ which one wins resolution is unspecified.
83
+
84
+ The two-identity contract (a distinct connector agent sees other agents'
85
+ org-non-private rows, never their private rows, 404-never-403 by id; a linked
86
+ connector sees exactly what the linked agent sees) is pinned end-to-end by
87
+ `test/integration/mcp-connector-principal-mapping.test.ts`, which drives the
88
+ real `mcpHandler`/`resolveAgentFromSub` against a real store with two
89
+ registered identities.
90
+
49
91
  ## Enabling (operator checklist)
50
92
 
51
93
  1. **Install the AS plugin** — add `@harperfast/oauth` (already an exact-pinned
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "packageManager": "bun@1.3.10",
5
5
  "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
6
6
  "type": "module",
@@ -47,12 +47,13 @@
47
47
  "scripts": {
48
48
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
49
49
  "prebuild": "npm run clean",
50
- "build": "tsc -p tsconfig.json --noCheck",
51
- "build:cli": "tsc -p tsconfig.cli.json --noCheck",
50
+ "build": "tsc -p tsconfig.json --noCheck && node scripts/write-build-info.mjs",
51
+ "build:cli": "tsc -p tsconfig.cli.json --noCheck && node scripts/write-build-info.mjs",
52
52
  "prepublishOnly": "npm run build && npm run build:cli",
53
53
  "test": "bun test",
54
54
  "test:e2e": "playwright test",
55
- "release": "./scripts/release.sh"
55
+ "release": "./scripts/release.sh",
56
+ "postinstall": "node -e \"try{require('./dist/postinstall.cjs')}catch(e){}\""
56
57
  },
57
58
  "publishConfig": {
58
59
  "access": "public"
@@ -219,4 +219,16 @@ type MemoryCandidate @table(database: "flair") @export {
219
219
  # evidence and would promote tagless (a cross-user leak). Nullable/additive —
220
220
  # pre-#1205b candidates and non-tagged (scope:"recent"/"all") distillations
221
221
  # read null, unchanged behavior (clean-upgrade-path gate).
222
+ visibilityRuling: String # flair#1257 slice 3: the distiller's AFFIRMATIVE visibility ruling for the
223
+ # promoted row — only ever "shared", and only stamped (resources/memory-
224
+ # reflect-lib.ts buildStagedCandidateRow) when the ruling arrived WITH a
225
+ # non-empty team-relevance justification. Promotion (resources/auto-promote-
226
+ # lib.ts decidePromotedVisibility) re-verifies all three conditions
227
+ # (continuity scopeTag + this ruling + visibilityRationale) and defaults
228
+ # "private" otherwise — default-private-unless (Sherlock): a shared promoted
229
+ # row must always trace to a recorded justification, never to a default.
230
+ # Nullable/additive — pre-slice-3 candidates read null ⇒ private, unchanged.
231
+ visibilityRationale: String # flair#1257 slice 3: the team-relevance justification paired with
232
+ # visibilityRuling — the auditable "why shared" recorded on the candidate.
233
+ # Nullable/additive, same clean-upgrade contract as scopeTag above.
222
234
  }