LifeOS Data Classification & Inference-Source Routing
LifeOS Data Classification & Inference-Source Routing
The doctrine that decides which inference source may process which data. Four data classes, four+ inference sources, one per-route routing matrix. Fail-closed. Designed + adversarially hardened 2026-06-18; routing policy revised by the principal the same day.
Why this exists
LifeOS draws inference from sources at different trust/egress tiers. Native (Anthropic) and Forge (OpenAI) are the two trusted US vendors; the GLM route (GLM via the OpenRouter broker, OpenRouter.ts) is an additional external cloud. Each external call is a data-egress surface. This doctrine classifies every piece of data and binds each class to the routes allowed to see it.
The core principle: data egress is ranked by trust, content is ranked by sensitivity, and a route may only process data at or below its ceiling. Unclassified data is RESTRICTED (fail-closed).
The four data classes
| Class | Rank | One-line test | Leak consequence |
|---|---|---|---|
| RESTRICTED | 0 (highest) | “If this showed up in someone else’s logs, do I have to rotate something or notify someone?” | Irreversible — rotation, breach notice, contract/law break |
| CONFIDENTIAL | 1 | ”Is this about MY health, money, security posture, private strategy, or inner life?” | Real harm (embarrassment, competitive loss, profiling) — no rotation |
| INTERNAL | 2 | ”Would I shrug if a trusted peer saw this, but I haven’t published it?” | Low — reveals how the system works, not who I am |
| PUBLIC | 3 (lowest) | “Is this already on the internet, or built to go there?” | None — already public or publish-destined |
RESTRICTED — auth material, third-party PII, customer-owned data. .env + anything quoting it; API keys/OAuth/Cloudflare/Google creds; customer-owned engagement data; contacts’ PII; financial source docs. Paths: **/.env, LIFEOS/USER/CONFIG/CREDENTIALS/**, LIFEOS/USER/WORK/CUSTOMERS/**, LIFEOS/USER/TELOS/FINANCES/**, LIFEOS/USER/CONTACTS.md. Also the fail-closed default.
CONFIDENTIAL — the user’s own sensitive life/business data: health, financials, own-infra security findings, TELOS internals, hot-layer + relationship memory, People/Companies notes. Default for all of LIFEOS/USER/** except RESTRICTED sub-globs and named PUBLIC demotes.
INTERNAL — private but not sensitive: ISAs, work notes, plans, non-PII knowledge, non-secret config, system internals. Paths: LIFEOS/MEMORY/**, skills/**, LIFEOS/USER/CONFIG/** (minus CREDENTIALS), CLAUDE.md, LIFEOS/LIFEOS_SYSTEM_PROMPT.md.
PUBLIC — already published or publish-destined: live blog/newsletter content, the scrubbed sync-docs.ts output only (raw LIFEOS/DOCUMENTATION/** is INTERNAL until scrubbed), post-SecurityFilter daemon profile, CANONICAL_CONTENT.md, public repo source.
The inference sources
| Tier | Source | Vendor | Egress |
|---|---|---|---|
| 0 | LOCAL | on-device (ollama) | none — NOT YET WIRED |
| 1 | NATIVE | Anthropic | home vendor, subscription/enterprise terms |
| 2 | FORGE | OpenAI (codex) | external, single known vendor |
| 2 | GENE | OpenRouter broker → GLM 5.2 | external broker — non-deterministic downstream provider/country; most opaque |
The routing matrix (the rule)
The ceiling is per-ROUTE — source + model + data-residency — not per-source (principal policy, revised 2026-06-18). Three rules, most-restrictive wins:
- RESTRICTED-capable vendors — Anthropic (Native) + OpenAI (Forge) ONLY. These two trusted US vendors may process every class, including RESTRICTED. No other provider or model ever touches RESTRICTED.
- Chinese-origin model → INTERNAL ceiling. Any model from a Chinese lab (GLM/Z.ai, Kimi/Moonshot, MiniMax, Qwen, DeepSeek) caps at INTERNAL regardless of who hosts it — and only when routing is guaranteed off China/Russia; an opaque broker with no such guarantee drops to PUBLIC.
- US/allied model, US inference, US company, verified no China/Russia egress → up to CONFIDENTIAL. Otherwise PUBLIC.
LOCAL (on-device) handles everything. A route’s ceiling depends on the model AND the residency: the GLM route pinned to a US+ZDR provider reaches INTERNAL; the same route unpinned drops to PUBLIC.
Resolved ceilings for the wired routes:
| Route | Ceiling | Why |
|---|---|---|
| NATIVE (Anthropic) | RESTRICTED | trusted US vendor |
| FORGE (OpenAI) | RESTRICTED | trusted US vendor |
| GENE · GLM 5.2, pinned US + ZDR (default: Fireworks) | INTERNAL | Chinese model, residency guaranteed by allowlisted US provider |
| GENE · GLM 5.2, unpinned or non-US pin | PUBLIC | residency not guaranteed (could route to CN/RU) |
Machine-readable form: maxClassForRoute() / ROUTES in LIFEOS/TOOLS/models.ts. Live enforcement: hooks/EgressClassGuard.hook.ts.
Note on Forge/OpenAI at RESTRICTED: assumes OpenAI’s enterprise / zero-retention API terms apply to the codex path. Worth explicit confirmation — a principal-risk decision, not an inherited default.
Marking convention
Path-default-first, fail-closed, mark-only-the-exceptions:
- Path defaults via
LIFEOS/USER/CONFIG/data-classification.json(longest-match wins). “If you didn’t tag it, it’s as locked as its directory.” - Demote-only tags — public-facing files inside a stricter tree carry
data_class: PUBLICfrontmatter (DAEMON.md, CANONICAL_CONTENT.md). Tag down only. - Auto-promote by content — a secret-shape match anywhere → RESTRICTED, blocked from all cloud sources.
- In-flight requests declare class; inline
[[dc:public]]for one-offs. - Precedence: auto-promote > explicit tag > path default; conflicts resolve to the more restrictive.
- Mixed payloads inherit the highest class of any constituent.
Enforcement
Built + LIVE:
hooks/EgressClassGuard.hook.ts+hooks/lib/egress-class-core.ts— PreToolUse Bash gate, registered in the Bash matcher. Detectsbun … OpenRouter.tsexecs, classifies the payload (secret-shape scan + sensitive-path references), and BLOCKS anything above the route ceiling. Gates at Bash, so it catches both direct calls and any subagent’s own internal OpenRouter.ts calls. Fail-closed on a confirmed Tier-2 route; never blocks unrelated Bash. Logs toLIFEOS/MEMORY/OBSERVABILITY/egress-decisions.jsonl. No LLM in the path. Verified by an 8-case battery + an observed live block.LIFEOS/TOOLS/models.ts—SOURCE_TIER,RESTRICTED_CAPABLE_VENDORS,CHINESE_MODEL_PATTERNS,maxClassForRoute()/isRouteAllowed(),ROUTES.LIFEOS/USER/CONFIG/data-classification.json— path-default glob table.- The hard guard is the deterministic
EgressClassGuardhook (above);OpenRouter.tsforcesprovider.data_collection: deny. (The formerGene.mdagent’s soft prompt-guard was retired with the persona 2026-07-02; the capability now lives entirely in the tool + hook.) - GLM route INTERNAL-by-default (2026-06-20): the GLM/OpenRouter route now default-pins a US+ZDR provider (Fireworks) so its standing ceiling is INTERNAL, not PUBLIC.
egress-class-core.tsgrants the INTERNAL ceiling ONLY when the--pinvalue is on theUS_ZDR_PROVIDERSallowlist; an unpinned call or a non-US pin fails closed to PUBLIC. The pin is honored as a constraint, not a preference:OpenRouter.tssendsprovider.order:[pin]+allow_fallbacks:false, so if the pinned US provider is down the call errors rather than silently rerouting off-US (verified live: a Fireworks rate-limit returned 429, did not fall back). Two honest caveats this doctrine must not blur: (1) ZDR ≠ residency —data_collection:denyis a retention/training guarantee; US geography comes only from the pinned provider being US infra (OpenRouter’s metadata claim), they are orthogonal. (2) The broker is inside the trust boundary — OpenRouter proxies plaintext and has its own logging policy, so the INTERNAL leak surface = OpenRouter infra + the pinned provider, not the provider alone. Acceptable for INTERNAL; named here rather than hidden. The gate enforces residency (route geography); the INTERNAL/CONFIDENTIAL content line still rests on the shape-scan + operator discipline (the gate sees secret-shapes and USER-paths, not semantic CONFIDENTIAL prose) — closing the prior “GLM wrapper pin” gap on residency, not on semantic content classification.
Still planned:
- Path/identity scrub for INTERNAL→Tier-2 (reuse sync-docs.ts + Daemon SecurityFilter rules).
- Pulse statusline count; ShadowRelease release-time backstop.
Known limits of the v1 gate (honest): it inspects the Bash command string — so it catches secrets and sensitive paths named in the command, but NOT the file contents the tool reads at runtime, nor free-prose PII with no token-shape or path reference. Strong first layer, not complete.
Gaps / roadmap
- LOCAL (ollama) not wired — RESTRICTED data has no on-device model destination; today it’s redact-or-deterministic-code only.
data-classification.jsonneeds review before deeper wiring.- No INTERNAL→PUBLIC downgrade-for-egress token yet.
LIFEOS/DOCUMENTATION/is dual-state — raw=INTERNAL, scrubbed=PUBLIC; pin the scrub-output dir into the PUBLIC glob.- Secret-shape scan catches tokens, not prose PII.
- Return-path leakage uncontrolled — governs input, not what a response echoes out.
- Non-inference egress (raw WebFetch/WebSearch, skill curls, Telegram/Discord/email) is out of scope here.
