@tpsdev-ai/flair 0.47.1 → 0.48.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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.47.1",
3
- "commit": "2a5259e318163710b73490fb6b6abc5f25aac967",
4
- "builtAt": "2026-08-22T14:43:00.927Z",
2
+ "version": "0.48.0",
3
+ "commit": "a6cc5ad0f58d9b2549ffd6214028120955c87349",
4
+ "builtAt": "2026-08-22T19:11:54.849Z",
5
5
  "builder": "tsc"
6
6
  }
@@ -614,8 +614,22 @@ export class Memory extends databases.flair.Memory {
614
614
  }
615
615
  }
616
616
  content.durability ||= "standard";
617
- content.createdAt = new Date().toISOString();
618
- content.updatedAt = content.createdAt;
617
+ // ── flair#1336: honor a caller-supplied createdAt (parity with put()) ──
618
+ // put() — the other HTTP-reachable create path — has always preserved the
619
+ // caller's createdAt (`content.createdAt ?? now`), and adk-flair's
620
+ // add_memory forwards MemoryEntry.timestamp through it for historical
621
+ // imports. When #1336 moved client creates onto POST, this line's
622
+ // unconditional re-stamp silently discarded those timestamps (caught by
623
+ // the #1334 list-pagination live test: rows written with backdated
624
+ // timestamps came back stamped "now"). Honoring the caller grants no new
625
+ // capability — PUT already accepted arbitrary createdAt from the same
626
+ // principals. validFrom below keys off createdAt and follows it, exactly
627
+ // as on the put() path; updatedAt stays the true write moment; the
628
+ // ephemeral expiresAt stamp keys off Date.now(), so a backdated create
629
+ // cannot stretch the #1257 exposure window.
630
+ const nowIso = new Date().toISOString();
631
+ content.createdAt = content.createdAt ?? nowIso;
632
+ content.updatedAt = nowIso;
619
633
  content.archived = content.archived ?? false;
620
634
  // ─── Default visibility (durability-keyed) — Layer 1, part A ────────────
621
635
  // post() only ever creates a NEW record — patchRecord/supersede-close/
@@ -658,9 +672,13 @@ export class Memory extends databases.flair.Memory {
658
672
  if (content.visibility === undefined || content.visibility === null) {
659
673
  content.visibility = defaultVisibilityForDurability(content.durability);
660
674
  }
661
- // Validate derivedFrom source IDs exist (best-effort, non-blocking)
675
+ // Validate derivedFrom source IDs exist (best-effort, non-blocking).
676
+ // lastReflected keys off updatedAt (the write moment), NOT createdAt —
677
+ // since #1336 a create may carry a backdated caller createdAt, and the
678
+ // reflection bookkeeping must record when the derivation actually ran.
679
+ // (Pre-#1336 the two were always identical here.)
662
680
  if (Array.isArray(content.derivedFrom) && content.derivedFrom.length > 0) {
663
- const now = content.createdAt;
681
+ const now = content.updatedAt;
664
682
  for (const sourceId of content.derivedFrom) {
665
683
  try {
666
684
  const src = await databases.flair.Memory.get(sourceId);
@@ -61,7 +61,7 @@ export class SemanticSearch extends Resource {
61
61
  // recall-harness (test/bench/recall-harness/run.ts) and `recall-eval.mjs`
62
62
  // before reconsidering this default if the compositeScore formula or
63
63
  // corpus changes.
64
- const { agentId: bodyAgentId, q, queryEmbedding, tag, subject, subjects, limit = 10, includeSuperseded = false, scoring = "raw", minScore = 0, since, asOf, includeTrust = false, abstain = false, explain = false } = data || {};
64
+ const { agentId: bodyAgentId, q, queryEmbedding, tag, subject, subjects, limit = 10, includeSuperseded = false, scoring = "raw", minScore = 0, since, asOf, includeTrust = false, includeMetadata = false, abstain = false, explain = false } = data || {};
65
65
  // Authenticated identity lives on the Harper Resource context (getContext().request).
66
66
  // `this.request` is NOT populated on Harper v5 Resources — prior reads here
67
67
  // silently returned undefined and the defense-in-depth scope check below
@@ -234,7 +234,20 @@ export class SemanticSearch extends Resource {
234
234
  // default projection omits. Widen the select ONLY when the caller opts
235
235
  // in — passing undefined otherwise keeps the default (no `provenance`)
236
236
  // so a non-trust recall response stays byte-identical.
237
- select: includeTrust ? [...DEFAULT_SELECT, "provenance"] : undefined,
237
+ //
238
+ // flair#1332: same idiom for the client-writable `metadata` JSON blob
239
+ // (ADK custom_metadata store-and-return). DEFAULT_SELECT deliberately
240
+ // does NOT grow it (K&S projection ruling — the shared retrieval core
241
+ // serves every consumer, and none of the others should pay result-size
242
+ // for an opaque blob they never read); adk-flair opts in per-request
243
+ // with `includeMetadata: true`. `subject` needs no widening — it is
244
+ // already in DEFAULT_SELECT. Neither flag ⇒ select stays undefined ⇒
245
+ // response bytes unchanged.
246
+ select: (includeTrust || includeMetadata)
247
+ ? [...DEFAULT_SELECT,
248
+ ...(includeTrust ? ["provenance"] : []),
249
+ ...(includeMetadata ? ["metadata"] : [])]
250
+ : undefined,
238
251
  // flair#744 slice 2 + confidence-band refinement: attach the absolute
239
252
  // per-result cosine confidence when the caller opts into abstention OR
240
253
  // the trust block — abstention reads the best of it for its verdict, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.47.1",
3
+ "version": "0.48.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",
@@ -85,6 +85,19 @@ type Memory @table(database: "flair") {
85
85
  # stamped by the ORIGINATING instance itself and preserved (never re-stamped) as the record flows through
86
86
  # sync merges. @indexed for the later sync push-query filter (per-record signature verification and the
87
87
  # classifier org-gate are separate, later slices — not built here).
88
+ metadata: String # JSON blob (flair#1332/#1202): ADK custom_metadata, store-and-return. CLIENT-WRITABLE
89
+ # (unlike provenance above, which is server-stamped) and OPAQUE TO THE SERVER by
90
+ # contract: stored verbatim, returned verbatim, never parsed server-side, never
91
+ # queryable (Harper cannot query into a JSON blob), and NO key inside this blob
92
+ # ever influences any server decision — visibility, read-scope, durability,
93
+ # expiry, federation, ranking, nothing. A blob carrying {"visibility":"shared"}
94
+ # leaves the record's ACTUAL visibility at its default (contract-tested in
95
+ # packages/adk-flair/tests/test_metadata_and_list.py). If a specific key ever
96
+ # needs to be filterable, promote THAT key to its own @indexed scalar column
97
+ # (the #1202-documented add-on pattern; `subject` below is the first example) —
98
+ # never parse this blob. Size/shape caps are enforced at the writing client
99
+ # (adk-flair: 64KB serialized, depth ≤ 16, ≤ 512 keys). Nullable/additive —
100
+ # existing rows read null, unchanged behavior (clean-upgrade-path gate).
88
101
  entities: [String] @indexed # attention-plane vocabulary strings (flair#675). Added in v1 (not v2) per K&S
89
102
  # verdict on FLAIR-ATTENTION-PLANE.md — gives the future attention query
90
103
  # uniform index pushdown across Memory/WorkspaceState/OrgEvent instead of a