HalofyIndex
AI agent governance · MCP/HTTP memory kernel

AI agent 的 memory kernel 需要身份、证据与可删除性

halofyai/halofy 源码级解析:服务端身份、统一 ACL、写入治理、ScopedView 检索驱动、ContextAllocator、FaultHandler、append-only 审计链与签名删除证书。

观察结论

它不是又一个 prompt memory 插件,而是把身份、ACL、写入验证、检索驱动、审计链和合规删除放进同一个内核边界。

993
stars
170k
kernel LOC
191
src files
2,678
tests claimed
01 / why it matters

它把 agent 记忆做成治理内核

源码判断

身份服务端化

namespace、actor、role 从 key 解析,不靠调用方自报。

检索只读化

driver 通过 ScopedView 读取候选,不能写入或绕过 ACL。

删除可证明

合规删除硬删数据,同时留下 tombstone、证书和审计链。

src/auth/index.ts:39 API key 表把 key_hash、role、namespace、expires_at 放在同一权威表里,说明身份不是客户端传参,而是服务端从凭证解析出的上下文。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:73 hashKey 只保存 SHA-256 摘要,明文 key 只在签发响应里出现一次,这让泄漏数据库时无法直接重放 agent 凭证。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:99 isOversightRole 把 owner、admin、auditor 抽成审计/治理角色集合,后续权限检查可以复用同一组语义。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:146 rowAgentCanWrite 把行级写权限压成纯函数,写入面能用同一判断处理 owner/admin/member/agent 的差异。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:224 auditDenied 在拒绝路径上也追加审计事件,系统不是只记录成功调用,而是把越权尝试纳入可追溯轨迹。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:286 issueKey 生成随机 key 后只插入 hash 与元数据,签发环节完成后系统内没有二次读取明文 key 的接口。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

02 / identity

身份与权限不是 HTTP 参数

源码判断

src/auth/index.ts:331 resolveKey 从 hash 查 principal,并在过期、禁用、不可用 principal 时写 denied audit,身份层承担了第一道治理责任。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/syscalls/index.ts:16 syscall 顶部注释列出 auditor 可读不可写的角色语义,说明 HTTP/MCP 入口共享的是同一套 operation gate。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/syscalls/index.ts:124 SyscallDeps 把 audit、rateLimiter、budget、credential、policy、runtimes、queue 都注入进来,内核调用不是裸函数直达存储。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/syscalls/index.ts:250 L4DispatchOptions 特别声明 source token 不能被日志、审计、持久化或回显,这把短期 OAuth 能力和长记忆数据切开。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/syscalls/index.ts:334 planViaDriver 总是用同一个 ScopedView 构造检索计划,driver 没机会自己绕过 ACL 去碰底层 index。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/syscalls/index.ts:380 MemReadResult 用 denied=false 避免把跨 namespace 的存在性泄露给调用者,这是一处明确的 oracle 防护。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/auth/index.ts:331-380source
 331  async resolve(plaintext: string): Promise<Identity | null> {
 332    const hash = hashKey(plaintext);
 333    const res = await this.db.query(
 334      `SELECT ${KEY_COLUMNS}, caller_class FROM api_keys WHERE key_hash = $1`,
 335      [hash],
 336    );
 337    const row = res.rows[0] as ApiKeyRow | undefined;
 338    // Verification-only principals (principal_purpose = 'channel_verification')
 339    // exist solely for in-process deterministic channel probes and must never
 340    // authenticate over the public API.
 341    if (!row || row.status !== "active" ||
 342        (row.principal_purpose !== undefined && row.principal_purpose !== "general")) {
 343      await this.auditDenied(
 344        "unknown",
 345        row?.namespace ?? "unknown",
 346        row
 347          ? `resolve denied: key ${row.id} is unavailable for public authentication`
 348          : "resolve denied: unknown key",
 349      );
 350      return null;
 351    }
 352    // D51: an expired key is indistinguishable from a revoked one at the
 353    // transport (401). It is NOT flipped to status 'revoked' here — resolve is
 354    // on the hot path for every request, and a write on each rejected call is
 355    // both a needless round trip and a lie about who revoked it. The audit row
 356    // names expiry explicitly so the two causes stay separable in the trail.
 357    if (isExpired(row, Date.now())) {
 358      await this.auditDenied(
 359        row.actor,
 360        row.namespace,
 361        `resolve denied: key ${row.id} expired at ${String(row.expires_at)}`,
 362      );
 363      return null;
 364    }
 365    await this.db.query(`UPDATE api_keys SET last_used = $1 WHERE id = $2`, [
 366      new Date().toISOString(),
 367      row.id,
 368    ]);
 369    const entitlements = rowToRecord(row);
 370    const sourcePrincipals = entitlements.sourcePrincipals ?? [];
 371    const sourceGroups = entitlements.sourceGroups ?? [];
 372    const agentAccessLevel = rowAgentLevel(row.role, row.agent_access_level);
 373    return {
 374      keyId: row.id,
 375      namespace: row.namespace,
 376      actor: row.actor,
 377      role: row.role as Role,
 378      ...(agentAccessLevel ? { agentAccessLevel } : {}),
 379      ...(row.role === "agent"
 380        ? { agentCanWrite: rowAgentCanWrite(row.role, row.agent_can_write) === true }
03 / write path

写入路径先治理,再落库

源码判断

src/write/index.ts:1 写入模块的文件头把 extraction、verification、entity resolution、dedupe、insert 排成管线,定位很像数据库事务前的治理编排。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:61 MemoryIndexLike 暴露 insert、vectorSearch、bumpAccess、closeValidity,说明写入路径可关闭旧事实而不是覆盖原行。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:133 SUPERSEDE_MIN_COSINE 给语义冲突设置阈值,避免只靠 exact string dedupe 判断一条事实是否应该 supersede。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:177 governedClassification 把 verbatim、fact、profile 等 hint 映射到允许的分类,写入前先对目的地做语义约束。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:321 write() 首先分类、再提取、再分批验证,LLM 输出只是候选事实,不能直接落库成权威记忆。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:354 每个候选事实独立经过验证、实体解析、目的地检查和 embedding,单条失败不会把整个写入批次粗暴吞掉。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:421 processCandidate 会拒绝低信息事实、剥离实体注解、检查 destination,再进入向量和去重阶段,落库前有多重过滤。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/driver/view.ts:1 ScopedView 的文件头明确它是 driver 世界的唯一构造器,检索插件不能导入存储层另起通道。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/write/index.ts:321-418source
 321  async write(
 322    req: WriteRequest,
 323    policy: WritePolicy,
 324    /** Agent identity for audit attribution (D4); defaults to the actor. */
 325    agentId: string = req.actor,
 326    hooks?: WritePathHooks,
 327  ): Promise<WriteResult> {
 328    const now = new Date().toISOString();
 329    const inputClassification = governedClassification(req.content, req, policy);
 330    if (!req.hints?.verbatim) {
 331      assertDestinationAllowed(inputClassification, "llm-extraction", {
 332        region: policy.dataRegion,
 333      });
 334    }
 335    const result: WriteResult = {
 336      stored: [],
 337      rejected: [],
 338      superseded: [],
 339      deduplicated: 0,
 340    };
 341
 342    // a. Extraction.
 343    await hooks?.onStage?.("extraction");
 344    const candidates = await this.extract(req);
 345
 346    // b (hoisted). One verifier call covers every extracted candidate —
 347    // per-fact calls re-sent the whole transcript per candidate, which made
 348    // verification the write path's dominant model spend on multi-fact
 349    // documents. Runs BEFORE any candidate commits, so an LLM outage here
 350    // propagates with zero progress and the caller's verbatim fallback stays
 351    // safe to re-run (nothing committed to duplicate).
 352    const verdicts = await this.verifyCandidates(req, policy, candidates, now);
 353
 354    for (const [index, cand] of candidates.entries()) {
 355      // M6 (D14): the caller's hard cap on STORED facts (the L4 write-back
 356      // passes its remaining per-fault cache budget). Checked before each
 357      // candidate — extraction fan-out can never land more rows than the cap,
 358      // and the overflow is reported, not silently dropped.
 359      if (req.maxFacts !== undefined && result.stored.length >= req.maxFacts) {
 360        result.rejected.push({ candidate: cand.content, reason: "max-facts-cap" });
 361        continue;
 362      }
 363      try {
 364        // Entity-registry drift between prepare() and the commit transaction
 365        // retries the whole candidate: the transaction rolled back, so a
 366        // fresh preparation inside processCandidate re-reads current state
 367        // and (normally) resolves the colliding mention as an exact match.
 368        // Any other failure - and a conflict that persists past the bound -
 369        // propagates to the handler below unchanged.
 370        for (let entityAttempt = 1; ; entityAttempt++) {
 371          try {
 372            await this.processCandidate(req, policy, agentId, now, cand, verdicts[index] ?? false, result, hooks);
 373            break;
 374          } catch (err) {
 375            if (
 376              err instanceof EntityResolutionConflictError &&
 377              entityAttempt < MAX_ENTITY_RESOLUTION_ATTEMPTS
 378            ) {
 379              continue;
 380            }
 381            throw err;
 382          }
 383        }
 384      } catch (err) {
 385        // An LLM circuit that opens MID-write — after earlier candidates
 386        // were already committed — must never surface as a whole-write
 387        // failure: the caller's verbatim fallback would re-run the ENTIRE
 388        // write, duplicating the committed facts (plus their supersede/
 389        // audit/cost rows) and dropping their ids from the returned result.
 390        // Record the outage per remaining candidate and keep the partial
 391        // WriteResult accurate instead. With ZERO progress the error still
 392        // propagates: the verbatim fallback is safe then (nothing committed
 393        // to duplicate).
 394        const progress =
 395          result.stored.length +
 396            result.superseded.length +
 397            result.rejected.length +
 398            (result.disputed?.length ?? 0) +
 399            result.deduplicated >
 400          0;
 401        if (err instanceof LlmUnavailableError && progress) {
 402          result.rejected.push({ candidate: cand.content, reason: "llm-unavailable" });
 403          await this.audit(
 404            req,
 405            agentId,
 406            now,
 407            "reject",
 408            null,
 409            "error",
 410            "llm-unavailable: circuit opened mid-write; earlier candidates already committed",
 411          );
 412          continue;
 413        }
 414        throw err;
 415      }
 416    }
 417
 418    return result;
src/write/index.ts:421-520source
 421  /** Steps b–f for one extracted candidate (mutates `result` in place). */
 422  private async processCandidate(
 423    req: WriteRequest,
 424    policy: WritePolicy,
 425    agentId: string,
 426    now: string,
 427    cand: Candidate,
 428    /** This candidate's verdict from the hoisted batch verification. */
 429    verdictOk: boolean,
 430    result: WriteResult,
 431    hooks?: WritePathHooks,
 432  ): Promise<void> {
 433    if (req.provenance !== undefined && !req.hints?.verbatim && isLowInformationFact(cand.content)) {
 434      result.rejected.push({ candidate: cand.content, reason: "low-information-fact" });
 435      await this.audit(req, agentId, now, "reject", null, "denied", "low-information-fact");
 436      return;
 437    }
 438
 439    // b. Verifier verdict (computed once for the whole write in
 440    // verifyCandidates) — SKIPPED for verbatim requests: verbatim is the
 441    // no-LLM lane (budget brownout / LLM-outage fallback), so it must not
 442    // call the LLM again. The candidate is the caller's own content stored
 443    // as-is; there is nothing extracted to verify against the transcript.
 444    if (policy.verifierEnabled && !req.hints?.verbatim && !verdictOk) {
 445      result.rejected.push({
 446        candidate: cand.content,
 447        reason: "failed-verification",
 448      });
 449      await this.audit(req, agentId, now, "reject", null, "denied", "failed-verification");
 450      return;
 451    }
 452
 453    // c. Entity resolution — canonical names appended as an annotation;
 454    // the fact text itself stays verbatim.
 455    //
 456    // `[entities: …]` is a RESERVED write-path marker: the read side
 457    // (DbFragmentationReader) parses canonical entities out of a trailing
 458    // annotation. Source-controlled content (a raw doc or verbatim import) that
 459    // legitimately ends in that bracket would otherwise inject FORGED canonical
 460    // entities the write path never resolved. Strip any trailing occurrence off
 461    // the base text first, so the only `[entities: …]` the read side can ever
 462    // see is the one the write path itself appends below after real resolution.
 463    const base = stripEntitiesAnnotation(cand.content);
 464    // A governed localOnly ingestion write (DLP brownout: the document's
 465    // classification denied llm-extraction / embedding-provider) must NOT
 466    // resolve mentions through the entity registry — EntityRegistry.prepare()
 467    // embeds with the external embedder and can call the external LLM
 468    // tiebreak, exactly the egress localOnly exists to prevent. But landing
 469    // with NO entity metadata makes the fact invisible to entity-grouped
 470    // contradiction detection (organization conflict scans report
 471    // entity_metadata_unavailable and skip it), so annotate the deterministic
 472    // proper-noun mentions directly: zero egress, no registry mutation, same
 473    // marker shape the read side parses. Non-ingestion localOnly writes stay
 474    // exactly as-is, mirroring the extract() lane's provenance gate.
 475    const deterministicMentions =
 476      (req.hints?.localOnly || req.hints?.deterministicEntities) &&
 477        req.provenance !== undefined
 478        ? capitalizedWords(base)
 479        : [];
 480    // Entity resolution itself can call the embedding provider. Report the
 481    // boundary before that work begins, not only before the final fact vector,
 482    // so a provider failure is attributed to the real stage.
 483    await hooks?.onStage?.("embedding");
 484    const preparedEntities: PreparedEntityResolution[] = [];
 485    if (this.deps.transact && deterministicMentions.length === 0) {
 486      for (const mention of cand.entities) {
 487        preparedEntities.push(await this.entities.prepare(req.namespace, mention));
 488      }
 489    }
 490    // Production writes resolve entities only after entering deps.transact so
 491    // entity rows, alias changes, erasure fences, the memory row, and its D4
 492    // audit evidence share one commit boundary. The non-transactional lane is
 493    // retained for small structural unit-test implementations.
 494    let content = deterministicMentions.length > 0
 495      ? `${base} [entities: ${deterministicMentions.join(", ")}]`
 496      : this.deps.transact
 497      ? base
 498      : await this.annotateEntities(base, req.namespace, cand.entities, this.entities);
 499
 500    // Embed the fact TEXT (un-annotated base), not the `[entities: …]` marker:
 501    // the annotation is derived read-side metadata, so dedupe / supersedence /
 502    // search similarity must depend only on the fact itself. This also keeps
 503    // the verbatim lane's newly-added entity annotation from perturbing
 504    // supersede matching between two near-identical facts.
 505    const classification = governedClassification(content, req, policy);
 506    if (!req.hints?.localOnly) {
 507      assertDestinationAllowed(classification, "embedding-provider", {
 508        region: policy.dataRegion,
 509      });
 510    }
 511    assertDestinationAllowed(classification, "canonical-l2", {
 512      region: policy.dataRegion,
 513    });
 514    await hooks?.onStage?.("embedding");
 515    const [embedding] = await (
 516      req.hints?.localOnly ? this.localEmbedder : this.deps.embedder
 517    ).embed([base]);
 518    if (!embedding) return; // defensive; embedders return 1:1
 519    const sourceAccess = req.provenance?.sourceAccess ?? {
 520      principals: [],
04 / retrieval

检索驱动只能站在 ScopedView 后面

源码判断

src/driver/view.ts:140 nameMatchers 用 whole-word 正则避免 Ann 命中 planning 这类误召回,图检索路径对实体边界有显式防护。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/driver/view.ts:181 buildScopedView 的 candidates 调用带 validOnly、access、sourceProjection,候选集从创建时就被 ACL 与 source scope 收窄。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/driver/view.ts:220 vectorSearch 同样传入 validOnly、access、sourceIds,向量检索没有被例外化成更宽的读取面。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/driver/view.ts:242 concepts 在 sourceProjection 存在时 fail closed,这让冷层概念检索不会把指定来源之外的信息混入计划。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/allocator/index.ts:7 ContextAllocator 把 policy-directive 排在 lane 0,并从预留 token slice 装入,治理指令不会被普通 recall 挤出上下文。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/driver/view.ts:181-258source
 181export function buildScopedView<TDb>(
 182  deps: ScopedViewDeps<TDb>,
 183  namespace: Namespace,
 184  access?: AccessSubject,
 185  sourceProjection?: RetrievalSourceProjection,
 186): ScopedView {
 187  return {
 188    namespace,
 189
 190    async candidates(limit = DEFAULT_CANDIDATE_LIMIT) {
 191      const rows = await deps.index.listCandidates(namespace, {
 192        validOnly: true,
 193        limit,
 194        access,
 195        sourceIds: sourceProjection?.sourceIds,
 196      });
 197      return rows.map((r) => ({
 198        ref: r.id,
 199        content: retrievalContent(r, sourceProjection),
 200      }));
 201    },
 202
 203    ...(deps.index.listLexicalCandidates
 204      ? {
 205          async lexicalCandidates(query: string, limit: number) {
 206            const rows = await deps.index.listLexicalCandidates!(namespace, query, {
 207              validOnly: true,
 208              limit,
 209              access,
 210              sourceIds: sourceProjection?.sourceIds,
 211            });
 212            return rows.map((r) => ({
 213              ref: r.id,
 214              content: retrievalContent(r, sourceProjection),
 215            }));
 216          },
 217        }
 218      : {}),
 219
 220    async vectorSearch(embedding, k) {
 221      const hits = await deps.index.vectorSearch(
 222        namespace,
 223        embedding,
 224        k,
 225        true,
 226        false,
 227        false,
 228        access,
 229        sourceProjection?.sourceIds,
 230      );
 231      return hits.map((h) => ({
 232        ref: h.obj.id,
 233        content: retrievalContent(h.obj, sourceProjection),
 234        score: h.similarity,
 235      }));
 236    },
 237
 238    async embed(texts) {
 239      return deps.embedder.embed(texts);
 240    },
 241
 242    async concepts(query, k) {
 243      // L3 concepts do not retain per-source provenance. A source-scoped view
 244      // therefore fails closed instead of allowing a future driver to widen
 245      // the result set through the cold tier.
 246      if (sourceProjection?.sourceIds !== undefined) return [];
 247      return deps.cold.searchConcepts(deps.db, deps.embedder, namespace, query, k);
 248    },
 249
 250    async entityFacts(mention, hops) {
 251      const needle = mention.trim().toLowerCase();
 252      if (needle.length === 0) return [];
 253
 254      const entities = await deps.entities.listEntities(namespace);
 255      const seeds = entities.filter((e) =>
 256        lowerNames(e).some((n) => n === needle),
 257      );
 258      if (seeds.length === 0) return [];
Identity
Policy Gate
ScopedView
Driver Plan
validOnly/access
sourceProjection
refs only
audit detail
05 / allocator

ContextAllocator 返回 blocks,不拼 prompt

源码判断

src/allocator/index.ts:125 tryAdd 用 ref 去重并按 tokenBudget 跳过超额 block,工作集是可解释的 blocks 而不是拼接 prompt。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/allocator/index.ts:181 legacy pinned concepts 仍是只读兼容路径,策略顺序保留但不会把旧 pin 自动升级成新 governed pin。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/allocator/index.ts:232 predicted-relevant lane 只有 taskHint 存在时才启动,系统不会无条件把相似记忆塞进每一次调用。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/allocator/index.ts:285 assemble audit detail 写入 blocks、tokens、budget、rejectedPins 和 policy digest,召回上下文也有审计证据。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/fault/index.ts:49 L2_SCORE_FLOOR 和 L3_SCORE_FLOOR 把故障补召回变成阈值判定,而不是只要有候选就硬塞回上下文。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/allocator/index.ts:110-177source
 110  async assemble(args: AssembleArgs): Promise<WorkingSet> {
 111    const t0 = performance.now();
 112    const { namespace, policy, taskHint } = args;
 113    // Defensive: some callers (syscall layer) route audit attribution
 114    // themselves and may omit actor/agentId at runtime.
 115    const actor = args.actor ?? "unknown";
 116    const agentId = args.agentId ?? "unknown";
 117    const budget = policy.tokenBudget;
 118
 119    let totalTokens = 0;
 120    let rejectedPins = 0;
 121    let brownoutDetail = "";
 122    const selected: ContextBlock[] = [];
 123    const usedRefs = new Set<string>();
 124
 125    const tryAdd = (block: ContextBlock): void => {
 126      if (usedRefs.has(block.ref)) return;
 127      if (totalTokens + block.tokenEstimate > budget) return; // never split
 128      selected.push(block);
 129      usedRefs.add(block.ref);
 130      totalTokens += block.tokenEstimate;
 131    };
 132
 133    // -- lane 0: governed policy directives ---------------------------------
 134    // Guardrails are packed first out of a reserved slice of the budget, so
 135    // no amount of recall can push a policy out of the working set. The
 136    // reserve is a CEILING, not an allotment: unused policy tokens stay
 137    // available to the ordinary lanes below.
 138    let policySet: PolicyDirectiveSet | null = null;
 139    if (this.deps.policyDirectives) {
 140      try {
 141        const loaded = await this.deps.policyDirectives.load(
 142          namespace,
 143          policyTokenReserve(budget),
 144        );
 145        if (loaded.documents.length > 0) {
 146          policySet = loaded;
 147          for (const block of loaded.blocks) tryAdd(block);
 148        }
 149      } catch {
 150        // Brownout (kernel rule 5): an unreadable guardrail lane degrades the
 151        // working set, it never fails the agent's call. The audit detail below
 152        // records that policy was unavailable for this assembly.
 153        brownoutDetail += " brownout=policy_unavailable";
 154      }
 155    }
 156
 157    // -- lane 1a: governed stable pins (durable creation order) --------------
 158    if (this.deps.pins) {
 159      try {
 160        const governed = await this.deps.pins.resolve(namespace, args.access);
 161        rejectedPins += governed.rejected;
 162        for (let i = 0; i < governed.pins.length; i++) {
 163          const pin = governed.pins[i];
 164          tryAdd({
 165            kind: pin.kind === "memory" ? "pinned-memory" : "pinned-concept",
 166            ref: pin.ref,
 167            content: pin.content,
 168            tokenEstimate: estimateTokens(pin.content),
 169            orderKey: `0-pin-0-governed-${String(i).padStart(6, "0")}-${pin.pinId}`,
 170          });
 171        }
 172      } catch {
 173        // Brownout: pin metadata or its backing store may be unavailable. The
 174        // compatibility and ordinary lanes still provide a working set.
 175        rejectedPins++;
 176      }
 177    }
src/allocator/index.ts:285-356source
 285    const assembledAt = new Date().toISOString();
 286    const event = {
 287      ts: assembledAt,
 288      actor,
 289      agentId,
 290      op: "assemble",
 291      memoryId: null,
 292      namespace,
 293      purpose: taskHint ?? null,
 294      result: "ok",
 295      detail: `blocks=${selected.length} tokens=${totalTokens} budget=${budget} rejectedPins=${rejectedPins}${policyDetail}${brownoutDetail}`,
 296    } as const;
 297    // D36: with the kernel's transact/usage wiring the audit row and its
 298    // companion usage row commit in one transaction; module-level tests keep
 299    // the standalone append.
 300    const writeAudit = async (audit: AuditPort, tx?: unknown): Promise<void> => {
 301      const auditSeq = await audit.append(event);
 302      if (this.deps.usage && tx !== undefined && typeof auditSeq === "number" && args.usage) {
 303        await this.deps.usage(tx, {
 304          auditSeq,
 305          traceId: args.usage.traceId,
 306          ts: assembledAt,
 307          namespace,
 308          actor,
 309          principal: args.usage.principal,
 310          callerClass: args.usage.callerClass,
 311          op: "assemble",
 312          tool: args.usage.tool,
 313          result: "ok",
 314          latencyMs: performance.now() - t0,
 315          tokens: totalTokens,
 316          returned: selected.length,
 317          driver: null,
 318          sourceTier: null,
 319          sourceIds: null,
 320          topScore: null,
 321        });
 322      }
 323    };
 324    if (this.deps.transact) {
 325      await this.deps.transact(writeAudit);
 326    } else {
 327      await writeAudit(this.deps.audit);
 328    }
 329
 330    if (deliveredPolicy && this.deps.policyDirectives?.onDelivered) {
 331      try {
 332        await this.deps.policyDirectives.onDelivered(deliveredPolicy, args);
 333      } catch {
 334        // The ledger is evidence, not a gate: a failed compliance write never
 335        // costs the agent its working set.
 336      }
 337    }
 338
 339    return {
 340      namespace,
 341      blocks: selected,
 342      totalTokens,
 343      budget,
 344      assembledAt,
 345      ...(deliveredPolicy
 346        ? {
 347            policy: {
 348              digest: deliveredPolicy.digest,
 349              documents: deliveredPolicy.documents,
 350              tokens: deliveredPolicy.totalTokens,
 351              required: deliveredPolicy.required,
 352              truncated: deliveredPolicy.truncated,
 353            },
 354          }
 355        : {}),
 356    };
06 / fault

FaultHandler 的价值是可降级而不是更聪明

源码判断

src/fault/index.ts:182 FaultHandler 优先走 driver-planned L2,但随后仍计算绝对分数,driver 的归一化分数不能单独决定是否命中。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/fault/index.ts:217 embedding 出错时 brownout 到 BM25,系统记录 degradedReason,但不把一次向量服务失败变成 agent 调用失败。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/fault/index.ts:265 L2 不够强时才进入 L3 cold concept search,冷层被设计成有门槛的补救路径而不是默认主路。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/fault/index.ts:317 故障路径会 bumpAccess 或 bumpFault 并同事务 append audit,召回压力会回流成可观测信号。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/fault/index.ts:160-249source
 160  async fault(args: FaultArgs): Promise<FaultResult> {
 161    const t0 = performance.now();
 162    const { namespace, query } = args;
 163    // Defensive: some callers (syscall layer) route audit attribution
 164    // themselves and may omit actor/agentId at runtime.
 165    const actor = args.actor ?? "unknown";
 166    const agentId = args.agentId ?? "unknown";
 167    const k = args.k ?? DEFAULT_K;
 168    const tokenBudget = args.tokenBudget ?? DEFAULT_TOKEN_BUDGET;
 169
 170    // -- tier L2 --------------------------------------------------------------
 171    // M2: when the kernel wired a driver-routed planner, the L2 candidates
 172    // come from the policy-selected retriever cartridge; otherwise the
 173    // legacy hybrid search runs. Either way the ACCEPTANCE decision uses the
 174    // absolute hybrid score against L2_SCORE_FLOOR (see FaultHandlerDeps).
 175    let l2Refs: ScoredRef[];
 176    let acceptL2: boolean;
 177    let bumpTargetId: string | null;
 178    let driverDetail = "";
 179    let driverName: string | null = null;
 180    let degradedReason: FaultResult["degradedReason"];
 181
 182    try {
 183      if (this.deps.l2Planner) {
 184        const outcome = await this.deps.l2Planner(query, k, tokenBudget, args.access);
 185        driverDetail =
 186          ` driver=${outcome.driver}` +
 187          (outcome.fallback ? ` driver_fallback=${outcome.fallback}` : "");
 188        driverName = outcome.driver;
 189        l2Refs = outcome.refs;
 190        const abs = await this.absoluteScores(namespace, query, outcome.refs, args.access);
 191        acceptL2 = abs.some((s) => s >= L2_SCORE_FLOOR);
 192        bumpTargetId = outcome.refs[0]?.ref ?? null;
 193      } else {
 194        const l2Hits: HybridHit[] = await hybridSearchL2({
 195          index: this.deps.index,
 196          embedder: this.deps.embedder,
 197          namespace,
 198          query,
 199          k: Math.max(k * 3, 12),
 200          access: args.access,
 201        });
 202        const bestL2 = l2Hits.length > 0 ? l2Hits[0] : null;
 203        acceptL2 = bestL2 !== null && bestL2.score >= L2_SCORE_FLOOR;
 204        bumpTargetId = bestL2?.row.id ?? null;
 205        l2Refs = l2Hits
 206          .filter((h) => h.score > 0)
 207          .slice(0, k)
 208          .map((h) => ({
 209            id: h.row.id,
 210            tier: "L2" as const,
 211            ref: h.row.id,
 212            score: h.score,
 213            content: h.row.content,
 214            tokenEstimate: estimateTokens(h.row.content),
 215          }));
 216      }
 217    } catch {
 218      // Immediate brownout, including failures before the circuit breaker has
 219      // opened: retain the governed lexical lane and expose the degradation.
 220      const rows = await this.deps.index.listCandidates(namespace, {
 221        validOnly: true,
 222        limit: 200,
 223        access: args.access,
 224      });
 225      const scores = bm25Scores(
 226        query,
 227        rows.map((row) => ({ ref: row.id, content: row.content })),
 228      );
 229      l2Refs = rows
 230        .map((row) => ({
 231          id: row.id,
 232          tier: "L2" as const,
 233          ref: row.id,
 234          // In a lexical-only brownout the BM25 lane owns the full confidence
 235          // scale; retaining the hybrid 0.4 weight would make its maximum too
 236          // small to clear the calibrated acceptance floor reliably.
 237          score: saturate(scores.get(row.id) ?? 0),
 238          content: row.content,
 239          tokenEstimate: estimateTokens(row.content),
 240        }))
 241        .filter((ref) => ref.score > 0)
 242        .sort((a, b) => b.score - a.score || a.ref.localeCompare(b.ref))
 243        .slice(0, k);
 244      acceptL2 = (l2Refs[0]?.score ?? 0) >= L2_SCORE_FLOOR;
 245      bumpTargetId = l2Refs[0]?.ref ?? null;
 246      driverDetail = " driver=bm25-degraded brownout=embedding_unavailable";
 247      driverName = "bm25-degraded";
 248      degradedReason = "embedding_unavailable";
 249    }
src/fault/index.ts:303-358source
 303    const event = {
 304      ts: new Date().toISOString(),
 305      actor,
 306      agentId,
 307      op: "fault",
 308      memoryId: injected.length > 0 ? injected[0].id : null,
 309      namespace,
 310      purpose: args.auditPurpose ?? query,
 311      result,
 312      detail:
 313        `source=${source ?? "none"} injected=${injected.length} tokens=${injectedTokens} latencyMs=${latencyMs}${driverDetail}` +
 314        (degradedReason ? ` brownout=${degradedReason}` : ""),
 315    } as const;
 316
 317    const mutateAndAudit = async (index: L2IndexPort, audit: AuditPort, tx?: TDb): Promise<void> => {
 318      if (accessIds.length > 0) await index.bumpAccess(accessIds);
 319      if (faultId !== null) await index.bumpFault(faultId);
 320      const auditSeq = await audit.append(event);
 321      // D36: the companion usage row rides the audit transaction (the hook
 322      // wraps its writes in a SAVEPOINT brownout). The audit detail string
 323      // above stays byte-identical.
 324      if (this.deps.usage && tx !== undefined && typeof auditSeq === "number" && args.usage) {
 325        await this.deps.usage(tx, {
 326          auditSeq,
 327          traceId: args.usage.traceId,
 328          ts: event.ts,
 329          namespace,
 330          actor,
 331          principal: args.usage.principal,
 332          callerClass: args.usage.callerClass,
 333          op: "fault",
 334          tool: args.usage.tool,
 335          result,
 336          latencyMs,
 337          tokens: injectedTokens,
 338          returned: injected.length,
 339          driver: driverName,
 340          sourceTier: source === "L2" ? "warm" : source === "L3" ? "cold" : null,
 341          sourceIds: null,
 342          topScore: injected[0]?.score ?? null,
 343        });
 344      }
 345    };
 346    if (this.deps.transact) {
 347      await this.deps.transact(mutateAndAudit);
 348    } else {
 349      await mutateAndAudit(this.deps.index, this.deps.audit);
 350    }
 351
 352    return {
 353      hit: result === "ok",
 354      source,
 355      injected,
 356      latencyMs,
 357      ...(degradedReason ? { degradedReason } : {}),
 358    };
07 / audit

审计链是 append-only 的系统记录

源码判断

src/audit/index.ts:4 AuditLog 的模块注释直接声明每个 syscall 都写行,包括 denied/miss/error,这决定了治理视角下的证据完整性。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/audit/index.ts:92 auditDigest 把 prevHash、ts、actor、op、namespace、result、detail 纳入 canonical JSON,再用 SHA-256 串链。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/audit/index.ts:109 appendAuditEventTx 先 FOR UPDATE 锁 head_hash,再插入事件与新 hash,链头推进和事件提交在一个事务里完成。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/audit/index.ts:180 verifyChain 重算每条 hashed audit row 并检查 audit_chain_state,能发现篡改、删除和链头不一致。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/audit/index.ts:225 search 的注释强调 namespace IN 精确枚举,不用 LIKE 通配,审计导出和控制台筛选都沿用同一 ACL 边界。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/audit/index.ts:92-144source
  92/** Low-level append primitive used by transactional and recovery paths. */
  93function auditDigest(event: AuditEvent, prevHash: string | null): string {
  94  const canonical = JSON.stringify({
  95    prevHash,
  96    ts: event.ts,
  97    actor: event.actor,
  98    agentId: event.agentId,
  99    op: event.op,
 100    memoryId: event.memoryId,
 101    namespace: event.namespace,
 102    purpose: event.purpose,
 103    result: event.result,
 104    detail: event.detail ?? null,
 105  });
 106  return createHash("sha256").update(canonical).digest("hex");
 107}
 108
 109async function appendAuditEventTx(db: Db, event: AuditEvent): Promise<number> {
 110  const head = await db.query(
 111    `SELECT head_hash FROM audit_chain_state WHERE singleton = TRUE FOR UPDATE`,
 112  );
 113  const prevHash = head.rows[0]?.head_hash == null ? null : String(head.rows[0].head_hash);
 114  const eventHash = auditDigest(event, prevHash);
 115  const inserted = await db.query(
 116    `INSERT INTO audit_log
 117       (ts, actor, agent_id, op, memory_id, namespace, purpose, result, detail, prev_hash, event_hash)
 118     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
 119     RETURNING seq`,
 120    [
 121      event.ts,
 122      event.actor,
 123      event.agentId,
 124      event.op,
 125      event.memoryId,
 126      event.namespace,
 127      event.purpose,
 128      event.result,
 129      event.detail ?? null,
 130      prevHash,
 131      eventHash,
 132    ],
 133  );
 134  await db.query(
 135    `UPDATE audit_chain_state SET head_hash = $1, updated_at = $2 WHERE singleton = TRUE`,
 136    [eventHash, event.ts],
 137  );
 138  // seq is BIGINT: pg binds it as text, PGlite as number — coerce either way.
 139  return Number((inserted.rows[0] as { seq: unknown }).seq);
 140}
 141
 142/** Standalone append: the chain-head lock and event commit atomically. */
 143export async function appendAuditEvent(db: Db, event: AuditEvent): Promise<void> {
 144  await db.transaction((tx) => appendAuditEventTx(tx, event));
src/audit/index.ts:180-209source
 180  async verifyChain(): Promise<{ ok: boolean; checked: number; brokenSeq?: number }> {
 181    const res = await this.db.query(
 182      `SELECT seq, ts, actor, agent_id, op, memory_id, namespace, purpose, result,
 183              detail, prev_hash, event_hash
 184         FROM audit_log WHERE event_hash IS NOT NULL ORDER BY seq`,
 185    );
 186    let expectedPrev: string | null = null;
 187    let checked = 0;
 188    for (const raw of res.rows) {
 189      const row = raw as Record<string, unknown>;
 190      checked += 1;
 191      const event = rowToAuditEvent(row);
 192      const storedPrev = row.prev_hash == null ? null : String(row.prev_hash);
 193      const storedHash = String(row.event_hash);
 194      const expectedHash = auditDigest(event, expectedPrev);
 195      if (storedPrev !== expectedPrev || storedHash !== expectedHash) {
 196        return { ok: false, checked, brokenSeq: Number(row.seq) };
 197      }
 198      expectedPrev = storedHash;
 199    }
 200    const state = await this.db.query(
 201      `SELECT head_hash FROM audit_chain_state WHERE singleton = TRUE`,
 202    );
 203    const storedHead = state.rows[0]?.head_hash == null
 204      ? null
 205      : String(state.rows[0].head_hash);
 206    if (storedHead !== expectedPrev) {
 207      return { ok: false, checked };
 208    }
 209    return { ok: true, checked };
08 / erasure

合规删除是唯一硬删除例外

源码判断

src/erasure/index.ts:4 forget 是 D7 不删除规则的唯一受治理例外:它硬删 memory_objects,但保留 append-only audit,并返回证书。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/index.ts:117 isInSubtree 复用 namespaceAncestors 判断 downward containment,擦除只能在 caller 自己的 subtree 内发生。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/index.ts:201 resolveTargets 对 memory、namespace、actor、entity 都先解析具体行,再按 caller subtree 过滤,避免跨租户删除。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/index.ts:306 resolveEntityRegistry 只读取 caller subtree 内的 canonical name 和 aliases,避免 sibling alias graph 扩大删除范围。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/index.ts:374 resolveUsageTargets 会清 usage_events 和 query_traces 的派生记录,并用精确 IN 绑定而非全表事务扫描。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/certificate.ts:24 stableStringify 对对象 key 递归排序,删除证书签名的 payload 因此能在 signer/verifier 两端得到同样字节。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/certificate.ts:75 verifyCertificate 要求 out-of-band trusted public key 与证书内 publicKey 一致,inline key 不是自签信任锚。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

src/erasure/index.ts:1-28source
   1/**
   2 * Module B (M4 / D3) — mem.forget erasure engine.
   3 *
   4 * `forget(deps, target, identity)` is the ONE governed exception to D7's "never
   5 * delete": a compliance erasure. It resolves the target to a concrete id set
   6 * INSIDE the caller's subtree, then in ONE database transaction:
   7 *   1. nulls any dangling `supersedes` links from surviving rows into the
   8 *      to-be-deleted set (the FK `memory_objects.supersedes -> memory_objects.id`
   9 *      would otherwise reject the DELETE);
  10 *   2. HARD-DELETEs the matching rows (their inline embeddings go with them);
  11 *   3. inserts one tombstone per erased id;
  12 *   4. builds + ed25519-signs a DeletionCertificate (public key inline) and
  13 *      inserts it.
  14 * E6: derived usage rows (usage_events, query_traces for actor/namespace
  15 * targets) are scrubbed in the same transaction — no tombstones (they are not
  16 * memory objects), but the certificate enumerates their ids.
  17 * It NEVER touches `audit_log` — the audit trail is append-only (D4); the
  18 * syscall layer appends the `forget` audit row.
  19 *
  20 * Subtree scoping (isolation = the existing ancestor-prefix ACL, downward):
  21 *   - the caller may only erase within its OWN subtree — a target whose
  22 *     namespace is an ANCESTOR or a SIBLING of the caller's namespace resolves
  23 *     to zero rows;
  24 *   - resolution runs DOWNWARD: a namespace target matches that namespace and
  25 *     its descendants (again constrained to the caller's subtree).
  26 *
  27 * `deps` is a STRUCTURAL slice `{ db, audit }` — no kernel import.
  28 */
src/erasure/certificate.ts:24-75source
  24 * Deterministic JSON of an arbitrary JSON value with object keys sorted at
  25 * every depth. Arrays keep their order (erasedIds ordering is meaningful and
  26 * fixed by the resolver); objects (including the nested ForgetTarget) sort.
  27 */
  28function stableStringify(value: unknown): string {
  29  if (value === null || typeof value !== "object") {
  30    return JSON.stringify(value);
  31  }
  32  if (Array.isArray(value)) {
  33    return `[${value.map((v) => stableStringify(v)).join(",")}]`;
  34  }
  35  const obj = value as Record<string, unknown>;
  36  const keys = Object.keys(obj).sort();
  37  const parts = keys.map(
  38    (k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`,
  39  );
  40  return `{${parts.join(",")}}`;
  41}
  42
  43/**
  44 * Canonical, signature-independent JSON string of a certificate. Given a full
  45 * certificate or a payload, the `signature` field is always excluded, so the
  46 * signer and the verifier hash exactly the same bytes.
  47 */
  48export function canonicalPayload(
  49  cert: CertificatePayload | DeletionCertificate,
  50): string {
  51  const { signature: _signature, ...payload } = cert as DeletionCertificate;
  52  return stableStringify(payload);
  53}
  54
  55/**
  56 * Sign a certificate payload with the kernel's ed25519 key; returns the base64
  57 * signature over the canonical payload bytes (UTF-8).
  58 */
  59export function signCertificate(
  60  payload: CertificatePayload,
  61  key: SigningKey,
  62): string {
  63  const bytes = Buffer.from(canonicalPayload(payload), "utf8");
  64  return key.sign(bytes).toString("base64");
  65}
  66
  67/**
  68 * Verify a certificate against an out-of-band trusted public key. The inline
  69 * key is signed descriptive metadata and must identify that same trusted key;
  70 * it is never itself a trust anchor. Rebuilds the canonical payload (dropping
  71 * `signature`) and checks the ed25519 signature.
  72 * Returns false — never throws — for a key mismatch, tampered field, bad
  73 * signature, malformed key, or non-ed25519 algorithm.
  74 */
  75export function verifyCertificate(
09 / tests

测试覆盖的是治理不变量

源码判断

test/allocator-fault.test.ts:227 测试直接覆盖 assemble 的 priority order、token budget、去重和 audit detail,证明上下文装配不是 README 声明。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/allocator-fault.test.ts:459 embedding 失败的测试期待 brownout=embedding_unavailable,并确认 BM25 仍可命中,这是故障模型的可执行证据。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/m7-audit-chain.test.ts:35 审计链测试检查第二行 prev_hash 指向第一行 event_hash,正常追加路径确实形成 hash-linked ledger。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/m7-audit-chain.test.ts:48 篡改 detail 后 verifyChain 返回 brokenSeq,说明链不仅存在,而且能捕捉历史事件字段变更。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/erasure.test.ts:165 actor erasure 测试要求删除 Alice 三条事实并保留 Bob 的事实,擦除范围按 provenance 和 scope 精确收束。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/erasure.test.ts:467 audit survives erasure 测试确认 erased memory 的 write/read 审计仍在,并只追加一个 forget 行。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/erasure.test.ts:624 sibling alias graph regression 防止外部租户的 Nimbus-Falcon alias 污染当前租户的 entity erasure。 这处引用用于判断实现边界,而不是只给页面堆行号;它把设计说法落到可检查的控制流、数据模型或测试断言上,读者可以沿着同一文件继续追踪上下游。

test/allocator-fault.test.ts:227-273source
 227describe("ContextAllocator.assemble", () => {
 228  it("assembles pinned, scope and predicted blocks in priority order under budget", async () => {
 229    const { allocator, audit, cold } = makeAllocator(SCOPE_ROWS);
 230    const ws = await allocator.assemble({
 231      namespace: NS,
 232      actor: "user:kay",
 233      agentId: "agent-7",
 234      policy: policy({
 235        pinnedConcepts: [REFUND_CONCEPT.path],
 236        tokenBudget: 100,
 237      }),
 238      taskHint: "database migration checklist",
 239    });
 240
 241    const kinds = ws.blocks.map((b) => b.kind);
 242    expect(kinds[0]).toBe("pinned-concept");
 243    expect(cold.readNamespaces).toContain(NS);
 244    expect(ws.blocks[0].ref).toBe(REFUND_CONCEPT.path);
 245
 246    const scopeRefs = ws.blocks
 247      .filter((b) => b.kind === "scope-memory")
 248      .map((b) => b.ref);
 249    expect(scopeRefs).toEqual(["m1", "m2"]); // m3 never accessed
 250
 251    const relRefs = ws.blocks
 252      .filter((b) => b.kind === "predicted-relevant")
 253      .map((b) => b.ref);
 254    expect(relRefs).toContain("m3");
 255
 256    // no duplicate refs across lanes (dedupe)
 257    const refs = ws.blocks.map((b) => b.ref);
 258    expect(new Set(refs).size).toBe(refs.length);
 259
 260    // budget respected, totals consistent
 261    expect(ws.totalTokens).toBeLessThanOrEqual(ws.budget);
 262    expect(ws.totalTokens).toBe(
 263      ws.blocks.reduce((s, b) => s + b.tokenEstimate, 0),
 264    );
 265
 266    // audit row
 267    const ev = audit.events.at(-1)!;
 268    expect(ev.op).toBe("assemble");
 269    expect(ev.result).toBe("ok");
 270    expect(ev.namespace).toBe(NS);
 271    expect(ev.detail).toContain(`blocks=${ws.blocks.length}`);
 272    expect(ev.detail).toContain(`tokens=${ws.totalTokens}`);
 273  });
test/erasure.test.ts:467-522source
 467  it("pre-existing audit rows for erased memory stay; a forget row is appended", async () => {
 468    const ns = "acme/audited";
 469    const owner = ownerOf(ns);
 470    const f = await plant({
 471      id: "01AU1",
 472      namespace: ns,
 473      content: "A fact that was read before it was erased.",
 474      provenance: [{ source: "conversation", actor: "grace", ts: NOW }],
 475    });
 476
 477    // Simulate the original write + a later read being audited (D4).
 478    await audit.append({
 479      ts: NOW,
 480      actor: "grace",
 481      agentId: "agent-1",
 482      op: "write",
 483      memoryId: f.id,
 484      namespace: ns,
 485      purpose: null,
 486      result: "ok",
 487    });
 488    await audit.append({
 489      ts: NOW,
 490      actor: "reader",
 491      agentId: "agent-2",
 492      op: "read",
 493      memoryId: f.id,
 494      namespace: ns,
 495      purpose: "support",
 496      result: "ok",
 497    });
 498
 499    const before = await audit.count(ns);
 500
 501    const result = await forget(
 502      { db, audit, signingKey },
 503      { kind: "actor", value: "grace" },
 504      owner,
 505    );
 506    expect(result.erasedCount).toBe(1);
 507
 508    // The row is gone from memory_objects...
 509    expect((await index.getById(f.id, ns)).obj).toBeNull();
 510
 511    // ...but its audit history survives, and a `forget` row was appended.
 512    const events = await audit.list(ns, { limit: 100 });
 513    const opsForMemory = events
 514      .filter((e) => e.memoryId === f.id)
 515      .map((e) => e.op);
 516    expect(opsForMemory).toContain("write");
 517    expect(opsForMemory).toContain("read");
 518
 519    const after = await audit.count(ns);
 520    expect(after).toBe(before + 1); // exactly one new row: the forget
 521    expect(events.some((e) => e.op === "forget" && e.result === "ok")).toBe(true);
 522  });
10 / operator view

适合学习的地方与合并前风险

源码判断

适合借鉴

src/syscalls/index.ts:124 把横切能力注入 syscall runtime,适合所有想把 agent 从 demo 推到组织系统的项目;src/driver/view.ts:181 则展示了“插件可扩展但边界不可扩展”的工程写法。src/audit/index.ts:109 与 src/erasure/certificate.ts:75 共同说明,证据链和证书验证最好从第一天进入数据模型。

需要继续确认

src/write/index.ts:354 的候选级处理非常细,但真实部署仍要压测 LLM outage、embedding brownout 和 Postgres 事务边界;src/fault/index.ts:317 会记录 fault pressure,因此运营侧也需要避免把噪声 query 变成错误优化信号。

∞ / deeper pages

继续拆源码