LifeOS Atlas:基于 SQLite 与 sync-and-expire 的资产图谱——从 Cloudflare Worker 到凭据爆炸半径的本地系统记录
2026/9/13 12:33:43 网站建设 项目流程

LifeOS Atlas:基于 SQLite 与 sync-and-expire 的资产图谱——从 Cloudflare Worker 到凭据爆炸半径的本地系统记录

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

LifeOS 的 Atlas 是一个基于图的"当前状态资产管理系统":它把 Cloudflare Worker、域名、DNS 记录、仓库、项目、扫描目标、launchd/systemd 服务、机器、设备(gear)统一维护成一张可查询的本地 SQLite 图谱,从各"权威数据源"同步并带有硬门控的过期(sweep)机制,最终通过 Pulse 的 ATLAS 标签页以 d3-force 力导向图呈现。读完本文,你将理解 Atlas 的"观察而非断言"数据模型、sweep 不变量在 Store.ts 中的具体实现、双层级更新(小时级全量对账 + 事件提示定向重采)的调用链,以及atlas owns/blast/exposed这类爆炸半径查询背后的递归 CTE 原理。

Atlas 的设计参考了 CNCF Cartography 的模型(同步并过期的采集器、按来源分列的观察记录、每条事实都有 first/last-seen 时间戳),但换掉了底座:TypeScript、bun:sqlite、无守护进程、无 Neo4j。正如文档定调:"要爬上理想状态之前,先要准确知道你此刻所处的状态"——Atlas 就是 LifeOS 核心命题中"当前状态"那一半的具体实现。

Atlas 在仓库中的位置

部件位置
CLI + 存储 + 采集器LIFEOS/ATLAS/(Atlas.tsStore.tscollectors/*.ts
后台服务com.lifeos.atlas——launchd(macOS,15 分钟 tick + 对事件文件的 WatchPaths)或 systemd --user timer(Linux,OnUnitActiveSec15 分钟,无 WatchPaths 等价物),由 InstallAtlas.ts 安装
事件提示 Hookhooks/AtlasEventCapture.hook.ts(PostToolUse: Bash, Write, Edit, MultiEdit)
Pulse 模块PULSE/modules/atlas.ts →GET /api/atlasGET/POST /api/atlas/insights→ 页面/atlas(tier1 导航):d3-force 实时图 + Insights + Browse + Gaps 标签
Insights 缓存MEMORY/STATE/atlas-insights.json(Inference 叙事,按指标内容哈希作键)
数据库(SQLite, WAL)~/.local/state/lifeos/atlas/atlas.db——按设计位于两个 git 仓库之外
脱敏 Pulse 快照~/.local/state/lifeos/atlas/snapshot.json
事件提示~/.local/state/lifeos/atlas/events.jsonl

这些路径常量并非散落各处:Store.ts 中统一定义了ATLAS_DIR(可用ATLAS_DIR环境变量覆盖)、DB_PATHSNAPSHOT_PATHEVENTS_PATH,数据库目录~/.local/state/lifeos/atlas/刻意不放入任何 git 仓库——即使不含秘密值,图谱本身也是"资产版图",因此本地-only、置于 git 之外。

架构:单一写者、观察而非断言、硬门控的 sync-and-expire

Atlas 的四条核心架构原则,全部能在源码中逐条对上:

1. 一个写者类,一个读者工件

采集器(collector)是唯一写图谱事实的东西;Pulse 只读导出的快照,从不碰活动数据库;atlas sql以只读方式打开库。Store.ts 的构造函数在只读模式下不建表、不设 WAL;而 Atlas.ts 中sqlinsights命令都显式传入{ readonly: true }——"结构上无法写入"。这使 WAL 单写者模型在没有守护进程的情况下依然诚实:写路径只有采集器经Store.applyRun()这一条事务。

2. 观察,而非断言(Observations, not assertions)

每条资产都携带按采集器分列的source_observation行——同一个域名可以同时被cloudflareprojectsinfra-inventory三个采集器观察到,落在同一个资产上(规范键形如domain:example.comcloudflare:worker:mysitegithub:repo:owner/name)。schema 上的主键设计直接体现了这一点(见 Store.ts):

CREATE TABLE source_observation ( asset_id INTEGER NOT NULL REFERENCES asset(id) ON DELETE CASCADE, collector TEXT NOT NULL, first_seen TEXT NOT NULL, last_seen TEXT NOT NULL, last_run_id INTEGER NOT NULL, fresh INTEGER NOT NULL DEFAULT 1, attrs TEXT NOT NULL DEFAULT '{}', PRIMARY KEY (asset_id, collector) ) STRICT;

资产的生命周期由所有观察共同推导:只要还有任何采集器看见它,它就 active;只有当所有采集器都停止观察,它才会先 stale、后 expired。单个来源沉默只会让该来源的观察过期,绝不直接杀掉资产。推导逻辑在 deriveStatuses() 中实现,阈值常量在 Store.ts:

/** Days without any fresh observation before an asset/edge transitions. */ const STALE_AFTER_DAYS = 2; const EXPIRE_AFTER_DAYS = 14;

即最后一次被观察到后 2 天无新鲜观察转stale,14 天转expired,每次状态迁移都会写入lifecycle_event表(这正是 Pulse GAPS 标签页和"漂移可见化"的数据来源)。

3. 带硬门控的 sync-and-expire(Cartography 模式 + 审计牙齿)

每次运行给"看到的东西"打时间戳;"清扫"(把观察标记为 unfresh)只允许在一次成功的、枚举完整的、full作用域的运行中发生,且只针对该采集器自己的观察。分页截断、API 错误、限流 → 记为PARTIAL运行,不清扫,旧状态原封不动。定向(targeted)运行结构上就无法使任何东西过期。

这个门控在 applyRun() 里只有四行:

// SWEEP GATE — the only path that can un-fresh observations. if (result.complete && scope === "full") { this.db.prepare("UPDATE source_observation SET fresh = 0 WHERE collector = ? AND last_run_id != ?").run(collector, runId); this.db.prepare("UPDATE edge_observation SET fresh = 0 WHERE collector = ? AND last_run_id != ?").run(collector, runId); swept = true; }

CollectResult.complete的契约注释写得很直白(Store.ts):"True ONLY when enumeration finished with no errors/truncation. Gates sweeping." 而 runSync() 中失败的采集器"什么也不写"——applyRun整个事务不落地,既有图谱状态保持完整,最后仍会重写快照。

4. 双层更新:对账是真理,提示是延迟

  • 对账(Reconciliation)atlas tick检查上一次成功full同步是否超过 1 小时,超过则全量同步。小时阈值是常量FULL_SYNC_INTERVAL_MS = 60 * 60 * 1000(Atlas.ts),判定 SQL 为SELECT MAX(finished_at) FROM sync_run WHERE scope = 'full' AND status = 'success'(lastFullSyncAt())。
  • 提示(Hints):AtlasEventCapture.hook.ts 监控"变更型"工具调用并向events.jsonl追加{ts, source, tool}提示行。Hook 的匹配规则(L37-L51)覆盖:Bash 中的wrangler deploy/ DNS 记录操作(→cloudflare)、gh repo create|delete|rename|archive(→github)、launchctl load|unload|bootstrap|bootout(→launchd)、systemctl --user ...(→systemd);以及 Write/Edit/MultiEdit 对PROJECTS.mdGEAR.mdinfra-inventory.ts~/Library/LaunchAgents/com.(lifeos|pai).*~/.config/systemd/user/com.(lifeos|pai).*的编辑。

launchd 侧 WatchPaths 被触发后,atlas tick消费提示并只对这些来源做定向重采。关键纪律是"提示绝不写事实":采集器仍然回到权威源重新拉取真理。从 processEvents() 的实现看,事件文件用rename做原子认领(新提示另起新文件)、坏行静默忽略、定向同步以targeted:events作用域运行——所以"漏掉一条提示没有任何代价,小时级全量对账会自愈"。

5. 永不存储秘密(Anti-claim A1)

任何 token、key、cookie 值都不被存储、记录或导出。凭据类资产只携带引用;exportSnapshot() 在导出前把kind === "credential"的资产 attrs 一律清空为"{}",脱敏快照才交给 Pulse。

v1 采集器:九个权威源与平台互斥规则

文档定义的 v1 采集器集合如下,实现位于 ATLAS/collectors/:

采集器权威源产出
cloudflareCF API(token 取自~/.claude/.env,仅 header 认证)域名(zones + hostnames + workers.dev 主机)、dns_records、workers(带cron/workers_dev/queue_consumer属性)、kv/r2/d1;边:OWNS、SERVES(自定义域名 + workers.dev)、ROUTE(zone 路由)、CALLS(服务绑定,worker→worker)
githubgh repo list仓库(visibility、archived、pushed_at)
projectsUSER/PROJECTS.md主表项目;SERVES → 域名、DEPLOYED_FROM → 仓库
infra-inventoryARBOL/Shared/infra-inventory.ts(只观察,不替代)目标(targets)、安全平面系统节点;MONITORS → 域名、REGISTERED_IN
launchd~/Library/LaunchAgents/com.{lifeos,pai}.*(plutil 恒用-o -服务、本机;RUNS_ON 边
systemd~/.config/systemd/user/com.{lifeos,pai}.*.{service,timer}(launchd 的 Linux 姊妹——直接解析 unit 文件,不起子进程)服务、本机;RUNS_ON 边
gearUSER/GEAR.md表格带 category/role 的设备
secrets事件响应凭据注册表(GenerateRegistry.ts --json)+ 分层形状(DetectCriticalKeys.ts --format json凭据(priority/cadence/vendor/dependencies 属性)、孤儿凭据;从本机(以及 env 文件被配置仓库跟踪时从该仓库)发出 HOLDS 边

launchdsystemd是平台互斥的:Atlas.ts 按process.platform恰好注册其中一个:

const SERVICE_COLLECTOR = process.platform === "linux" ? systemd : launchd;

因此 macOS 安装的采集器集合与引入 systemd 之前逐字节一致;在 macOS 上执行atlas sync systemd会作为未知采集器响亮地失败(runSync() 打印unknown collector: systemd (have: ...)),而不是报一个"永远不完整"的运行。

两个值得注意的实现决策:

  • Worker 凭据来自cloudflare采集器而非secrets:每个脚本的/settings响应列出其secret_text绑定名,提供方才是权威。grep 源码只能找到一小部分,因为多数 worker 秘密是用wrangler secret put推送的,不出现在任何文件里。
  • v2 候选(文档标注为"尚未指定"):UniFi/网络设备、智能家居、费用/账户、云端观察馈送(D1 暂存 → 本地拉取)、只存在于供应商侧的凭据(OAuth grant、仪表板签发的 token)——目前对所有采集器均不可见。

CLI 全解:从对账到爆炸半径

命令行入口是 Atlas.ts,安装后的典型调用形式(路径以~/.claude/LIFEOS安装布局为准):

bun ~/.claude/LIFEOS/ATLAS/Atlas.ts <cmd> sync [collector ...] 全量对账(默认全部采集器) sync X --targeted <scope> 作用域内的仅 upsert 运行(从不清扫) tick events-then-hourly 入口(launchd 实际执行的命令) status 按采集器的运行记录 + 数量 owns <key> OWNS 闭包——删除 <key> 会孤儿化什么 blast <key> 入向运行闭包——什么依赖于 <key> exposed <key> <key> 若失窃会泄露哪些凭据(按优先级排序) stale 已不再被任何采集器观察的资产 unregistered 有东西 SERVES 但没有策展目标 MONITORS 的域名 sql "SELECT ..." 只读 SQL 直通 export 重写脱敏的 Pulse 快照

此外,Atlas.ts 还实现了文档 Insights 一节依赖的insights子命令(只读打开库,输出确定性结构指标 JSON)。

sync:--targeted的语义

sync子命令的解析逻辑(L107-L112):不带--targeted时 scope 为full;带--targeted <scope>时 scope 变为targeted:<scope>。采集器收到非full的 scope 参数(collector.collect(scope === "full" ? undefined : scope)),只 upsert 新事实;配合前文的 sweep 门控,定向运行不可能使任何观察过期。退出码等于失败采集器数量,unknown collector计入失败并继续处理其余采集器。

owns / blast / exposed:三个方向的闭包查询

这三个命令是 Atlas 对"破坏性操作前的爆炸半径预览"的核心贡献,实现全部是 SQLite 递归 CTE(用 Cypher 的替代品):

owns <key>——出向 OWNS 闭包,回答"删掉它会孤儿化什么"(Store.owns()):

WITH RECURSIVE reach(id) AS ( SELECT id FROM asset WHERE canonical_key = ? UNION SELECT e.dst FROM edge e JOIN reach r ON e.src = r.id WHERE e.kind = 'OWNS' AND e.status = 'active' ) SELECT ... FROM asset a JOIN reach r ON a.id = r.id WHERE a.canonical_key != ?

blast <key>——入向运行闭包,回答"它坏了/被轮换了,什么会跟着坏"(Store.blast())。"运行边"集合是常量 OPERATIONAL_EDGE_KINDS:SERVES, ROUTE, CALLS, DEPENDS_ON, RUNS_ON, OWNS, DEPLOYED_FROM, HOLDS——其中 HOLDS 被刻意放入运行边,因为"持有某凭据的资产会在该凭据轮换时坏掉",所以atlas blast credential:X才能正确返回所有需要新值的对象。

exposed <key>——沿 OWNS + HOLDS 出向遍历,收集可达的credential类资产(Store.exposed()),回答事件响应问题:"这个资产失窃了,它泄露了什么"。CLI 侧(Atlas.ts)把结果按CRITICAL, HIGH, MEDIUM, LOW, DEFER, ORPHAN, UNKNOWN优先级排序——即使注册表从未对某凭据分层,只要形状匹配,它仍携带自己的层级,"最危险的条目不会因为未知而沉底"。输出末尾还打印运维纪律:"先遏制资产,再轮换——对着活的立足点轮换等于把新值双手奉上"。

破坏性操作纪律atlas owns/atlas blast对 Algorithm 主张 16 而言是派生证据——先跑它们做爆炸半径预览,再在任何删除前对照提供方的权威 API 确认。Atlas 永不替代权威源;CLI 在每次 owns/blast 调用末尾都会打印这条提醒(Atlas.ts)。

status / stale / unregistered / sql

  • status输出三部分 JSON:按采集器的last_run/成功数/总运行数(sync_run表聚合)、按 kind/status 的资产计数、以及资产与活跃边总数(Store.status())。
  • stale列出所有status != 'active'的资产及其last_observed_at
  • unregistered是注册缺口查询:被活跃 SERVES 但没有任何target类资产 MONITORS 的域名,且排除*.workers.dev(Store.unregistered())——即"有流量但没有小时级扫描器中的认证边界断言"的应用。
  • sql直通只读 SQL,"结构上无法写入"(构造只读 Database)。

凭据入图:一条 HOLDS 边同时回答两个方向的问题

凭据本身是资产(kind: credential);任何存储它的对象都获得一条指向它的HOLDS边。因为 HOLDS 是运行边类,一种关系同时回答两个方向

  • atlas exposed <asset>走出向(OWNS+HOLDS)——事件响应问题:"这个失窃了,它泄露了什么"
  • atlas blast credential:X走入向——轮换问题:"轮换这个会坏什么"

价值永不入图:采集器只读名字,exportSnapshot()在任何东西到达 Pulse 前就抹掉凭据 attrs。文档记录了这次 join 的即时回报:第一次 env↔graph 对账就发现了 64 个只活在 Cloudflare Worker 秘密里、任何清单都从未见过的凭据,把整个资产组合的 compromise-tier 计数从 3 提升到 11。

后台服务安装:launchd 与 systemd 双后端

InstallAtlas.ts 负责物化并引导com.lifeos.atlas计时单元,两条平台分支并行存在、互为补充而非替代:

bun ~/.claude/LIFEOS/ATLAS/InstallAtlas.ts # 安装 bun ~/.claude/LIFEOS/ATLAS/InstallAtlas.ts --uninstall # 卸载 bun ~/.claude/LIFEOS/ATLAS/InstallAtlas.ts --status # 状态
  • macOS(launchd):把 com.lifeos.atlas.plist.template 中的{{HOME}}/{{BUN}}/{{PATH}}占位符替换后写入~/Library/LaunchAgents/com.lifeos.atlas.plistlaunchctl bootout(幂等拆除旧单元)后launchctl bootstrap gui/<uid>。plist 带 15 分钟StartInterval,外加对事件文件的WatchPaths——所以 AtlasEventCapture.hook.ts 捕获到一次变更就立即触发定向同步。
  • Linux(systemd --user):把 com.lifeos.atlas.service.template 与 com.lifeos.atlas.timer.template 写入~/.config/systemd/user/daemon-reloadsystemctl --user enable --now启用 timer。systemd 在这里没有 WatchPaths 等价物——提示只能等到下一个 tick,代价只是延迟。Linux 分支还会执行loginctl enable-linger <user>(L203-L206)让 timer 在注销/重启后存活;用户名通过id -un而非$USER解析,因为非交互式 shell 中USER不保证被设置。
  • 依赖探测bun是硬依赖(单元的 ExecStart 就是 bun,无可降级路径,which bun找不到直接失败);gh缺失则只是警告并继续安装——Github.ts采集器在二进制缺失时会自然降级为不完整(PARTIAL)运行,"因为八个采集器之一够不到源就拒绝安装整个 Atlas tick,严格劣于装上并让那个采集器报 PARTIAL"(detectGh() 注释)。

它抓到了什么:首次同步的实例

  • 注册缺口atlas unregistered首次运行就发现 33 个"在服务但未策展"的域名——有流量但小时级扫描器中没有认证边界断言的应用。
  • 爆炸半径atlas owns domain:example.com列出一个 zone 删除会孤儿化的 DNS 记录与 hostname——正是 2026-07-17 DNS 删除事故中"缺失的那个查询"。
  • 漂移:停止被观察的资产以可见方式转 stale(Pulse GAPS 标签 + lifecycle 事件),而不是悄悄消失。

Pulse 上的三个表面

  • Graph——实时d3-force仿真(SVG 归 d3 所有,外壳是 React):拖节点拉并固定(双击释放)、滚轮缩放、拖背景平移、悬停隔离邻居。节点大小编码 degree,颜色编码 kind,kind 过滤器驱动显示。
  • Insights——确定性指标瓦片(atlas insights加上Inference 生成的三维叙事:运行系统安全事物如何互连。叙事由 shell 调用Inference.ts --level high在指标上生成,缓存到MEMORY/STATE/atlas-insights.json,键是指标的 sha256 内容哈希前 16 位(metricHash())。同步改变了图 → 哈希变化 → 缓存stale→ 下次查看重新生成;Regenerate 按钮强制刷新(202 + generating 异步模式,与 Algorithm 标签的 summary 同一模式)。这就是"对更新跑推理"的表面。指标本身由 Store.insights() 以纯 SQL 产出:资产/边普查、unwired_workersworker_wiring六类调用方式分解、projects_no_repo溯源缺口、blast_zones(按 OWNS 数量排序的 zone 爆炸半径地图)、数据存储蔓延、auth_curation_gaps、stale 计数。
  • Browse / Gaps——按 kind 列出每条资产及其按来源的观察;Gaps 汇总认证策展缺口 + stale + 生命周期事件。

值得强调系统提示词(SYSTEM_PROMPT)里刻进推理约束的规则:200–320 词、每句话必须锚定数据中的数字、"绝不在unwired_workers或 stale 计数支持的情况下把任何资产称为 unused/dead/orphaned"——这正是下一节规则的执行端。

缺失指标规则(2026-07-22 学到的教训)

任何声称某事物"未使用/孤儿/死亡"的指标,必须建立在完整的边覆盖之上——缺少一种边类型绝不等于缺少使用。第一次 Insights 生成把绝大多数 worker 称为"孤儿",因为采集器当时只抓取了 worker 的自定义域名,对 workers.dev 来源、zone 路由、服务绑定、cron 触发全部盲视。真实的"未接线"数量只有寥寥几个。此后立起三道护栏:

  1. Cloudflare 采集器在任何孤儿查询运行前捕获全部六种调用机制(自定义域名、workers.dev、zone 路由、服务绑定、cron、queue);
  2. unwired_workers指标要求 worker同时没有 {SERVES、ROUTE、CALLS 目标、cron、workers.dev、queue}——对照 insights() 的实现,这六个条件一个不少:
SELECT COUNT(*) AS n FROM asset a WHERE a.kind='worker' AND a.status='active' AND json_extract(a.attrs,'$.workers_dev') IS NOT 1 AND json_extract(a.attrs,'$.cron') IS NOT 1 AND json_extract(a.attrs,'$.queue_consumer') IS NOT 1 AND NOT EXISTS (SELECT 1 FROM edge e WHERE e.src=a.id AND e.kind IN ('SERVES','ROUTE') AND e.status='active') AND NOT EXISTS (SELECT 1 FROM edge e WHERE e.dst=a.id AND e.kind='CALLS' AND e.status='active')
  1. Insights 叙事提示词禁止在unwired_workers或 stale 计数不支持的情况下把任何东西称为"unused/dead/orphaned"。

扩展规则:新增任何资产类型时,先枚举它所有可能的连接方式,再为它写缺失查询。

血缘:保留 Cartography 的模型,换掉底座

Cartography(Lyft → CNCF Sandbox 2024)证明了这一模型:类型化节点/边、情报模块、sync-and-expire、漂移检测。Atlas 保留模型、替换底座——

维度CartographyAtlas
存储Neo4jSQLite(WAL):2026-07-21 跨供应商审计的并发发现是嵌入式列式图引擎为单写进程,SQLite WAL 恰好匹配"多进程无守护"拓扑
采集器Python 模块TS 采集器
查询Cypher递归 CTE(如前文 owns/blast/exposed 三查询)

适用前提与边界

  • 运行环境:需要 bun(后台单元的 ExecStart 即 bun),macOS 或 Linux 双后端之一;InstallAtlas.ts对两者之外的平台直接报错退出。
  • 数据位置:数据库、快照、事件文件全部位于~/.local/state/lifeos/atlas/,在 git 仓库之外;图谱不含秘密值,但仍是资产版图,应保持本地-only。
  • 纪律边界:Atlas 是派生证据(derived evidence),不是权威源。任何破坏性操作前必须对照提供方权威 API 确认——这条提醒由 CLI 在每次 owns/blast 调用中主动打印,是系统设计的一部分而非文档脚注。

Atlas 展示了如何在不引入图数据库、不运行守护进程的前提下,用单文件 SQLite + WAL 单写者 + 按来源观察表,把"我的系统里到底有什么、什么依赖什么、删了/坏了/泄了会怎样"变成一组可查询、可审计、可被 Pulse 实时呈现的确定性答案。

【免费下载链接】LifeOS⛰️ The Life Operating System — an intent engineering platform that moves you from your current state to your ideal state, in life and work.项目地址: https://gitcode.com/GitHub_Trending/pe/LifeOS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询