diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d85c54b25..467d7f181 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,131 +1,187 @@ # LifeOS architecture decisions -This document is the architectural source of truth for repository-wide boundaries. Feature-level specifications and runbooks may add detail, but they must not weaken these decisions. +**Status:** Implemented on active PR + +Protected-main `AGENTS.md`, source, migrations, tests, and live repository policy are the executable authority for shipped behavior. This document is the canonical whole-product architecture view. Active pull requests are evidence only until integration. ## 1. Product and deployment boundary -LifeOS is a modular, self-hostable personal operating system. Every bounded service must work independently and remain composable inside the monorepo deployment. Services communicate through versioned HTTP/event contracts and never read another service's database tables directly. +LifeOS is a public, multi-user, server-backed, self-hostable personal operating system. It operates independently and composes with other bounded contexts only through explicit versioned interfaces. + +The earlier login-free/browser-only local-first primary design, UUIDv7 internal identifiers, private-personal-only positioning, and single-application durable architecture are **Superseded**. Browser-local state is not durable until the owning service confirms persistence. Offline drafts and Docker Compose remain explicit supported profiles, not alternate sources of durable authority. ```mermaid flowchart LR - U[Web / PWA user] --> W[Next.js web boundary] - W --> G[Gateway / BFF] - G --> I[Identity service] - G --> P[Planning service] - G --> H[Habit service] - G --> R[Review service] - G --> A[AI proposal service] - G --> C[Calendar integration service] - G --> X[Plugin integration service] - P -. domain events .-> N[(NATS JetStream)] - H -. domain events .-> N - R -. domain events .-> N - subgraph Data ownership - IDB[(Identity PostgreSQL schema)] - PDB[(Planning PostgreSQL schema)] - HDB[(Habit PostgreSQL schema)] - ADB[(AI audit PostgreSQL schema)] - NDB[(Notification PostgreSQL schema)] - end - I --> IDB - P --> PDB - H --> HDB - A --> ADB + U[Web / PWA] --> G[Gateway / BFF] + G --> I[Identity] + G --> P[Planning] + G --> H[Habit] + G --> R[Review] + G --> C[Calendar] + G --> N[Notification] + G --> A[AI Proposal] + G --> V[Privacy] + G --> X[Plugin Integration] + P -. versioned events .-> J[(NATS JetStream)] + H -. versioned events .-> J + R -. projections/events .-> J + J -. reminder inputs .-> N ``` ### Required invariants -- Internal object identifiers are opaque UUIDv4 strings. Numeric provider identifiers are never reused as internal primary keys. -- Database object names contain at least two words and use `snake_case` unless an external standard requires another form. -- Each service owns migrations, runtime configuration, persistence adapters, tests, and shutdown behavior. -- Cross-service writes require an explicit API, event, saga, or plugin contract; shared-table coupling is prohibited. -- Public errors, metrics, logs, artifacts, and review evidence exclude credentials and unbounded tenant data. +- Internal/public product IDs are opaque UUIDv4; provider IDs are bounded external metadata. +- Product-owned database objects use descriptive multiword `snake_case`. +- Every service owns its persistence, migrations, credentials, runtime composition, tests, observability, and shutdown behavior. +- Services never read or mutate another service's tables directly. +- Cross-service composition uses versioned HTTP, event, saga, plugin, or MCP contracts and never grants SQL authority. +- Public errors, logs, metrics, retained artifacts, and model inputs exclude credentials, hidden reasoning, and unnecessary tenant content. +- AI output is untrusted inert proposal data until an explicit authorized decision; proposal evidence cannot execute its own operations. -## 2. AI proposal safety boundary +## 2. Identity, workspace, and data-rights authority -AI output is an inert proposal, not an execution command. The AI service can generate, persist, retrieve, and record explicit decisions about proposals, but it has no planning mutation repository or generic command bus. +Identity owns internal user identity, external provider mappings, workspace membership, sessions, authentication provenance, whole-request data-rights identity, and durable aggregate request/receipt evidence. Authentication-ceremony time is distinct from compatible session issuance and rotation. -```mermaid -sequenceDiagram - participant Browser - participant Web as Authenticated web BFF - participant Identity - participant AI as AI proposal service - participant Audit as Append-only AI audit store - - Browser->>Web: Proposal request + opaque session cookie - Web->>Identity: Validate session - Identity-->>Web: Workspace UUIDv4 + actor UUIDv4 - Web->>AI: Signed method/path/tenant/actor context - AI->>AI: Validate bounded request and model output - AI->>Audit: Persist immutable proposal evidence - Audit-->>AI: Recorded digest evidence - AI-->>Web: Inert proposal requiring confirmation - Web-->>Browser: Credential-free response -``` +Protected main includes: -The signed private context uses one active HMAC key and at most one previous verification-only key. Key identifiers, method, path, workspace, actor, and issuance time are integrity protected. Browser credentials and provider keys never reach the AI service. +- recent-authentication provenance and policy; +- durable data-rights request and immutable terminal receipt evidence; +- authenticated tenant-and-requesting-user status lookup; +- deterministic contributor export integrity evidence; +- the versioned `life-os.data-rights-contributor.v1` contract from PR #159. -Production contextual-orchestrator proposal requests send `orchestration_mode: auto` and omit provider-native `response_format`. That gateway passthrough would pin a single worker instead of adaptive orchestration. LifeOS remains the fail-closed parser and domain validator. Explicit route and conduct profiles stay on the live-conformance harness. +Planning is a protected contributor through PR #179 and its request-bound authenticated transport through PR #194. Habit is a protected contributor through PR #184 and its replay-safe authenticated transport through PR #192. Review contribution is **Implemented on protected main** in PR #195, Notification contribution is **Implemented on active PR** in PR #198, and AI contribution is **Implemented on active PR** in PR #199. -## 3. Test-time compute and live conformance +Issue #55 remains **Partial**. Active contributors do not become shipped truth, and even their future integration will not by itself finish Identity-owned data, Calendar, Privacy, Plugin Integration, durable reconciliation, retention/legal-hold/backup-expiry, protected export delivery, or final participant-set completion. -The deterministic proposal evaluator is authoritative for proposal validity, operation conformance, grounding, benign utility, forbidden-text leakage, and prompt-injection resistance. Live provider execution is governance evidence and is not a pull-request availability gate. +## 3. Planning, Habit, Review, Today, Notification, and first-party journey -```mermaid -flowchart TB - F[Versioned realistic fixtures] --> E[Production ProposalQualityEvaluator] - E --> B[Strong single-route baseline] - E --> L[Lower reasoning-effort route] - E --> M[Bounded multi-agent conduct workflow] - B --> D[Counts, rates, and deltas] - L --> D - M --> D - D --> V[Validated credential-free report] - V --> Q{Measured quality gain without safety regression?} - Q -->|No| S[Keep single-route baseline] - Q -->|Yes| O[Permit bounded orchestration profile] -``` +Planning owns Goals, Projects, Tasks, search, and the durable Today aggregate. Habit owns recurring definitions and completion evidence. Review owns guided-review persistence/projections without Planning or Habit mutation authority. Notification owns reminder occurrences, claims, delivery attempts, outcomes, and recovery evidence. + +Protected main now requires signed tenant authority on Planning through PR #168 and request-bound signatures through PR #188. Habit signed authority is protected through PR #173. Review request-bound signed workspace authority is protected through PR #185. + +Gateway Today composition is real protected behavior: PR #186 composes authenticated Planning state and PR #187 composes authenticated Habit state. Issue #163 is completed; the earlier PR #164 fail-closed placeholder removal remains historical safety evidence, not the current end state. + +Durable Today synchronization is protected-main behavior. Durable Today uses explicit local-to-workspace acceptance, strong create/update preconditions, idempotency, and stale-conflict reconciliation. No browser draft is presented as durable before server acceptance. + +Issue #209 is **Partial** for the complete first-party buyer journey. The current Draft stack starts at PR #214 with an authenticated Goal BFF that keeps Identity-derived workspace authority and exact Planning request signing server-side. Descendants add the remaining BFF prerequisites and buyer-visible workspaces; PR #229 is the durable `/goals` workspace and PR #234 is the current stacked `/review` workspace with persistence-aligned ritual-period uniqueness. These pages consume validated server-authoritative evidence and do not move Planning/Review persistence or workspace authority into the browser. + +The active journey remains incomplete until the dependency-ordered Goals → Projects → Tasks → Habits → Review flow has current-head browser E2E and exact repository gates after final restack, Figma/Storybook traceability, normal/loading/empty/error/permission/responsive/interaction states, keyboard/focus/reduced-motion/a11y acceptance, authoritative Review read projections, and KO/EN/JA/ZH/VI/ES/DE/FR translation-ledger/font/text-expansion parity. Active browser work is not protected product truth and does not close #209. + +## 4. Calendar integration boundary + +Calendar synchronization and user credential lifecycles use different authority contexts. + +Protected-main foundations are: + +- trusted workspace synchronization context from PR #139; +- workspace-and-user scoped connection persistence from PR #150; +- atomic local revocation from PR #153; +- signed `life-os.calendar-user.v1` workspace-and-user authority from PR #155; +- authenticated local disconnect application/HTTP boundary from PR #157; +- exact returned lookup-evidence validation from PR #176; +- authenticated credential-free connection read lifecycle from PR #189; +- scoped credential materialization port from PR #193; +- authenticated connection creation with secret-first persistence and compensation boundaries from PR #197; +- returned durable create-evidence validation and reverse-order secret compensation from PR #201; +- Calendar-owned AES-256-GCM encrypted self-hosted file credential storage from PR #203, using opaque UUIDv4-backed handles and no plaintext database persistence. + +PR #150 added workspace-and-user scoped connection persistence with opaque secret references. PR #153 added atomic local connection revocation; neither grants provider-side credential revocation authority. + +The current active Calendar stack advances this boundary without changing shipped truth. PR #216 rejects deployment-wide Google and CalDAV credentials from the hosted multi-user runtime until authenticated user-owned connection evidence and scoped secret materialization are composed. Stacked PR #228 adds five-minute Google OAuth authorization-state/PKCE authority with opaque durable state and secret-store-held verifier material, including hostile consumed-row validation before verifier materialization. Both remain Draft active-PR evidence. + +Issue #129 remains **Partial** because protected main still lacks complete hosted Google OAuth callback/token exchange, successful verifier cleanup after exchange, concrete PostgreSQL OAuth-state persistence, refresh fencing, provider-side revoke/delete recovery, calendar discovery/selection, scoped synchronization composition, end-to-end KMS/runtime composition, and retirement of process-global development credentials. PR #203 protects one concrete self-hosted encrypted store; PR #216/#228 do not become protected authority until normal integration. Connection rows store only bounded metadata and opaque secret references; local revocation is not provider credential revocation. + +## 5. Plugin integration boundary + +A plugin manifest expresses untrusted requested intent. Host-owned authority grants only an explicit bounded tenant/user capability subset. + +Protected main includes: + +- explicit grant/replay/conflict/revocation authority from PR #151; +- restart-safe PostgreSQL installation persistence from PR #169; +- opaque secret-reference credential binding and compensation from PR #172; +- exact opaque installation evidence validation from PR #175; +- request-bound one-time operator authority and durable replay protection from PR #191; +- fail-closed authenticated operator HTTP composition from PR #196. + +The active #130 stack is deeper than protected main and remains explicitly non-shipped. PR #205 defines host-owned exact HTTPS delivery-origin authority. PR #235 adds Integration-owned PostgreSQL grant persistence and active-installation fencing. PR #241 hardens credential authority and concurrent revocation admission. PR #242 adds an operator-configured Vault KV v2 secret-store adapter that keeps provider plaintext and Vault credentials out of durable LifeOS metadata. PR #243 composes authenticated Vault operator authority, PR #244 composes the hosted Integration runtime over one service-owned PostgreSQL pool, and PR #245 adds the concrete PostgreSQL/default-entrypoint runtime. A hosted acceptance run on an exact #245 ancestor exercised real Vault KV v2 plus migrated Integration-owned PostgreSQL across installation, credential creation and exact replay, installation-revocation fencing, runtime restart, credential revocation, and idempotent cleanup; that retained ancestor evidence is not current-head merge authority. + +Draft PR #250 is stacked on #245 and composes the existing delivery-origin aggregate/store through exact signed one-time operator grant/read/revoke authority. It deliberately stops before HTTP delivery-origin transport and outbound networking. + +Draft PR #251 is stacked exactly on #250 and retains the signed delivery-origin HTTP grant/read/revoke transport at `5641005c5ed0193b6206848f9c4c807050271ef7` without adding outbound HTTPS. Hosted verifier run `34186936889`, job `101937064591`, first proved the exact #250 parent lacks the routes and then proved the pre-repair percent-encoded raw-route/HMAC canonicalization defect. The minimum transport repair compares the server-observed raw method and URL byte-for-byte with each canonical signed plugin-operator route before decoded parameters can reach durable authority. Focused real-HTTP GREEN passed 2 files / 4 tests, Integration typecheck passed, and the complete Integration suite passed 59 files / 376 tests with 5 files / 17 environment-dependent tests skipped. The successful run ordinary-pushed the retained repair and removed only its purpose-complete verifier. This is active-PR evidence only: it is neither protected-main shipped truth nor independent review/security merge authority. + +Draft PR #252 proceeds in parallel from #245 and establishes Integration-owned durable delivery-attempt admission without depending on mutable #250/#251 transport. It introduces opaque `life-os.plugin-delivery-attempt.v1` work identity plus service-owned PostgreSQL migration/store, exact idempotency scope, bounded retry budget metadata, and a durable INSERT-time fence requiring both the exact active origin grant and active installation under matching workspace/user authority. The table intentionally stores no origin copy, credential, request payload, response body, or network authorization. Exact review-repair proof head `9e6e88e49e14d0b2d247e747df968e2494697666` completed hosted run `34209314512`, job `102006262067`, GREEN on Ubuntu 24.04/PostgreSQL 16 after replaying the timestamp, PostgreSQL/TLS target, IPv6 loopback and table-contract RED ancestors for their intended reasons. A subsequent repository-boundary review found that SQL result envelopes, durable row getters and stored timestamp conversion could throw native dependency detail before the fixed persistence-evidence boundary. Reality RED `b390f37ed651ce2d9cb74d862fbae02641f3a55f` demonstrates that hostile evidence leak; minimum repair `e9a8b212b24b4914069a48a113d118ff4e4c0a56` snapshots and bounds those evidence reads without changing SQL/schema/admission authority. Exact proof head `ec2353a003c15c9f464b5d5d2454d3593427d768`, run `34213508199`, job `102019765395`, completed GREEN with the focused delivery-attempt suite 24/24, Integration typecheck and the complete Integration suite 403 passed / 3 environment-dependent skipped across 65 files. CHANGELOG descendant `fe51a79c1e19cc7fb14e5296f04b3f91b41bf535` also completed exact run `34213937828` / job `102021136219` GREEN. Current #252 head `9ed7847d3106d9418e1ff6e04e45c1c7d84d0b0d` removes only the purpose-complete hostile-SQL verifier. This remains active-PR evidence, not protected shipped truth. + +Draft PR #253 is the direct child of #252 and establishes deterministic Integration-owned delivery-attempt claim/lease authority without adding provider execution or outbound HTTPS. `life-os.plugin-delivery-attempt-claim.v1` returns an opaque UUIDv4 claim token only to the worker while persistence retains only its SHA-256 digest, exact claim start/expiry evidence and the atomic retry-budget transition. Application and repository validation bound a lease to 30–3600 seconds. A durable-bound review found migration `0007_plugin_delivery_attempt_claim_lease.sql` did not yet enforce the same lower/upper bounds: regression `32c02f3ca2ead7617b08d35973d518b25ed4f691`, isolated at reality-RED `400e88163ca9d878806aaee11ef8ca2917ec33dc`, demonstrated real migrated PostgreSQL accepting 29.999-second and 3600.001-second leases. Minimum repair `e25a96d902adc6d6a6422ca4bc114c5bf189f47f` adds the same inclusive 30–3600 second database invariant. Exact proof head `94ef04e854d2480f4520243521bdd9a7550889ea`, run `34217754447`, job `102033443629`, completed GREEN on Ubuntu 24.04/PostgreSQL 16 across retained capability/persistence/durable-bound REDs, formatting/diff hygiene, focused claim/lease acceptance, Integration typecheck and the complete Integration suite. Current #253 exact `62b6fd2c3388f6038641945df34afedd5b5f3b78` removes only the purpose-complete verifier. This is active-PR evidence only and does not move network authority into LifeOS. + +Issue #130 remains **Partial**. Protected main does not yet contain this Vault/PostgreSQL/delivery-origin/delivery-attempt active stack. #252 establishes durable admission and #253 adds deterministic finite worker claim/lease; bounded retry/backoff, append-only sanitized outcomes, dead-letter/pause/resume, per-attempt revocation fencing, operator-visible status and restart/recovery remain successor work. No active LifeOS PR supplies complete host-authorized outbound HTTPS. The network boundary still requires immutable released/versioned canonical egress authority for connect-time DNS/IP and rebinding controls, redirect/proxy policy, bounded response/time behavior and non-leaking failures. Durable origin identity and durable delivery identity are not network authorization. Manifests and stored installations never self-authorize network capabilities. + +## 6. AI proposal boundary + +AI may generate, validate, persist, and retrieve inert proposal evidence and append explicit accept/reject decisions. It has no generic Planning mutation repository or command bus. Deterministic schema, authorization, quality, and release gates remain authoritative when model providers are unavailable or disagree. + +PR #199 is **Implemented on active PR** for an AI-owned data-rights contributor. Its active migrations and application code are not protected-main truth. + +## 7. Privacy authority + +Privacy owns purpose-bound sensitive-access decisions, bounded grants, and audit events. Sensitive access binds actor, workspace, purpose, resource/resource class, lifetime, and audit evidence. Blanket masking is not the authorization model. + +Whole-right orchestration remains Identity-owned. Every bounded service remains authoritative for its own export and erasure contribution and cannot claim whole-workspace completion independently. + +## 8. External identity, secret references, and grants + +ADR 0011 is authoritative: + +- LifeOS integration identities are internal UUIDv4 values; +- external provider/plugin identifiers remain bounded metadata; +- credential material is separate from metadata and referenced through opaque least-authority handles; +- manifests never self-authorize capabilities; +- revocation, replay, conflict, compensation, and recovery fail closed; +- owning services retain migrations, repositories, and API authority. + +The protected Calendar and Plugin Integration lines above are executable evidence of this decision. Their active successors are evidence only until integration and do not close their parent buyer gaps. + +## 9. Model-assisted development and automation + +ADR 0012 is authoritative. A strong single-model route is measured before deeper orchestration. Workflow stage, reasoning effort, decomposition, recursion depth, role-specific reasoning effort, worker/model selection, verifier topology, and access/communication topology are explicit experimental dimensions only when supported by the exact reviewed dependency. + +Protected main includes PR #200's exact pinned OpenCode executable bootstrap hardening; that historical line does not authorize direct provider selection as the target model-routing architecture. Current active PR #208 routes scheduled model-assisted work through contextual-orchestrator and virtual `orchestrator/free`, while preserving exact OpenCode identity verification. It remains Draft because the required contextual-orchestrator authentication/bootstrap contract and immutable reviewed upstream release are not yet available to LifeOS. LifeOS does not copy mutable upstream source or convert provider credentials into direct model-selection authority. + +Model execution has no product-data authority beyond bounded inputs and no independent review, branch-protection, merge, or release authority. Retained evidence excludes credentials, raw prompts/responses, and hidden reasoning. Unsupported gateway capability fails closed and is repaired in the canonical owner rather than bypassed in LifeOS. + +## 10. Verification identity and merge safety + +ADR 0010 keeps these identities separate: -### Compute-allocation rules +- `source_head_sha`; +- `pr_base_snapshot_sha`; +- independently resolved `live_base_tip_sha`; +- `integration_tree_sha` or separately classified synthetic merge identity; +- `workflow_checkout_sha`; +- `protected_main_sha`; +- `release_source_sha`. -- A strong single-model route is always measured first. -- Reasoning effort, workflow stage, decomposition, recursion depth, role, and access topology are explicit test cells rather than hidden defaults. -- Deeper orchestration is justified by measured fixture-level quality or heterogeneous capability coverage, not by agent count. -- Latency and token use are recorded for capacity review but are not the optimization objective. -- Unsupported capabilities remain explicit unavailable cells; tests never fabricate an ablation result. +PR #154 is **Implemented on protected main** for exact-source jobs, independently reconstructed live-base compatibility, and explicit AppGuardrail source attribution. Issue #132 remains **Partial** only for central reusable SAST/Security checkout and evidence taxonomy. A green status for one identity never transfers to another. -The hourly live workflow pins `ContextualWisdomLab/contextual-orchestrator` to an exact reviewed commit, installs hash-locked dependencies, seeds only `NVIDIA_NIM_API_KEY` through the encrypted credential bootstrap, executes the pinned checkout on loopback, and retains no prompts, responses, hidden reasoning, credentials, or raw traces. +Old PR #147 is **Superseded** as verification authority; the protected PR #154 identity model above is the current canonical line. -## 4. Mathematical and psychometric modules +PR #204 is **Implemented on active PR** for a read-only detector that binds the complete Actions workflow registry to one exact protected-default-branch Git tree and reports active orphan workflow identities. It does not authorize workflow-state mutation and is not passing merge evidence until its exact-head required checks pass. -LifeOS currently contains no psychometric computation service. Any future mathematical or psychometric module must follow these additional decisions before it can be treated as production-capable: +PR #190 protects exact request-bound integration event authority. PR #191 and PR #196 protect the plugin operator request/replay/HTTP line. These product authorities are independent from merge authority. -- the numerical kernel is implemented in Rust; -- CPU parallelism minimizes context switching and GPU acceleration is available behind a deterministic capability boundary; -- true-parameter recovery, bias, coverage, and RMSE are tested on realistic simulations; -- multilevel and multiple-membership structures are modeled to avoid atomistic inference; -- temporal change, repeated measurement, drift, and state evolution are explicit model dimensions; -- numerical reproducibility, precision, seed control, convergence diagnostics, and fallback behavior are documented; -- statistical assumptions and estimands are cited in APA 7 style. +## 11. Release and recovery boundary -## 5. Automation and merge safety +A release is cut from one exact integrated protected head only after applicable CI, security, review, coverage/docstrings, packaging, SBOM/provenance, reproducibility, compatibility, migrations/rollback, backup/restore/recovery, accessibility/localization, and operational acceptance pass together. No feature PR, documentation PR, or model judgment is release evidence by itself. -Pull requests follow one loop: inspect every review and check, fix root causes, rerun the exact head, resolve addressed threads, and merge only after all required evidence passes. Administrative bypasses are prohibited. +Issue #210 remains **Partial**. Draft PR #217 adds a machine-readable exact release-evidence index with fail-closed structural validation, including artifact/checksum/provenance/signature coverage and nightly identity constraints. Stacked Draft #236 adds detached Ed25519 signature verification and a bounded operator CLI. Neither publishes an immutable release, distributes trust roots, completes key rotation/revocation/custody, or transfers ancestor checks into current release authority. -Scheduled model-assisted automation uses `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is prohibited. Existing dedicated review-agent credentials are not repurposed. Deterministic audit and merge eligibility remain independently enforceable even when a model provider is unavailable. +## 12. Mathematical and psychometric future constraint -The pinned OpenCode configuration disables project-local overrides, explicitly reloads reviewed repository instructions, enables only NVIDIA, registers and whitelists one model label independently of the bundled catalog, pins primary and small-model work to it, and checks that effective catalog offline before its credential bridge starts; the bridge exposes no provider-wide discovery route. Model-generated source verification runs without Docker authority. A later trusted operation parses the accepted candidate's explicitly selected Compose file, while credential-free pull-request CI starts digest-pinned images, proves PostgreSQL query execution and NATS JetStream availability, binds published ports to loopback, and tears down unconditionally. +LifeOS currently has no psychometric computation service. Future product-owned mathematical or psychometric kernels are Rust-first, use low-context-switch CPU multithreading, add parity-verified GPU paths where material, and prove parameter recovery, uncertainty/coverage, convergence, reproducibility, multilevel/multiple-membership structure, and temporal/repeated-measurement semantics before product claims. -## 6. Documentation hierarchy +## 13. Canonical documentation graph -1. `AGENTS.md` — repository-wide agent and merge rules. -2. `ARCHITECTURE.md` — durable architectural decisions and diagrams. -3. `CLAUDE.md` — Claude-compatible operational handoff that defers to `AGENTS.md`. -4. `docs/superpowers/specs/` — approved feature designs. -5. `docs/superpowers/plans/` — implementation sequences. -6. `docs/operations/` — operator runbooks and SLOs. -7. `docs/research/` — standards and research rationale with APA 7 references. -8. `CHANGELOG.md` — user-visible unreleased and released changes. +The canonical line comprises `AGENTS.md`, this root Architecture, PRD, TRD, ADR index/details, UML/C4 views, logical Data Model, API/event/schema contracts, Security, Threat Model, Privacy/Data Lifecycle, Test Strategy, Operability/recovery, Release/Migration/Rollback/provenance, Standards/Research, Traceability, Documentation Assessment, README, CLAUDE, and CHANGELOG. -A behavior or boundary change is incomplete until the relevant level is updated and executable tests prove the claim. +Canonical maturity uses only `Implemented on protected main`, `Implemented on active PR`, `Partial`, `Accepted architecture`, `Planned`, `Research only`, `Superseded`, and `Out of scope`. File presence and old green checks do not prove semantic currentness. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c4cc4bd7e..7efdc2965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,9 @@ All notable changes to LifeOS are documented in this file. ## Unreleased -### Changed - -- Production contextual-orchestrator proposal requests now explicitly use adaptive `auto` mode and avoid provider-native structured-output passthrough, allowing the orchestration plane to meet the quality requirement and then minimize known cost while LifeOS retains strict fail-closed proposal validation. - ### Added +- A canonical architecture/governance decision for model-assisted test-time compute and repository development: ADR 0012 requires a strong single-route baseline, explicit stage/decomposition/recursion/role-effort/access-topology evidence, comparable-budget justification for deeper orchestration, `NVIDIA_NIM_API_KEY` through approved OpenCode/contextual-orchestrator boundaries, and strict separation of model execution from deterministic review, merge, and release authority. This documents and reconciles existing protected-main governance/live-conformance behavior rather than claiming a new shipped product capability. - Durable PostgreSQL plugin-installation authority with opaque UUIDv4 installation/workspace/installer identity, exact manifest digests, normalized explicit grants, bounded conflict replay, and atomic revocation evidence in the service-owned `plugin_integration` schema. - An authenticated calendar-connection disconnect application and optional hosted HTTP composition boundary that derives workspace and requesting-user authority only from the signed `life-os.calendar-user.v1` context and returns credential-free local revocation evidence. - A durable PostgreSQL data-rights request ledger with workspace-scoped idempotency, immutable request and terminal receipt digests, one-way completion state, and real integration evidence that erasure receipts survive removal of the source workspace and user. diff --git a/CLAUDE.md b/CLAUDE.md index 06439b77a..3ff834abe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # Claude operating contract for LifeOS -`AGENTS.md` is the canonical repository-wide instruction file. This document maps that contract into a concise execution order for Claude-compatible agents and must not override `AGENTS.md`, `ARCHITECTURE.md`, branch protection, or security policy. +`AGENTS.md` is the canonical repository-wide instruction file. `ARCHITECTURE.md` is the durable system-boundary source of truth. The canonical product documentation graph is indexed from `README.md` and includes PRD, TRD, ADRs, Data Model, UML, API contracts, threat/privacy/test/operability/release/standards/traceability views. This document maps those authorities into a concise execution order for Claude-compatible agents and must not override live repository policy. ## Execution order @@ -10,38 +10,48 @@ 4. Make the smallest complete correction, including tests and documentation. 5. Re-run the exact pull-request head and resolve only threads whose finding is actually addressed. 6. Merge only when required checks pass, no actionable findings remain, and the repository's merge policy accepts the exact head. -7. Continue with the highest-impact buyer-visible gap after the pull-request queue is empty. +7. Non-force restack dependent branches after an accepted parent change; never discard concurrent valid delta. +8. Continue with the highest-impact buyer-visible gap after the pull-request queue is empty or a current lane is independently blocked. -Routine progress narration is not a substitute for repository evidence. Record decisions in code, tests, ADRs, specifications, plans, runbooks, issues, and pull-request descriptions. +Routine progress narration is not a substitute for repository evidence. Record durable decisions in canonical docs/ADRs, code, tests, runbooks, issues, and pull-request evidence with truthful protected-main/active-PR/planned maturity. ## Non-negotiable boundaries - Never use `COPILOT_GITHUB_TOKEN`. -- Scheduled model-assisted work uses `NVIDIA_NIM_API_KEY` through the approved OpenCode or contextual-orchestrator boundary. +- All model capability is consumed through an immutable reviewed `contextual-orchestrator` API/client/schema. GitHub Actions model-backed work uses only virtual `orchestrator/free` plus the gateway authentication token; provider/model/group selection and provider credentials remain contextual-orchestrator owner authority. +- `BYTEZ`, `NVIDIA_NIM`/`NVIDIA_NIM_SUB`, `OPENROUTER`, `OPENAI`, embedding, responses/completions, audio/video/image and other model/provider credential discovery stay in contextual-orchestrator. LifeOS does not copy mutable owner source or add a direct-provider fallback when a capability is missing. - Do not alter or repurpose the credential scheme of existing review agents. - Never forward browser cookies, provider credentials, hidden reasoning, raw prompts, raw model responses, or stack traces into retained artifacts. -- Internal identifiers are UUIDv4 strings; numeric external identifiers are mapped through an explicit provider-identity boundary. +- Internal identifiers are UUIDv4 strings; numeric/external provider identifiers are mapped through explicit provider-identity boundaries. - Database objects use multiword `snake_case` names unless an external protocol mandates a different spelling. -- Services do not read or mutate another service's database tables. +- Services own their persistence, migrations, credentials, transaction boundaries and recovery. They do not read or mutate another service's database tables. +- Browser-local state is not durable until accepted by the owning service. - AI proposals remain inert until a separately authorized user-confirmed execution capability exists. -- Mathematical and psychometric numerical kernels require Rust, deterministic CPU/GPU execution boundaries, realistic parameter-recovery tests, multilevel or multiple-membership structure, and temporal modeling where applicable. +- Sensitive access is tenant/resource/purpose/lifetime/audit bound rather than relying on blanket masking. +- Mathematical, psychometric, EDA, data-science, performance and security hot kernels are Rust-first; numerical claims require deterministic CPU/GPU parity as applicable, realistic parameter-recovery/error evidence, multilevel or multiple-membership structure, and temporal modeling where applicable. ## LLM orchestration decisions -Use a strong single-model route as the mandatory baseline. Allocate additional test-time compute only through explicit profiles that identify reasoning effort, workflow stages, role assignment, decomposition, recursive depth, and access topology. Use measured proposal validity, grounding, utility, and prompt-injection resistance to justify deeper orchestration. Do not optimize this decision for latency alone. +Use a strong single-route baseline before deeper orchestration. Allocate additional test-time compute only through explicit profiles that identify reasoning effort, workflow stages, role assignment, decomposition, recursive depth, worker/model choice where the owner exposes it, verifier topology, and access/communication topology. Use measured proposal validity, grounding, utility, and prompt-injection resistance to justify deeper orchestration. Fugu/Conductor/TRINITY-style experiments are evidence profiles, not authority. -Live model tests may use `NVIDIA_NIM_API_KEY`. Deterministic pull-request checks must remain meaningful when that secret or the provider is unavailable. Provider failures produce sanitized unavailable evidence, never fabricated scores. +LifeOS does not seed provider credentials directly. The target model-assisted line is the released contextual-orchestrator boundary with virtual `orchestrator/free`. If gateway authentication, a required capability, or the released owner contract is unavailable, fail closed and repair/release the canonical owner before bumping the LifeOS consumer. Model timeout defaults remain owner-contract-driven; user cancellation, provider termination, administrative timeout, stream/tool-call lifecycle and reasoning completion are distinct evidence classes. + +Deterministic pull-request checks remain meaningful when model capability is unavailable. Provider/gateway failures produce sanitized unavailable evidence, never fabricated scores or a direct-provider bypass. ## Verification standard -- Production declarations have explanatory docstrings. -- Changed production code maintains 100% statement, branch, function, and line coverage where the package enforces those gates. -- Tests model realistic domain outcomes, not only mocked implementation calls. +- Production declarations have explanatory docstrings/rustdoc. +- Owned production code maintains the repository's exact configured statement, branch, function and line/edge coverage gates; a green suite alone is not a 100% coverage claim. +- Tests model realistic domain outcomes, including PostgreSQL/browser/concurrency/security behavior where applicable. Synthetic data is unit-test evidence, not production acceptance. +- Buyer-path web/API performance is measured end-to-end on applicable real paths; an asserted p95 target is not accepted without the actual denominator and profiling evidence. +- Material UI preserves reusable component/page composition, product-design/Figma/Storybook traceability, normal/loading/empty/error/permission/responsive/interaction states, keyboard/a11y evidence and KO/EN/JA/ZH/VI/ES/DE/FR locale behavior where the changed surface requires it. - Standards and research claims are documented with APA 7 references and publication status is distinguished from drafts or preprints. -- `CHANGELOG.md` records buyer-visible behavior. -- `ARCHITECTURE.md` and relevant feature ADR/specification files record boundary changes. +- Canonical status fields use the exact repository vocabulary and never mix PR/issue qualifiers into the status value. +- `CHANGELOG.md` records buyer-visible behavior and meaningful security/operational contract changes. +- PRD/TRD/Architecture/ADR/UML/Data Model/API/Security/Privacy/Test/Operability/Release/Traceability views are reconciled when their boundary changes. +- Exact source, PR-base snapshot, live base, integration/synthetic tree, workflow checkout, protected main and release-source identities are never conflated. - Release tags and versions are created only after the repository proves release readiness; unreleased work stays under `Unreleased`. ## Safe escalation -Escalate only for a decision or permission that cannot be resolved from repository policy, tests, standards, or available credentials. Waiting for checks or reviews is not itself an escalation condition; continue independent analysis, documentation, or the next non-conflicting planned task while preserving merge safety. +Escalate only for a decision or permission that cannot be resolved from repository policy, tests, standards, or available credentials. Waiting for checks or reviews is not itself an escalation condition; continue independent analysis, documentation, testing, restacking, owner-path repair, or the next non-conflicting planned task while preserving merge safety. diff --git a/README.md b/README.md index 59412fabb..2abbc2442 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ LifeOS connects everyday action to longer-term direction. It is designed as a mu ## Status -LifeOS is in active foundation development. The current `main` branch contains the monorepo, gateway, bounded services, shared contracts, responsive web shell, PostgreSQL persistence, NATS JetStream configuration, security gates, and commercial-readiness evidence loop. Interfaces and migrations may still change before the first stable release. +LifeOS is in active foundation development. The current protected `main` branch contains the monorepo, gateway, bounded services, shared contracts, responsive web shell, service-owned PostgreSQL persistence, NATS JetStream configuration, security gates, and commercial-readiness evidence loop. Interfaces and migrations may still change before the first stable release. Active pull requests are development evidence, not shipped capability. ## Architecture @@ -14,13 +14,14 @@ LifeOS is in active foundation development. The current `main` branch contains t Web / PWA | API Gateway / BFF - |---------------------------------------------| -Identity Planning Habit Review - | | | | -PostgreSQL schemas / databases + NATS JetStream events + |------------------------------------------------------------------| +Identity Planning Habit Review Calendar Notification AI Privacy Plugin + | | | | | | | | | + service-owned PostgreSQL + versioned HTTP/event contracts + NATS JetStream where required ``` -The MVP deliberately keeps goals, projects, milestones, and tasks in one Planning bounded context. Services own their persistence boundaries; direct cross-service table access is prohibited. +The product keeps goals, projects, milestones, and tasks in the Planning bounded context. Each service owns its persistence, migrations, credentials, transaction boundaries, runtime composition and recovery. Direct cross-service table access is prohibited. Browser-local state is not durable until the owning service accepts it. ## Repository layout @@ -75,21 +76,35 @@ Metrics endpoints contain operational data. Production ingress must restrict the ## Authentication -Google and GitHub OAuth are the required login providers. Provider credentials are supplied through environment variables and must never be committed. Deployment operators are responsible for provider registration, redirect URI policy, secret rotation, and production access controls. +Google and GitHub OAuth are the required login providers. Provider registration and bootstrap credentials are deployment configuration and must never be committed. Deployment operators are responsible for redirect URI policy, secret rotation, and production access controls. Browser-supplied workspace or user identifiers never replace authenticated server authority. ## Calendar synchronization -The calendar integration service supports explicit `caldav` and `google` provider modes. Set `CALENDAR_PROVIDER` and the matching variables in `.env.example` before starting the service. +The calendar integration service supports explicit `caldav` and `google` provider modes. CalDAV writes use deterministic resource names, `If-None-Match: *` for creation, and strong `If-Match` ETags for updates. Google Calendar writes use a deterministic API event identifier to prevent duplicate creation and the same strong-ETag precondition for updates. Neither adapter exposes delete, move, or copy operations through the LifeOS provider contract. -CalDAV writes use deterministic resource names, `If-None-Match: *` for creation, and strong `If-Match` ETags for updates. Google Calendar writes use a deterministic API event identifier to prevent duplicate creation and the same strong-ETag precondition for updates. Neither adapter exposes delete, move, or copy operations through the LifeOS provider contract. +Protected main derives Calendar workspace/user authority from signed server context and stores only bounded connection metadata plus opaque secret references. It includes local disconnect, credential-free reads, scoped materialization, secret-first create/compensation, and a Calendar-owned encrypted self-hosted credential-store profile. -`GOOGLE_CALENDAR_ACCESS_TOKEN` is an operator-supplied runtime secret for this bounded adapter slice. Per-user OAuth credential storage, token refresh, revocation, calendar discovery, and encrypted persistence remain deferred and must be implemented before a multi-user hosted deployment enables Google Calendar synchronization. +Hosted multi-user provider lifecycle is still incomplete under issue #129. Active work rejects deployment-wide Google/CalDAV credentials as end-user authority and adds bounded Google OAuth state/PKCE authority, but callback/token exchange, concrete PostgreSQL OAuth-state runtime, refresh fencing, provider revoke/delete recovery, discovery/selection and scoped synchronization remain unshipped until normal integration and completion. -## Plugin contract +## Plugin integration -The `@life-os/plugin-sdk` package defines strict versioned manifests, tenant-scoped CloudEvents 1.0 structured JSON envelopes, deterministic canonical serialization, and HMAC-SHA256 delivery-proof helpers. The integration service exposes contract discovery, manifest validation, and event preparation only. +The `@life-os/plugin-sdk` package defines strict versioned manifests, tenant-scoped CloudEvents 1.0 structured JSON envelopes, deterministic canonical serialization, and bounded signing helpers. A manifest expresses requested intent only; it never self-authorizes capabilities, secrets, database access or network destinations. -This slice deliberately has no plugin installation, secret persistence, outbound webhook delivery, inbound commands, or direct database access. Those require separately reviewed least-privilege authorization, durable audit, and SSRF-safe delivery boundaries. +Protected main contains explicit installation grants, restart-safe Integration-owned PostgreSQL installation persistence, opaque credential-binding references, exact returned-evidence validation, one-time signed operator authority and fail-closed operator HTTP composition. Active issue #130 work adds host-owned normalized HTTPS delivery-origin grants, Vault KV v2 secret storage, Integration-owned PostgreSQL runtime composition and signed delivery-origin application authority. These active slices remain non-shipped and deliberately stop before complete outbound HTTPS. + +A stored delivery origin is not connect-time network authorization. Complete plugin delivery still requires immutable released/versioned canonical egress authority for DNS/IP/rebinding, redirect/proxy and bounded response/time enforcement, plus durable delivery attempts/outcomes, retry/dead-letter, revocation fencing and operator recovery. + +## First-party product journey + +Protected main contains durable server-side Planning/Habit/Review foundations and real authenticated Today composition. Issue #209 tracks the complete first-party Goals → Projects → Tasks → Habits → Review journey. Active stacked work begins with an authenticated Goal BFF and includes durable Goals and Weekly Review workspaces, but remains Draft/non-shipped. + +Commercial completion requires current-head browser E2E, Figma/Storybook traceability, normal/loading/empty/error/permission/responsive/interaction states, keyboard/focus/reduced-motion/accessibility acceptance, authoritative Review projections, and KO/EN/JA/ZH/VI/ES/DE/FR translation-ledger/font/text-expansion parity. + +## Model-assisted development + +LifeOS treats model output as untrusted evidence and keeps review, merge and release authority deterministic. The target automation boundary is an immutable reviewed `contextual-orchestrator` API/client with virtual `orchestrator/free`. Provider credentials, provider/model/group selection and multimodal capability discovery belong to contextual-orchestrator rather than LifeOS workflows or product code. + +Protected OpenCode bootstrap hardening does not authorize direct-provider routing. Active consumer work remains fail-closed until the contextual-orchestrator authentication/bootstrap contract is repaired and published as an immutable reviewed release. LifeOS does not copy mutable owner source or fall back to direct provider selection. ## Backup and recovery @@ -103,15 +118,42 @@ This logical-dump tier is not point-in-time recovery and does not schedule, encr The manual deployment workflow accepts only digest-pinned images and an exact HTTPS web origin, uses one shared renderer, optionally applies forward-only migrations, runs through the protected GitHub `production` environment, and performs server-side dry-run and diff. Before applying, it captures whether each Deployment exists and its current revision. A failed apply or rollout must either verify rollback to that captured revision or verify deletion of a first-time Deployment; a separate failure is reported when workload-state recovery itself fails. Namespace policy, completed migrations, external infrastructure, and other non-Deployment resources are not automatically reversed. The reference does not provision a cluster, database, NATS, ingress, TLS, DNS, image pipeline, or secret manager. Operators must follow the [production deployment runbook](docs/operations/production-deployment.md) and preserve those explicit ownership boundaries. +## Release status + +Issue #210 tracks immutable commercial release readiness. Active Draft release-evidence work validates structural artifact/checksum/provenance/signature evidence and detached Ed25519 signatures, but no active PR is itself a release. A stable release requires one unchanged protected source bound to version, CHANGELOG, tag, package/image, SBOM, provenance/signatures, trust-root/key lifecycle, reproducibility, migration/rollback/recovery, installed buyer-path verification and all required CI/security/review/coverage/docstring/accessibility/localization evidence. + ## Privacy and deployment responsibility This is a public repository. It contains synthetic examples only. Personal goals, health information, relationship data, credentials, access tokens, private prompts, customer data, and production exports must not be committed. The upstream project does not operate every LifeOS deployment. A self-hosting organization controls its deployment data and must establish its own privacy notice, retention policy, security controls, subprocessors, and legal basis. See the [upstream privacy notice](docs/legal/privacy.md) and [upstream project terms](docs/legal/terms.md) for the upstream project boundary. -## Documentation +## Canonical product documentation + +The following graph is the whole-product source of truth alongside protected-main code and root `AGENTS.md` / `ARCHITECTURE.md`: + +- [Product requirements](docs/PRD.md) +- [Technical requirements](docs/TRD.md) +- [Architecture decisions](ARCHITECTURE.md) +- [ADR index](docs/adr/README.md) +- [Logical data model / ERD](docs/DATA_MODEL.md) +- [UML and interaction views](docs/UML.md) +- [API and event contracts](docs/API_CONTRACTS.md) +- [Threat model](docs/THREAT_MODEL.md) +- [Privacy and data lifecycle](docs/PRIVACY_DATA_LIFECYCLE.md) +- [Test strategy](docs/TEST_STRATEGY.md) +- [Operability](docs/OPERABILITY.md) +- [Release, migration, and rollback](docs/RELEASE_AND_MIGRATION.md) +- [Standards and research traceability](docs/STANDARDS_TRACEABILITY.md) +- [Requirements/evidence traceability](docs/TRACEABILITY.md) +- [Documentation fitness assessment](docs/DOCUMENTATION_ASSESSMENT.md) +- [Vulnerability reporting](SECURITY.md) + +Scoped feature designs, plans and runbooks remain useful evidence but do not override this code-current canonical graph. + +## Additional documentation -- Product and architecture design: `docs/superpowers/specs/2026-08-02-life-os-design.md` +- Product and architecture design history: `docs/superpowers/specs/2026-08-02-life-os-design.md` - Foundation implementation plan: `docs/superpowers/plans/2026-08-02-life-os-foundation.md` - Gateway service-level objectives: `docs/operations/service-level-objectives.md` - Planning-service service-level objectives: `docs/operations/planning-service-level-objectives.md` @@ -120,7 +162,6 @@ The upstream project does not operate every LifeOS deployment. A self-hosting or - [Production Kubernetes deployment runbook](docs/operations/production-deployment.md) - [Upstream privacy notice](docs/legal/privacy.md) - [Upstream project terms](docs/legal/terms.md) -- [Vulnerability reporting](SECURITY.md) ## Contributing diff --git a/apps/ai-service/src/contextual-orchestrator-proposal-model.test.ts b/apps/ai-service/src/contextual-orchestrator-proposal-model.test.ts index a8e67c395..fad6f2365 100644 --- a/apps/ai-service/src/contextual-orchestrator-proposal-model.test.ts +++ b/apps/ai-service/src/contextual-orchestrator-proposal-model.test.ts @@ -129,7 +129,7 @@ describe('contextual orchestrator configuration', () => { }); describe('ContextualOrchestratorProposalModel', () => { - it('sends a no-tools adaptive request and returns untrusted output', async () => { + it('sends a no-tools schema-constrained request and returns untrusted output', async () => { const draft = { summary: 'Prioritize launch readiness.', rationale: ['The checklist is the active critical path.'], @@ -166,8 +166,6 @@ describe('ContextualOrchestratorProposalModel', () => { const body = JSON.parse(String(init?.body)) as Record; expect(body.model).toBe('contextual-orchestrator'); - expect(body.orchestration_mode).toBe('auto'); - expect(body.include_orchestration_trace).toBe(false); expect(body.tools).toBeUndefined(); expect(body.stream).toBe(false); expect(body.temperature).toBe(0); @@ -181,7 +179,24 @@ describe('ContextualOrchestratorProposalModel', () => { content: JSON.stringify(request), }); - expect(body.response_format).toBeUndefined(); + const responseFormat = body.response_format as Record; + expect(responseFormat.type).toBe('json_schema'); + const jsonSchema = responseFormat.json_schema as Record; + expect(jsonSchema.name).toBe('life_os_inert_proposal_draft'); + expect(jsonSchema.strict).toBe(true); + const schema = jsonSchema.schema as Record; + expect(schema.$schema).toBe('https://json-schema.org/draft/2020-12/schema'); + expect(schema.additionalProperties).toBe(false); + expect(schema.required).toEqual(['summary', 'rationale', 'operations']); + const properties = schema.properties as Record; + const operations = properties.operations as Record; + const operationItem = operations.items as Record; + expect(operationItem.oneOf).toHaveLength(3); + for (const variant of operationItem.oneOf as Array< + Record + >) { + expect(variant.additionalProperties).toBe(false); + } }); it('accepts one valid completion at the exact response-byte limit', async () => { diff --git a/apps/ai-service/src/contextual-orchestrator-proposal-model.ts b/apps/ai-service/src/contextual-orchestrator-proposal-model.ts index cae9dc3d6..0c9d830d3 100644 --- a/apps/ai-service/src/contextual-orchestrator-proposal-model.ts +++ b/apps/ai-service/src/contextual-orchestrator-proposal-model.ts @@ -208,21 +208,10 @@ export const CONTEXTUAL_ORCHESTRATOR_PROPOSAL_SCHEMA = deepFreeze({ }, }); -/** - * Builds one immutable no-tools adaptive orchestration request. - * - * `auto` delegates model/provider choice, workflow depth, verification, - * fallback, and known-price optimization to contextual-orchestrator. Trace - * disclosure stays private by default, and provider-native `response_format` - * is deliberately omitted because the gateway proxies that feature to one - * worker instead of applying adaptive orchestration. LifeOS still validates - * every returned proposal through its strict local domain contract. - */ +/** Builds one immutable no-tools OpenAI-compatible structured-output request. */ function requestBody(input: ProposalRequest): string { return JSON.stringify({ model: 'contextual-orchestrator', - orchestration_mode: 'auto', - include_orchestration_trace: false, temperature: 0, stream: false, messages: [ @@ -232,6 +221,14 @@ function requestBody(input: ProposalRequest): string { }, { role: 'user', content: JSON.stringify(input) }, ], + response_format: { + type: 'json_schema', + json_schema: { + name: 'life_os_inert_proposal_draft', + strict: true, + schema: CONTEXTUAL_ORCHESTRATOR_PROPOSAL_SCHEMA, + }, + }, }); } diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md new file mode 100644 index 000000000..a5ea395f6 --- /dev/null +++ b/docs/API_CONTRACTS.md @@ -0,0 +1,179 @@ +# LifeOS API, Event, and Schema Contracts + +**Status:** Implemented on active PR + +This registry summarizes repository-level contract invariants. Concrete route, event, and migration schemas remain owned by implementing services and tests. + +## Common rules + +- Internal/public product IDs are opaque UUIDv4. +- Ownership comes from authenticated/signed context, never arbitrary browser fields. +- Signed service context binds version, exact actor/workspace, method, path, issuance, and one-time evidence where replay matters. +- Replayable or stale-sensitive mutations use idempotency, fencing, and/or strong preconditions. +- Public failures are bounded, non-reflective, and credential-free. +- Provider responses, stored JSON, plugin metadata, and model output remain untrusted until validated. +- Cross-service contracts never grant direct database authority. +- Unknown versions, malformed evidence, corrupt rows, and unavailable authority fail closed. +- Verification evidence is valid only for the exact tree inspected. +- Active-PR contracts are not shipped API authority until normal protected integration. + +## Contract registry + +| Contract | Owner | Status | Notes | +| --- | --- | --- | --- | +| OAuth login/callback/session | Identity | Implemented on protected main | Google/GitHub, bounded state/redirect/session/auth-age lifecycle | +| Planning Goal/Project/Task | Planning | Implemented on protected main | signed/request-bound workspace authority through PR #168 and PR #188 | +| Durable Today aggregate | Planning | Implemented on protected main | PR #127; preconditions/idempotency/conflicts | +| Authenticated Today composition | Gateway + Planning + Habit | Implemented on protected main | PR #186 and PR #187; Issue #163 completed | +| Habit recurrence/completion | Habit | Implemented on protected main | signed workspace authority through PR #173 | +| Review completion/projection | Review | Implemented on protected main | request-bound signed authority through PR #185 | +| Integration event context | Integration | Implemented on protected main | exact request binding through PR #190 | +| Calendar sync request | Calendar Integration | Implemented on protected main | PR #139 signed workspace context | +| Calendar connection metadata | Calendar Integration | Implemented on protected main | PR #150 workspace+user scope, opaque secret references | +| Calendar local revocation | Calendar Integration | Implemented on protected main | PR #153 and authenticated disconnect PR #157 | +| Calendar connection read | Calendar Integration | Implemented on protected main | exact lookup PR #176 and authenticated read PR #189 | +| Calendar credential materialization | Calendar Integration | Implemented on protected main | PR #193; validated handles only | +| Calendar connection creation | Calendar Integration | Implemented on protected main | PR #197; authenticated secret-first persistence/compensation | +| Calendar create-evidence compensation hardening | Calendar Integration | Implemented on protected main | PR #201 | +| Calendar encrypted self-hosted secret storage | Calendar Integration | Implemented on protected main | PR #203; Calendar-owned AES-256-GCM file-store profile | +| Hosted Calendar credential admission | Calendar Integration | Implemented on active PR | PR #216 rejects deployment-wide Google/CalDAV credentials as user authority | +| Google OAuth authorization state / PKCE | Calendar Integration | Implemented on active PR | PR #228; bounded one-time state and secret-held verifier; callback/token exchange not included | +| Complete hosted calendar credential lifecycle | Calendar Integration | Partial | issue #129 | +| Reminder scheduling/delivery | Notification | Implemented on protected main | bounded claims/retries/outcomes | +| AI proposal/evidence/decision | AI Proposal | Implemented on protected main | inert proposal + explicit decision | +| Purpose-bound sensitive access | Privacy | Implemented on protected main | actor/workspace/resource/purpose/lifetime bound | +| Data-rights request ledger/status | Identity | Implemented on protected main | durable request/receipt and bounded non-cacheable projection | +| Tenant export integrity manifest | Identity + contributors | Implemented on protected main | deterministic sections/whole digest | +| Contributor lifecycle v1 | Contracts | Implemented on protected main | PR #159 | +| Planning data-rights contributor | Planning | Implemented on protected main | PR #179 and authenticated transport PR #194 | +| Habit data-rights contributor | Habit | Implemented on protected main | PR #184 and authenticated transport PR #192 | +| Review data-rights contributor | Review | Implemented on protected main | PR #195 | +| Notification data-rights contributor | Notification | Implemented on active PR | PR #198 | +| AI data-rights contributor | AI Proposal | Implemented on active PR | PR #199 | +| Complete cross-domain export/erasure | Identity + every owner | Partial | issue #55 | +| Plugin manifest/event validation | Integration | Implemented on protected main | versioned SDK/validation | +| Plugin installation grants | Integration | Implemented on protected main | PR #151 | +| Durable plugin installation | Integration | Implemented on protected main | PR #169 and exact evidence PR #175 | +| Plugin credential binding | Integration | Implemented on protected main | PR #172; opaque secret reference only | +| Plugin operator request authority | Integration | Implemented on protected main | PR #191 one-time request/replay evidence | +| Plugin operator HTTP composition | Integration | Implemented on protected main | PR #196 fail-closed composition | +| Plugin delivery-origin aggregate/grant | Integration | Implemented on active PR | PR #205 plus active PostgreSQL/runtime descendants; durable exact HTTPS origin is not network authorization | +| Plugin Vault secret-store/operator runtime | Integration | Implemented on active PR | PR #242/#243/#244/#245; provider plaintext remains Vault-owned and LifeOS rows retain opaque references | +| Signed plugin delivery-origin operator application | Integration | Implemented on active PR | PR #250 exact signed grant/read/revoke application authority; no public HTTP delivery-origin route yet | +| Complete plugin secret/outbound runtime | Integration | Partial | issue #130 | +| First-party authenticated Goal BFF | Web/Gateway + Planning | Implemented on active PR | PR #214; browser credential is not forwarded and workspace/request authority stays server-side | +| Durable Goals workspace | Web/PWA | Implemented on active PR | PR #229; validated server-authoritative evidence only | +| Durable Weekly Review workspace | Web/PWA + Review | Implemented on active PR | PR #234; persistence-aligned ritual/period uniqueness; authoritative Planning/Habit read projections remain open | +| Complete first-party buyer journey | Web/PWA + BFF/services | Partial | issue #209 | +| Source/live-base/integration verification | Repository workflows | Implemented on protected main | PR #154; residual central taxonomy issue #132 | +| Exact pinned OpenCode bootstrap allowlist | Repository automation | Implemented on protected main | PR #200 historical bootstrap authority | +| Contextual-orchestrator model route | Repository automation | Implemented on active PR | PR #208 exact OpenCode identity + virtual `orchestrator/free`; blocked on immutable owner authentication/bootstrap release | +| Actions workflow-registry orphan detector | Repository automation | Implemented on active PR | PR #204; read-only exact-tree/registry evidence and no workflow-mutation authority | +| Release evidence structural index | Release tooling | Implemented on active PR | PR #217; exact-source structural admission only | +| Detached release signature verification | Release tooling | Implemented on active PR | PR #236; bounded Ed25519 verification/operator CLI | +| Immutable commercial release | Release tooling + protected main | Partial | issue #210 | + +## Data-rights contributor v1 + +**Status:** Partial + +PR #159 protects the versioned operation set: + +- `export` returns bounded deterministic service-owned data, schema version, safe record count, and contributor digest evidence; +- `erase_preflight` reports explicit blockers without deleting; +- `erase` binds exact request/workspace/actor/idempotency authority and returns replay-safe owner receipt evidence; +- `verify_erased` proves the owner no longer retains scoped live records or fails closed. + +Planning, Habit, and Review are protected participants. Notification and AI are active-PR participants. The contract does not imply every owner participates or that whole-product reconciliation/delivery is complete. + +## Calendar connection lifecycle + +### Authority + +**Status:** Implemented on protected main + +`life-os.calendar-user.v1` binds exact workspace and requesting-user UUIDv4 identities under short-lived HMAC evidence distinct from workspace-only synchronization authority. Stale, future, malformed, substituted, or unconfigured evidence fails closed. + +### Read, disconnect, materialize, create + +**Status:** Implemented on protected main + +- PR #157 exposes authenticated local disconnect without reading provider secret handles. +- PR #176 prevents alternate/corrupt persistence adapters from returning a different connection/workspace/user record. +- PR #189 exposes only bounded credential-free active connection state. +- PR #193 materializes plaintext credential data only inside a validated secret-store port boundary. +- PR #197 writes secret material first, persists only opaque handles, validates returned durable authority, and compensates reviewed failure paths. +- PR #201 protects reverse-order compensation of all newly materialized handles when returned durable create evidence mismatches exact identity/handles. +- PR #203 provides the concrete Calendar-owned encrypted self-hosted secret-store profile. + +### Hosted OAuth ceremony + +**Status:** Implemented on active PR + +PR #216 fails hosted startup closed rather than accepting process-global Google/CalDAV provider credentials as end-user authority. PR #228 adds an opaque, expiring, one-time Google OAuth authorization-state contract bound to exact workspace/user/provider/redirect evidence and an opaque PKCE-verifier secret reference. The repository-returned consumed row is revalidated before secret materialization. + +This active contract does not claim callback/token exchange, provider token persistence, successful verifier cleanup after exchange, concrete PostgreSQL OAuth-state runtime, refresh fencing, provider-side revoke/delete recovery, calendar discovery/selection or scoped synchronization. Those remain **Partial** under #129. + +## Plugin installation, credentials, and operator composition + +### Installation and credential binding + +**Status:** Implemented on protected main + +PR #151 treats a manifest as requested intent. PR #169 persists exact bounded installation authority. PR #172 materializes credentials only through `PluginSecretStore` and persists only an opaque reference. PR #175 rejects mismatched returned installation identity. + +Exact replay cannot rematerialize or overwrite an existing secret. Conflicting durable winners trigger compensation. Revocation ends durable authority before external cleanup and never restores authority during retry. + +### Operator requests + +**Status:** Implemented on protected main + +PR #191 binds installation/workspace/actor, exact method/path, freshness, and one-time evidence to an atomic replay store. PR #196 composes this authority behind a fail-closed HTTP boundary and maps malformed JSON, stale/replayed evidence, absent dependencies, and invalid durable evidence to bounded credential-free problems. + +No operator route grants arbitrary SQL, filesystem, subprocess, tool, or network authority. + +### Delivery-origin, Vault, and hosted Integration composition + +**Status:** Implemented on active PR + +PR #205 establishes a host-owned exact normalized HTTPS origin scoped to opaque UUIDv4 grant, installation, workspace, and granting-user identities. PR #235 adds service-owned PostgreSQL grant persistence and active-installation fencing; PR #241 strengthens credential/revocation admission; PR #242 adds the Vault KV v2 `PluginSecretStore`; PR #243/#244 compose authenticated Vault operator authority and the shared Integration-owned PostgreSQL pool; PR #245 supplies the concrete hosted/default-entrypoint runtime. + +Draft PR #250 composes existing delivery-origin authority only through exact signed one-time operator grant/read/revoke application methods. The canonical route verifier accepts only lowercase UUIDv4 delivery-origin collection/item/revoke paths and their exact POST/GET/POST methods. This is an internal application authority contract: #250 deliberately adds no public HTTP delivery-origin route and performs no outbound HTTPS. + +A durable origin grant never authorizes a resolved IP address, DNS rebinding result, redirect, proxy route, or later connection. Immutable/versioned canonical egress authority, connect-time SSRF enforcement, bounded response/time behavior, delivery attempts/outcomes, retry/dead-letter and operator recovery remain **Partial** under #130. + +## First-party buyer-path contracts + +**Status:** Partial + +Issue #209 is the commercial first-party Goals → Projects → Tasks → Habits → Review path. PR #214's active BFF contract authenticates through Identity, derives workspace authority server-side, signs the exact Planning method/path, forwards no browser credential downstream, and validates bounded returned ownership/schema evidence. PR #229 renders and mutates only validated durable Goal evidence. The stacked PR #234 Review page likewise consumes only browser-safe Review evidence and preserves persistence invariants such as one completion per `(workspace_id, ritual_kind, period_start_date)`. + +The active stack is not shipped. Complete contract acceptance still requires all prerequisite BFF/workspace descendants, Figma/Storybook identity, normal/loading/empty/error/permission/responsive/interaction and keyboard/a11y states, authoritative Review Planning/Habit projections, and KO/EN/JA/ZH/VI/ES/DE/FR translation-ledger parity on current exact heads. + +## Model-assisted development contract + +**Status:** Partial + +Protected PR #200 authorizes only the exact reviewed OpenCode bootstrap surface; it does not authorize direct provider model selection. Active PR #208 routes model-assisted work through contextual-orchestrator and virtual `orchestrator/free`. Provider credentials remain owner-side bootstrap material. LifeOS cannot promote the consumer until contextual-orchestrator repairs the currently mismatched authentication/bootstrap contract, publishes an immutable reviewed release, and the exact released LifeOS consumer passes acceptance. Mutable source copying or direct-provider fallback is not a compatibility mechanism. + +## Release evidence contract + +**Status:** Partial + +Active Draft #217 structurally validates one exact release-evidence index, including artifact/checksum/provenance/signature coverage and bounded nightly identity. Stacked #236 verifies detached Ed25519 evidence and provides a bounded operator interface. These contracts do not publish a release or distribute/rotate/revoke trust roots. #210 remains open until an unchanged protected release source is bound to version/CHANGELOG/tag/package, immutable artifact, SBOM, provenance/signature, reproducibility and rollback/recovery acceptance. + +## Events + +Asynchronous events use opaque event IDs, explicit type/version, validated workspace/actor/correlation/causation context, bounded immutable payloads, and idempotent consumers. PR #190 binds protected integration event authority to the exact request. Receiving an event never grants producer-database authority. + +## Versioning and compatibility + +Breaking route/event/schema semantics require explicit versioning or a reviewed migration contract. Additive optional fields remain bounded and default-safe. Unknown versions fail closed. Migration rollback never fabricates restored external secret/provider state. + +## Verification evidence identity + +**Status:** Accepted architecture + +`source_head_sha`, `pr_base_snapshot_sha`, `live_base_tip_sha`, integration/synthetic tree identity, `workflow_checkout_sha`, `protected_main_sha`, and `release_source_sha` are separate authorities. PR #154 protects source and live-base compatibility separation. Issue #132 remains **Partial** for central reusable scanner attribution; a synthetic merge scan cannot be called exact-source evidence. + +PR #204 is an active, read-only extension that compares one exact protected-default-branch Git tree with the complete Actions workflow registry so deleted workflow files cannot silently leave active orphan identities. Its evidence is not protected truth until integration and it does not authorize workflow-state mutation. diff --git a/docs/DATA_MODEL.md b/docs/DATA_MODEL.md new file mode 100644 index 000000000..19421981a --- /dev/null +++ b/docs/DATA_MODEL.md @@ -0,0 +1,159 @@ +# LifeOS Logical Data Model + +**Status:** Implemented on active PR + +This document describes logical ownership, cardinality, immutability, and maturity. It never authorizes cross-service SQL. Physical schema truth remains in each owning service's migrations. + +## Ownership rules + +- Every bounded context owns its database schema/role, migrations, repositories, credentials, transaction boundaries, and recovery behavior. +- Shared UUIDv4 values are logical references, not cross-service foreign keys or table access authority. +- Product-owned database objects use descriptive multiword `snake_case`. +- Provider/plugin identifiers are bounded metadata. Secret references are separate and never become primary identity. +- Browser-local objects are drafts/cache until the owning service accepts them. +- Conceptual or active-PR records are explicitly labeled and are not protected-main persistence claims. + +## Logical ERD + +```mermaid +erDiagram + USER_ACCOUNT ||--o{ EXTERNAL_IDENTITY : maps + USER_ACCOUNT ||--o{ BROWSER_SESSION : owns + USER_ACCOUNT ||--o{ WORKSPACE_MEMBERSHIP : joins + WORKSPACE_RECORD ||--o{ WORKSPACE_MEMBERSHIP : contains + + WORKSPACE_RECORD ||--o{ GOAL_RECORD : contains + GOAL_RECORD ||--o{ PROJECT_RECORD : organizes + PROJECT_RECORD ||--o{ TASK_RECORD : contains + WORKSPACE_RECORD ||--o{ TODAY_AGGREGATE : owns + TODAY_AGGREGATE ||--o{ TODAY_ACTION : contains + + WORKSPACE_RECORD ||--o{ HABIT_RECORD : contains + HABIT_RECORD ||--o{ HABIT_COMPLETION : records + WORKSPACE_RECORD ||--o{ REVIEW_RECORD : contains + + WORKSPACE_RECORD ||--o{ CALENDAR_CONNECTION_RECORD : authorizes + USER_ACCOUNT ||--o{ CALENDAR_CONNECTION_RECORD : owns + CALENDAR_CONNECTION_RECORD ||--o{ CALENDAR_SYNC_RECORD : tracks + CALENDAR_CONNECTION_RECORD ||--o{ OAUTH_AUTHORIZATION_STATE : authorizes + + WORKSPACE_RECORD ||--o{ REMINDER_OCCURRENCE : contains + REMINDER_OCCURRENCE ||--o{ DELIVERY_OUTCOME : records + + WORKSPACE_RECORD ||--o{ AI_PROPOSAL_RECORD : contains + AI_PROPOSAL_RECORD ||--o{ AI_DECISION_RECORD : decides + + WORKSPACE_RECORD ||--o{ PRIVACY_ACCESS_DECISION : governs + PRIVACY_ACCESS_DECISION ||--o{ PRIVACY_ACCESS_GRANT : issues + + WORKSPACE_RECORD ||--o{ DATA_RIGHTS_REQUEST : owns + DATA_RIGHTS_REQUEST ||--o{ DATA_RIGHTS_RECEIPT : terminates + + WORKSPACE_RECORD ||--o{ PLUGIN_INSTALLATION_RECORD : grants + USER_ACCOUNT ||--o{ PLUGIN_INSTALLATION_RECORD : installs + PLUGIN_INSTALLATION_RECORD ||--o{ PLUGIN_CREDENTIAL_BINDING_RECORD : binds + PLUGIN_INSTALLATION_RECORD ||--o{ PLUGIN_DELIVERY_ORIGIN_GRANT : authorizes +``` + +Relationships from `USER_ACCOUNT` to Calendar/Plugin records express logical ownership identifiers only. They do not imply cross-schema foreign keys. `OAUTH_AUTHORIZATION_STATE` and `PLUGIN_DELIVERY_ORIGIN_GRANT` are active-line logical records until their owning migrations integrate; the diagram does not promote them to protected persistence. + +## Protected-main persistence + +### Identity + +Identity owns users, provider mappings, sessions, workspace membership, authentication provenance, `data_rights_request`, immutable terminal receipt evidence, tenant/requesting-user scoped status lookup, and aggregate export-integrity manifests. + +Authentication instant and session rotation instant are distinct. Request, idempotency, receipt, and digest evidence are immutable once terminal. + +### Planning + +Planning owns Goal, Project, Task, search, Today aggregate/action/revision/idempotency state, and its service-owned data-rights erasure receipt. PR #179 protects the contributor; PR #194 protects request-bound authenticated contributor transport. + +### Habit + +Habit owns recurrence/completion evidence and its service-owned data-rights erasure/replay evidence. PR #184 protects the contributor; PR #192 protects the authenticated one-time transport/replay boundary. + +### Review + +Review owns guided-review completion/projection records. Request-bound workspace authority is protected through PR #185. The Review data-rights erasure receipt migration and contributor are **Implemented on protected main** through PR #195. + +### Notification + +Notification owns reminder occurrences, expiring claims, delivery attempts/outcomes, and inbox evidence. Its data-rights erasure migration/receipt/contributor are **Implemented on active PR** in PR #198. + +### AI Proposal + +AI owns immutable proposal/evidence rows and append-only accept/reject decisions. Its data-rights erasure migration/receipt/contributor and cursor-capable export contract changes are **Implemented on active PR** in PR #199. + +### Privacy + +Privacy owns purpose-bound access decisions, grants, consumption/fencing, and audit events. Whole-right request identity remains Identity-owned; Privacy retains authority over its own eventual contributor. + +### Calendar Integration + +**Status:** Partial + +`calendar_integration.calendar_connection_record` is protected and scoped simultaneously to opaque connection, workspace, and user UUIDv4 identities. It stores bounded provider/account/calendar metadata, normalized scopes, lifecycle timestamps, and opaque access/refresh secret references—not plaintext provider credentials. + +Protected-main lifecycle evidence: + +- PR #150 creates the owning record; +- PR #153 adds atomic active-to-revoked transition and replay; +- PR #176 validates returned lookup identity exactly; +- PR #189 exposes a credential-free authenticated read projection; +- PR #193 materializes secrets only through validated opaque handles; +- PR #197 composes authenticated secret-first creation and compensation boundaries; +- PR #201 compensates both newly written handles when persistence returns mismatched durable evidence; +- PR #203 provides Calendar-owned AES-256-GCM encrypted self-hosted credential storage. + +Active PR #216 prevents deployment-wide Google/CalDAV credentials from substituting for user-owned hosted authority. Stacked PR #228 adds an OAuth authorization-state aggregate with opaque UUIDv4 state identity, exact workspace/user/provider/redirect evidence, bounded expiry/consumption state and an opaque PKCE verifier secret reference. Verifier plaintext remains secret-store-owned. This active aggregate is not protected persistence yet, and #129 still requires concrete PostgreSQL state persistence, callback/token exchange, successful verifier cleanup, refresh fencing, provider revoke/delete recovery, discovery/selection and scoped synchronization. + +### Plugin Integration + +**Status:** Partial + +`plugin_integration.plugin_installation_record` is protected through PR #169 and retains opaque installation/workspace/installer UUIDv4 identities, bounded plugin/version metadata, exact manifest SHA-256 evidence, normalized explicit grants, lifecycle status, and timestamps. PR #175 requires exact opaque installation identity at application and repository boundaries. + +`plugin_integration.plugin_credential_binding_record` is protected through PR #172. It retains only bounded opaque `secret_reference` metadata and binding lifecycle evidence; plaintext credential material remains behind the `PluginSecretStore` port. + +Operator request replay evidence is protected through PR #191 and consumed by the fail-closed HTTP composition from PR #196. + +The active #130 stack adds Integration-owned persistence without widening this ownership boundary: + +- #205 defines the host-owned normalized HTTPS delivery-origin aggregate; +- #235 persists delivery-origin grants in Integration-owned PostgreSQL and fences them against active installation evidence; +- #241 strengthens credential/revocation admission consistency; +- #242 stores provider secret material behind an operator-configured Vault KV v2 adapter; LifeOS durable rows retain only opaque references; +- #243/#244 compose Vault operator authority and one Integration-owned PostgreSQL pool; +- #245 supplies the concrete hosted/default-entrypoint PostgreSQL runtime and retained exact-ancestor real Vault+PostgreSQL lifecycle acceptance; +- #250 exposes delivery-origin grant/read/revoke only through signed one-time operator application authority. + +These active rows are not protected truth until integration. There is still no delivery attempt/outcome persistence for plugin outbound delivery, and a durable delivery-origin grant is not connect-time DNS/IP/redirect/proxy authorization. #130 remains Partial. + +## Data-rights participant model + +| Participant | Persistence owner | Status | Evidence | +| --- | --- | --- | --- | +| Identity coordinator/ledger | Identity | Implemented on protected main | durable request/terminal receipt and status | +| Planning contributor/receipt | Planning | Implemented on protected main | PR #179 and PR #194 | +| Habit contributor/receipt | Habit | Implemented on protected main | PR #184 and PR #192 | +| Review contributor/receipt | Review | Implemented on protected main | PR #195 | +| Notification contributor/receipt | Notification | Implemented on active PR | PR #198 | +| AI contributor/receipt | AI Proposal | Implemented on active PR | PR #199 | +| Remaining owning domains and whole-product reconciliation | Each owner + Identity coordinator | Partial | issue #55 | + +No participant row grants Identity direct access to another service's tables. Whole-product completion requires an explicit participant registry and reconciled exact request evidence. + +## Cardinality and immutability + +- One workspace may contain many planning, habit, review, reminder, proposal, calendar, privacy, and plugin records. +- One Calendar connection belongs to exactly one workspace and one user authority scope; an OAuth authorization state is bound to one exact workspace/user/provider ceremony and is one-time/expiring. +- One Plugin installation belongs to exactly one workspace and one installing user and may have bounded credential bindings and delivery-origin grants. +- One durable delivery-origin grant remains scoped to its installation/workspace/granting-user evidence and may be revoked; it does not confer authority over later network resolution. +- One data-rights request has zero or more contributor sections/receipts and at most one immutable terminal aggregate receipt. +- Proposal decisions, delivery outcomes, terminal data-rights receipts, and immutable audit evidence are append-only or mutation-denying by owning-service contract. +- Mutable lifecycle rows expose explicit state/version/timestamps and deterministic replay/conflict semantics. + +## Temporal and provenance rules + +Use UTC instants plus explicit IANA timezone/local-calendar fields where civil-time semantics matter. Creation, update, completion, revocation, expiry, revision, idempotency, fencing, digest, and provenance fields exist only when supported by owning migrations. Diagrams never authorize new columns. diff --git a/docs/DOCUMENTATION_ASSESSMENT.md b/docs/DOCUMENTATION_ASSESSMENT.md new file mode 100644 index 000000000..d4adb34ad --- /dev/null +++ b/docs/DOCUMENTATION_ASSESSMENT.md @@ -0,0 +1,116 @@ +# LifeOS Documentation Assessment + +**Status:** Implemented on active PR + +## Assessment rule + +File presence, age, old review resolution, PR-body prose, and predecessor checks do not prove semantic fitness. Protected-main source/migrations/tests and live repository policy are authoritative. Active-PR behavior is labeled and remains non-shipped until integration. + +This assessment intentionally avoids embedding volatile head SHAs. Exact heads, live base, workflow checkout identities, reviews, and writer state must be refetched for every merge or mutation decision. + +## Canonical graph fitness + +| Dimension | Status | Evidence and remaining condition | +| --- | --- | --- | +| Product definition and supersession chain | Implemented on active PR | PRD/Architecture preserve server-backed modular MSA, UUIDv4, explicit offline/draft profiles, service-owned durability and active-vs-protected separation | +| Technical boundaries | Partial | protected service authority remains current; active Calendar, Plugin, model-routing, first-party journey and release stacks still require full TRD/API/UML/security propagation before docs integration | +| Root Architecture | Implemented on active PR | current active Calendar #216/#228, Plugin chain through #250, model-routing #208, and release #217/#236 are separated from protected truth | +| ADR index/details | Implemented on active PR | ADR 0001-0013 remain indexed; active evidence may narrow implementation without retroactively marking an architectural decision shipped | +| UML/C4/sequence/state/deployment/authority/recovery | Partial | existing views remain authoritative for protected boundaries; the current active #209/#129/#130/#210 stacks still need diagram-level propagation | +| Logical ERD/Data Model | Partial | protected ownership is preserved; active OAuth-state, Plugin grant/Vault/PostgreSQL and release-evidence structures need current active labeling across the logical model | +| API/event/schema/version contracts | Partial | protected contracts remain shipped truth; active #228/#250 and #209 BFF/workspace surfaces require current active contract propagation | +| Security and Threat Model | Partial | purpose-bound authority remains canonical; active Vault, OAuth verifier, delivery-origin and contextual-orchestrator authentication boundaries need current threat-model propagation | +| Privacy/Data Lifecycle | Partial | Review is protected while Notification/AI contributors remain active; #55 completion/reconciliation/retention/delivery is still open | +| Test Strategy | Partial | realistic PostgreSQL/browser/security evidence remains canonical; current exact-head and real-server evidence identities need active-stack propagation without inheriting predecessor GREEN | +| Operability/incident/recovery | Partial | service-owned recovery is canonical; Calendar provider cleanup, Plugin delivery retry/dead-letter and immutable release recovery remain open | +| Release/Migration/Rollback/provenance | Partial | issue #210 is now an explicit buyer gap; active #217/#236 narrow evidence validation but do not constitute an immutable release | +| Standards/Research | Implemented on active PR | final standards and publication-status-aware research remain linked; no active product slice changes authority of primary standards | +| Traceability | Implemented on active PR | protected chronology and selected current architecture-defining active stacks are now separated, including #209/#210 buyer gaps | +| README discoverability | Partial | canonical files remain linked; buyer-gap/current-active summaries still require final propagation before integration | +| Protected `AGENTS.md` authority | Implemented on protected main | live single-maintainer, exact-evidence and protected-branch policy remains superior authority | +| CLAUDE discoverability | Partial | contributor routing remains present; current owner-release/model-routing and buyer-gap summaries require reconciliation | +| CHANGELOG product/governance history | Partial | protected history remains; this documentation-currentness repair still needs an exact-head accepted changelog entry before integration | +| Executable documentation contracts | Implemented on active PR | currentness contract now requires selected architecture-defining active PR rows and canonical buyer gaps #55/#129/#130/#209/#210 | + +## Protected-main reconciliation + +The canonical branch retains these shipped authorities. The same line must never be relabeled active merely because successor work exists: + +- PR #154 — **Implemented on protected main** for exact-source verification identity and independently reconstructed live-base compatibility. +- PR #155 — **Implemented on protected main** for signed workspace-and-user Calendar authority. +- PR #156 — **Implemented on protected main** in the protected Calendar lifecycle lineage. +- PR #157 — **Implemented on protected main** for authenticated Calendar disconnect. +- PR #159 — **Implemented on protected main** for the versioned service-owned data-rights contributor lifecycle. +- PR #168 and PR #188 — **Implemented on protected main** for Planning signed/request-bound authority. +- PR #169, PR #172 and PR #175 — **Implemented on protected main** for durable plugin installation, opaque credential binding and exact installation evidence. +- PR #173 — **Implemented on protected main** for signed Habit authority. +- PR #176 and PR #189 — **Implemented on protected main** for exact Calendar lookup and authenticated read. +- PR #179 and PR #194 — **Implemented on protected main** for Planning contribution and authenticated transport. +- PR #184 and PR #192 — **Implemented on protected main** for Habit contribution and authenticated replay-safe transport. +- PR #185 — **Implemented on protected main** for request-bound Review authority. +- PR #186 and PR #187 — **Implemented on protected main** for real authenticated Planning/Habit Today composition. +- PR #190 — **Implemented on protected main** for request-bound integration event authority. +- PR #191 and PR #196 — **Implemented on protected main** for one-time plugin operator authority and fail-closed HTTP composition. +- PR #193 — **Implemented on protected main** for scoped Calendar credential materialization. +- PR #195 — **Implemented on protected main** for the Review-owned data-rights contributor. +- PR #197 — **Implemented on protected main** for authenticated Calendar connection creation. +- PR #200 — **Implemented on protected main** for the exact pinned OpenCode bootstrap allowlist; this does not make direct-provider routing the current target architecture. +- PR #201 — **Implemented on protected main** for returned-create-evidence validation and reverse-order secret compensation. +- PR #203 — **Implemented on protected main** for Calendar-owned encrypted self-hosted credential storage. + +Issue #163 is completed. PR #164 remains historical fake-success-removal evidence; PR #186/#187 are the protected real-composition completion. + +PR #156, PR #160, PR #162, PR #165, PR #175, PR #176, PR #178, PR #179, PR #195, PR #200, and PR #203 must not be described as current active PRs. + +## Current active pull-request line + +| Pull request | Status | Documentation meaning | Current gate caveat | +| --- | --- | --- | --- | +| PR #145 | Implemented on active PR | single canonical whole-product documentation successor | Draft; exact-head docs/repository/security/review/live-base evidence required | +| PR #198 | Implemented on active PR | Notification-owned data-rights contributor | non-shipped until exact-head gates and normal integration | +| PR #199 | Implemented on active PR | AI-owned contributor plus additive cursor/runtime-authority hardening | non-shipped until exact-head gates and normal integration | +| PR #204 | Implemented on active PR | read-only Actions workflow-registry detector for exact-tree orphan evidence | detector grants no workflow mutation authority; exact-head policy/review still applies | +| PR #205 | Implemented on active PR | host-owned delivery-origin authority foundation | ancestor of the current #130 stack; no standalone outbound network authority | +| PR #208 | Implemented on active PR | exact OpenCode identity with contextual-orchestrator `orchestrator/free` routing | blocked on canonical owner authentication/bootstrap repair plus immutable upstream release and consumer GREEN | +| PR #214 | Implemented on active PR | authenticated first-party Goal BFF foundation for #209 | Draft base of a deep buyer-journey stack; exact-head gates must be reacquired after each restack | +| PR #216 | Implemented on active PR | hosted Calendar rejects deployment-wide provider credentials pending authenticated user-owned composition | does not itself implement Google OAuth callback/token lifecycle | +| PR #217 | Implemented on active PR | machine-readable structural release-evidence index/validator | Draft; no immutable release or current exact-head acceptance implied | +| PR #228 | Implemented on active PR | scoped Google OAuth state/PKCE authority with opaque durable state and secret-held verifier | no hosted callback/token exchange, durable PostgreSQL OAuth-state runtime or provider cleanup yet | +| PR #229 | Implemented on active PR | durable browser-safe Goals workspace consuming the authenticated BFF stack | does not complete Projects/Tasks/Habits/Review, Figma/Storybook or locale parity | +| PR #234 | Implemented on active PR | durable Weekly Review workspace with persistence-aligned `(ritual_kind, period_start_date)` uniqueness | stacked Draft; authoritative Planning/Habit review projections and full UI/localization gates remain open | +| PR #236 | Implemented on active PR | detached Ed25519 release-evidence verification and bounded operator CLI | stacked on #217; trust roots/key lifecycle and immutable release remain open | +| PR #245 | Implemented on active PR | concrete hosted Plugin Vault plus Integration-owned PostgreSQL runtime; retained ancestor real-server lifecycle acceptance | ancestor evidence is not current-head merge authority; outbound delivery remains absent | +| PR #250 | Implemented on active PR | signed delivery-origin operator authority over the existing service-owned aggregate/store | exact repair verifier is pending at current evidence point; HTTP delivery-origin transport and outbound networking are deliberately absent | + +Active work may change while this document is reviewed. The table records bounded semantic scope, not merge eligibility, current head identity, or gate success. + +## Open issue and buyer-gap fitness + +| Issue | Status | Current meaning | +| --- | --- | --- | +| #21 | Partial | umbrella commercial readiness; capability maturity does not close buyer gaps | +| #55 | Partial | complete participant inventory, remaining contributors, reconciliation, retention/legal hold, backup expiry, protected export delivery and terminal whole-right evidence | +| #129 | Partial | protected encrypted storage plus active #216/#228 narrow hosted/OAuth state authority; callback/token exchange, durable OAuth-state runtime, refresh, provider cleanup/discovery and scoped sync remain incomplete | +| #130 | Partial | active chain through #250 narrows origin/Vault/PostgreSQL/operator authority; connect-time SSRF-safe outbound HTTPS, outcomes, retry/dead-letter and operator recovery remain incomplete | +| #209 | Partial | active first-party Goal/BFF/workspace/Review stack exists, but complete Goals→Projects→Tasks→Habits→Review journey, Figma/Storybook traceability, all UI states, authoritative Review projections and KO/EN/JA/ZH/VI/ES/DE/FR parity remain incomplete | +| #210 | Partial | active #217/#236 validate release evidence/signatures, but immutable version/tag/package/release, trust/key lifecycle, SBOM/provenance/reproducibility and rollback/recovery acceptance remain incomplete | +| Issue #132 | Partial | residual central reusable scanner checkout/SARIF/status attribution taxonomy | +| #148 | Partial | closes only when this exact canonical successor integrates and currentness evidence remains green | + +Canonical buyer gaps are #55, #129, #130, #209, and #210. Issue #132 is verification governance and #148 is documentation integration; neither is silently counted as a buyer-visible product capability. + +## Semantic checks performed by this successor + +- Protected-main chronology remains authoritative and current active work is never promoted before integration. +- Review-owned data rights (#195) and OpenCode bootstrap (#200) remain protected evidence while Notification/AI contribution and the contextual-orchestrator consumer lane remain active as applicable. +- Calendar documentation distinguishes protected encrypted storage from active hosted rejection and OAuth state/PKCE without inventing callback/token success. +- Plugin documentation distinguishes protected operator foundations from active origin/Vault/PostgreSQL/signed-operator layers and from still-missing connect-time outbound authority. +- Buyer-visible #209 UI work remains Draft and explicitly lacks Figma/Storybook/full locale/release parity where not proven. +- #210 structural/signature evidence remains distinct from publishing an immutable release. +- Exact source/live-base/integration/checkout/protected/release evidence identities remain separate. +- Model-assisted work cannot self-authorize review, merge or release and does not copy mutable owner source into LifeOS. +- Documentation contract tests fail when protected work reappears as active, selected current active architecture work disappears, or the canonical buyer-gap set regresses. + +## Remaining integration conditions + +PR #145 remains documentation-incomplete until PRD/TRD/Architecture/Data Model/UML/API/Security/Threat Model/Privacy/Test/Operability/Release/Standards/Traceability/Assessment/README/CLAUDE/CHANGELOG and executable contracts are mutually code-current, then its unchanged exact head passes required repository/security checks, current independent review/thread state and live-base compatibility and integrates under live policy. Integration of this documentation line does not complete LifeOS; maintenance returns immediately to #55/#129/#130/#209/#210 product gaps. diff --git a/docs/OPERABILITY.md b/docs/OPERABILITY.md new file mode 100644 index 000000000..96e4b1182 --- /dev/null +++ b/docs/OPERABILITY.md @@ -0,0 +1,135 @@ +# LifeOS Operability, Incident, and Recovery + +**Status:** Implemented on active PR + +## Deployment profiles + +### Self-hosted composition + +**Status:** Implemented on protected main + +Docker Compose composes independent LifeOS workloads with operator-owned PostgreSQL, NATS, secrets, and provider configuration. Compose is a deployment profile, not shared persistence or credential authority. + +### Kubernetes reference + +**Status:** Implemented on protected main + +Kubernetes/Kustomize artifacts are provider-neutral restricted references. Operators own cluster provisioning, TLS/DNS/ingress, managed PostgreSQL/NATS, registry, KMS/secret stores, backup storage, network policy, egress controls, identity/provider configuration, and monitoring. + +## Runtime ownership and shutdown + +Each service owns process configuration, database pool, migrations, provider clients, health/readiness, metrics/logs, graceful shutdown, and retry/recovery. Shared process composition cannot create cross-service table authority. + +Shutdown must: + +1. reject new work where required; +2. stop/await workers and in-flight bounded operations; +3. release claims/leases according to owner semantics; +4. close provider clients and database pools exactly once; +5. emit bounded credential-free terminal evidence. + +## Liveness and readiness + +Liveness reports process/runtime viability. Readiness fails when a service cannot safely serve the contracted workload. Dependency-specific readiness must not be collapsed into generic process health. + +Optional provider outage may yield explicit degraded behavior where unrelated domains remain safe. Owning persistence, signing authority, replay store, required secret store/KMS, or required released gateway authority absence must fail the affected durable/secret/model operation closed. + +## Observability + +- structured bounded credential-free logs; +- correlation, request, idempotency, fencing, and evidence IDs where implemented; +- bounded operator-only metrics; +- no cookies, bearer credentials, secret handles, plaintext credentials, Vault credentials, raw prompts/responses, hidden reasoning, provider bodies, or unbounded tenant content; +- explicit failure class and dependency boundary without reflecting attacker-controlled identifiers; +- exact source/integration/release identities on retained CI/provenance evidence. + +## Protected operational boundaries + +- PR #186 and PR #187 provide real authenticated Today composition; Issue #163 is completed. +- PR #157, PR #176, PR #189, PR #193, PR #197, PR #201 and PR #203 provide Calendar disconnect, lookup validation, read, materialization, secret-first creation/compensation and encrypted self-hosted secret storage. +- PR #179/PR #194, PR #184/PR #192 and PR #195 provide protected Planning/Habit/Review data-rights participant evidence. +- PR #169, PR #172, PR #175, PR #191, and PR #196 provide durable Plugin installation/credential/operator boundaries. +- PR #200 protects only the exact reviewed OpenCode bootstrap boundary. + +These boundaries have owner-specific degraded and replay semantics and do not close #55/#129/#130/#209/#210. + +## Active operational boundaries + +- #216 rejects deployment-wide Google/CalDAV credentials in hosted multi-user composition; #228 adds bounded OAuth state/PKCE ceremony authority but not token exchange or refresh/provider cleanup. +- #242/#243/#244/#245 compose Vault KV v2 and one Integration-owned PostgreSQL pool for the hosted Plugin runtime. Retained real Vault+PostgreSQL lifecycle acceptance on an exact #245 ancestor is valuable recovery evidence but not current-head merge authority. +- #250 adds signed delivery-origin grant/read/revoke application authority and deliberately stops before public delivery-origin HTTP transport/outbound networking. +- #214/#229/#234 are active first-party journey evidence; browser-visible completion still requires current-head E2E/all-state/a11y/Figma/Storybook/8-locale acceptance. +- #208 consumes contextual-orchestrator/`orchestrator/free` but remains fail-closed until the canonical owner authentication/bootstrap contract is repaired and immutably released. +- #217/#236 validate release evidence/signatures but do not publish a release. + +No active line is production authority until normal protected integration. + +## Failure semantics + +- malformed ownership, UUIDs, signatures, issuance, one-time evidence, cursors, digests, and persisted rows fail closed; +- database outage cannot return durable-success claims; +- provider/Vault/KMS outage never falls back to plaintext or process-global caller-visible credentials; +- Calendar hosted configuration cannot replace user-owned credentials with deployment-global provider values; +- stale concurrent writes return explicit conflict rather than overwrite; +- workers use bounded retries/claims/backoff and retain exact replay identity; +- unknown data-rights participant state cannot become terminal completion; +- local revoke never becomes provider revoke success without proof; +- external cleanup retry never restores revoked LifeOS authority; +- plugin/operator origin authority never becomes arbitrary egress/tool/process/filesystem authority; +- a stored HTTPS origin is not authorization for later DNS/IP/redirect/proxy resolution; +- missing immutable contextual-orchestrator owner capability fails the model-assisted lane closed rather than selecting a direct provider; +- queued, stale, predecessor, synthetic-only, or temporary-writer-only checks never become release evidence. + +## Incident priorities + +1. preserve tenant isolation, credentials, and authority boundaries; +2. prevent false durable-success, deletion, delivery, provider-revocation, or release claims; +3. stop unsafe writes, secret materialization, workers, or outbound behavior; +4. retain bounded evidence needed for diagnosis and replay; +5. restore through documented rollback, forward-fix, compensation, restore, or retry; +6. reconcile partial workflows idempotently without restoring revoked authority; +7. revalidate readiness and exact protected/release identity before resuming normal operation. + +## Backup and restore + +**Status:** Implemented on protected main + +Logical PostgreSQL backup produces integrity evidence. Restore validates artifacts and refuses unsafe non-empty targets. This does not claim PITR; WAL/archive/replication and managed backup scheduling are operator-owned until implemented and measured. + +Backups preserve owning-service boundaries. A restored data-rights, OAuth-state, credential, or delivery-origin record must still satisfy current schema, tenant, immutability, expiry/revocation, and secret-reference validation. Backup expiry remains explicit in whole-right deletion claims. + +## Migration and rollback + +Migrations require compatibility analysis, executable migration evidence, and rollback or forward-fix appropriate to risk. Rollback never claims to undo already committed destructive erasure, external provider revocation, delivered notification/calendar mutation, consumed OAuth ceremony state, Vault/secret-store write/delete, or immutable release publication unless a tested compensation contract exists. + +Review PR #195 is now protected. Active #198/#199 introduce Notification/AI owner migrations. Active Calendar/Plugin stacks introduce additional owner-specific persistence/runtime obligations and must prove restart, privilege, replay, compensation and rollback/forward-fix semantics before integration. Protected #201 keeps Calendar compensation uncertainty fail-closed. + +## Current operational gaps + +| Gap | Status | Remaining operational evidence | +| --- | --- | --- | +| Complete data-rights participant/reconciliation/retention/protected delivery | Partial | issue #55 | +| Complete per-user Calendar OAuth/token/refresh/provider cleanup/discovery/scoped sync and hosted secret lifecycle | Partial | issue #129 | +| Complete Plugin canonical egress/outcomes/retry/dead-letter/operator recovery | Partial | issue #130 | +| Complete first-party buyer journey with all states/a11y/Figma/Storybook/8 locales/current-head E2E | Partial | issue #209 | +| Immutable protected release with package/SBOM/provenance/signature/trust/reproducibility/rollback/recovery | Partial | issue #210 | +| Central reusable scanner checkout/SARIF/status identity taxonomy | Partial | issue #132 | +| Fixed public SLO/RPO/RTO commitments | Out of scope | unavailable without measured deployment-specific evidence | + +## Runbooks and recovery drills + +Required drills include database outage/restore, migration failure, stale-write conflict, worker replay, NATS outage, OAuth expiry/replay and callback failure, provider timeout, Vault/KMS create/delete partial failure, Calendar create compensation, data-rights stuck participant, plugin credential/origin revocation races, delivery retry/dead-letter once introduced, contextual-orchestrator auth/capability outage, release provenance/signature/trust-root mismatch, rollback and forward-fix. + +Runbooks identify owner, trigger, exact affected authority/evidence identity, safe-stop behavior, smallest recovery action, rollback/forward-fix/compensation limits, and acceptance evidence. + +## Verification-writer recovery + +Purpose-bounded writer workflows are not permanent operational dependencies. A writer may change only its declared owner paths, prove the intended exact source, and retire by deleting only itself through an ordinary descendant after success. If its environment omits a declared runtime prerequisite—such as a workspace package whose `main` points to built `dist`—repair the harness by building the dependency; do not skip the full suite or reinterpret a collection failure as product GREEN. + +## SLO discipline + +LifeOS publishes no fixed availability, RPO, or RTO without measured profile-specific evidence. Operator runbooks may define targets only when monitoring and repeated recovery exercises support them. + +## Release operations + +Issue #210 remains Partial. Active #217/#236 narrow structural and cryptographic evidence validation. A release is one unchanged protected integrated revision plus version/CHANGELOG/tag/immutable package or image, required CI/security/review/coverage/docstrings, browser/accessibility/localization, SBOM/provenance/signatures/trust lifecycle/reproducibility, migration/rollback, backup/restore, installed buyer-path verification and operational evidence. A merged feature, generated documentation pack, model score, ancestor GREEN, or configured maturity percentage is not release readiness. diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 000000000..b11da8226 --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,97 @@ +# LifeOS Product Requirements Document + +**Status:** Implemented on active PR + +Protected-main code, migrations, tests, and live GitHub policy are authoritative for shipped behavior. This PRD is the canonical product-level index; active pull requests are labeled and never promoted to protected truth. + +## Product definition + +LifeOS is a privacy-first, multi-user, server-backed, self-hostable personal operating system connecting Goals, Projects, Tasks, Habits, Today planning, Review, Calendar, reminders, auditable AI proposals, privacy/data-rights controls, plugins, and operator recovery in one user-authoritative workflow. + +## Superseded product assumptions + +- Login-free browser-only/local-first storage as the primary architecture is **Superseded**. +- UUIDv7 internal identifiers are **Superseded** by opaque UUIDv4 product IDs. +- A single durable application and private-personal-only positioning are **Superseded** by modular service ownership and public multi-user operation. +- Browser-local state remains supported only as explicit draft/cache/offline state until an owning service accepts it. + +## Primary customer journey + +1. Authenticate with Google or GitHub and enter an authorized workspace. +2. Organize Goals, Projects, Tasks, and recurring Habits. +3. Create and synchronize an explicit Today plan without silent overwrite. +4. Complete work and inspect durable guided Review evidence. +5. Connect one authorized calendar account and run conflict-safe synchronization. +6. Receive bounded timezone-correct reminders. +7. Request an inert AI proposal and explicitly accept or reject its evidence. +8. Request, inspect, export, and delete personal/workspace data through service-owned contributors. +9. Install explicitly granted plugins without database or arbitrary network authority. +10. Recover, migrate, deploy, observe, and release from auditable protected evidence. + +## Functional requirements + +| ID | Requirement | Status | Evidence / tracking | +| --- | --- | --- | --- | +| PRD-ID-001 | Google/GitHub login, revocable server sessions, workspace membership, and preserved authentication-age provenance. | Implemented on protected main | Identity source/migrations/tests | +| PRD-ID-002 | Internal/public product IDs are opaque UUIDv4; external IDs remain bounded metadata. | Implemented on protected main | `AGENTS.md`, validators, migrations, ADR 0001 | +| PRD-PLAN-001 | Planning owns durable Goals, Projects, Tasks, search, and Today persistence. | Implemented on protected main | Planning migrations/repositories | +| PRD-PLAN-002 | Today synchronization uses explicit acceptance, strong preconditions, idempotency, and stale-conflict reconciliation. | Implemented on protected main | PR #127 | +| PRD-PLAN-003 | Every public Planning route derives signed workspace authority and binds it to the exact method/path/request. | Implemented on protected main | PR #168 and PR #188 | +| PRD-HAB-001 | Habit owns recurring definitions and replay-safe completion history. | Implemented on protected main | Habit migrations/tests | +| PRD-HAB-002 | Every public Habit route derives signed workspace authority; trusted contributor transport consumes destructive authority once. | Implemented on protected main | PR #173 and PR #192 | +| PRD-REV-001 | Review owns guided-review persistence/projections without Planning or Habit mutation authority. | Implemented on protected main | Review service boundaries | +| PRD-REV-002 | Guided-review routes require request-bound signed workspace authority. | Implemented on protected main | PR #185 | +| PRD-CAL-001 | Google/CalDAV synchronization is conflict-safe and tenant-scoped. | Implemented on protected main | Calendar provider tests | +| PRD-CAL-002 | Calendar synchronization uses signed trusted workspace context, not browser-selected ownership. | Implemented on protected main | PR #139 | +| PRD-CAL-003 | Complete encrypted per-user credential lifecycle, OAuth/PKCE, callback/token exchange, refresh/revoke, discovery/selection, and scoped sync. | Partial | issue #129; active PR #216 and PR #228 narrow hosted credential and OAuth state/PKCE boundaries | +| PRD-CAL-004 | Calendar-owned connection metadata is scoped to exact workspace and user and stores opaque secret references only. | Implemented on protected main | PR #150 | +| PRD-CAL-005 | Local connection revocation is atomic, replay-safe, and tenant/user scoped. | Implemented on protected main | PR #153 | +| PRD-CAL-006 | User-sensitive hosted operations use signed `life-os.calendar-user.v1` workspace+user authority. | Implemented on protected main | PR #155 | +| PRD-CAL-007 | Authenticated disconnect, exact lookup validation, bounded connection read, scoped credential materialization, and authenticated secret-first creation are protected behavior. | Implemented on protected main | PR #157, PR #176, PR #189, PR #193, PR #197 | +| PRD-CAL-008 | Create-evidence mismatch compensates every newly materialized credential before sanitized failure. | Implemented on protected main | PR #201 | +| PRD-NOT-001 | Notification owns bounded timezone-correct reminders, claims, outcomes, retries, and recovery evidence. | Implemented on protected main | Notification migrations/scheduler tests | +| PRD-AI-001 | AI output is inert auditable proposal evidence until explicit authorized accept/reject. | Implemented on protected main | AI proposal/audit service | +| PRD-AI-002 | Deterministic schema/quality/safety gates remain independent of live model availability. | Implemented on protected main | Proposal evaluator and live-conformance split | +| PRD-PRIV-001 | Sensitive access is tenant, actor, purpose, resource, lifetime, and audit bound. | Implemented on protected main | Privacy service | +| PRD-PRIV-002 | Data-rights requests preserve recent-auth provenance, durable request identity, immutable terminal receipts, and bounded status. | Implemented on protected main | PR #146 and predecessor foundations | +| PRD-PRIV-003 | Complete export/deletion orchestration covers every owning domain, reconciliation, retention/legal hold, backup expiry, protected artifact delivery, and final participant-set completion. | Partial | issue #55 | +| PRD-PRIV-004 | Export sections carry deterministic bounded data, safe record counts, and integrity evidence. | Implemented on protected main | PR #149 | +| PRD-PRIV-005 | Independent services use versioned `life-os.data-rights-contributor.v1`, never cross-service SQL. | Implemented on protected main | PR #159 | +| PRD-PRIV-007 | Planning owns a deterministic PostgreSQL-backed contributor and authenticated request-bound transport. | Implemented on protected main | PR #179 and PR #194 | +| PRD-PRIV-008 | Habit owns a deterministic PostgreSQL-backed contributor and replay-safe authenticated transport. | Implemented on protected main | PR #184 and PR #192 | +| PRD-PRIV-009 | Review, Notification, and AI own bounded contributors without widening Identity database authority. | Partial | Review protected in PR #195; Notification PR #198 and AI PR #199 remain active until integration | +| PRD-INT-001 | Plugin SDK/manifest/event contracts are versioned, bounded, and deny direct database authority. | Implemented on protected main | Plugin SDK/integration tests | +| PRD-INT-002 | Complete concrete secret/KMS, authorized-origin outbound delivery, retry/dead-letter, revocation fencing, and operator lifecycle. | Partial | issue #130; active #205/#235/#241/#242/#243/#244/#245/#250 narrow durable origin, Vault, PostgreSQL and signed operator boundaries but do not authorize outbound networking | +| PRD-INT-003 | A manifest is intent only; the host grants an explicit tenant/user-scoped capability subset. | Implemented on protected main | PR #151 | +| PRD-INT-004 | Plugin installation persistence is restart-safe and validates exact opaque installation/workspace/installer evidence. | Implemented on protected main | PR #169 and PR #175 | +| PRD-INT-005 | Credential binding stores only opaque secret references and compensates conflicting durable winners. | Implemented on protected main | PR #172 | +| PRD-INT-006 | Operator requests use exact request-bound one-time authority, durable replay protection, and fail-closed HTTP composition. | Implemented on protected main | PR #191 and PR #196 | +| PRD-WEB-001 | The PWA is responsive, keyboard-operable, installable, and structurally localized in Korean and English. | Implemented on protected main | Browser/accessibility/localization tests | +| PRD-WEB-002 | Gateway Today composes authenticated Planning and Habit state without fabricated success. | Implemented on protected main | PR #186 and PR #187; Issue #163 completed | +| PRD-WEB-003 | The first-party authenticated buyer journey covers Goals → Projects → Tasks → Habits → Review with durable server evidence, explicit normal/loading/empty/error/permission states, responsive keyboard/a11y behavior, Figma/Storybook traceability, and KO/EN/JA/ZH/VI/ES/DE/FR locale parity. | Partial | issue #209; active authenticated Goal BFF #214, durable Goals workspace #229 and stacked Weekly Review workspace #234 are bounded evidence only | +| PRD-OPS-001 | Logical PostgreSQL backup/restore proves integrity and refuses unsafe targets. | Implemented on protected main | Backup scripts/tests/runbook | +| PRD-OPS-002 | Deployment/readiness/metrics are provider-neutral and bounded. | Implemented on protected main | Compose/Kubernetes/observability evidence | +| PRD-GOV-001 | Capability maturity and canonical buyer-gap exhaustion are reported independently. | Implemented on protected main | Commercial Readiness registry | +| PRD-GOV-002 | Exact source, PR-base snapshot, live base, integration tree, workflow checkout, protected main, and release identities remain distinct. | Implemented on protected main | PR #154 and ADR 0010; issue #132 remains Partial | +| PRD-GOV-003 | Scheduled model-assisted development preserves exact reviewed OpenCode identity but routes model capability through a released contextual-orchestrator client/gateway using virtual `orchestrator/free`; provider credentials and model selection remain owner-side bootstrap authority. | Partial | protected #200 is bootstrap evidence; active PR #208 is blocked on the immutable contextual-orchestrator authentication/bootstrap release and current exact-head gates | +| PRD-REL-001 | A commercial release binds one unchanged protected source to version/CHANGELOG/tag/package plus immutable artifact, SBOM, provenance, signature verification, reproducibility, rollback/recovery and operator-verifiable evidence. | Partial | issue #210; active Draft #217 and stacked #236 narrow structural/signature evidence but do not publish a release | + +## Non-functional requirements + +- Fail closed on malformed ownership, UUIDs, signatures, digests, timestamps, cursors, provider evidence, and persisted rows. +- Parameterize dynamic data and keep SQL structures fixed within service-owned schemas. +- Use idempotency, fencing, and version/precondition controls where replay or stale overwrite can cause loss. +- Bound request/response bodies, provider/model outputs, logs, errors, metrics, and retained evidence. +- Credentials, cookies, secret references, raw model prompts/responses, and hidden reasoning never enter public artifacts. +- Integrity digests are evidence, not authorization, confidentiality, provenance, or digital signatures. +- Core customer journeys require realistic PostgreSQL and browser evidence, not mock-only success. +- Product-owned production packages maintain exact configured coverage and beginner-readable public docstrings. +- Pending, skipped, cancelled, absent, stale, predecessor, synthetic-only, or rate-limited evidence is never passing. + +## Non-goals + +LifeOS does not claim medical diagnosis/treatment, autonomous consequential employment/credit/legal decisions, silent AI mutation, provider availability guarantees, cross-service SQL access, certification without independent evidence, arbitrary plugin code execution, or unmeasured public SLA/RPO/RTO values. + +## Release outcome + +A stable release requires one unchanged integrated protected head where product journeys, tenant/privacy boundaries, required CI/security/review, coverage/docstrings, packaging, SBOM/provenance/reproducibility, migration/rollback/recovery, accessibility/localization, deployment, and operational acceptance pass together. diff --git a/docs/PRIVACY_DATA_LIFECYCLE.md b/docs/PRIVACY_DATA_LIFECYCLE.md new file mode 100644 index 000000000..6bdc98037 --- /dev/null +++ b/docs/PRIVACY_DATA_LIFECYCLE.md @@ -0,0 +1,136 @@ +# LifeOS Privacy and Data Lifecycle + +**Status:** Implemented on active PR + +## Control model + +LifeOS preserves legitimate product utility while constraining sensitive data through tenant-derived authority, exact actor/resource/purpose/lifetime binding, least privilege, service-owned persistence, explicit secret boundaries, bounded retention, and auditable privileged access. Blanket masking is not the authorization model. + +## Data classes and owners + +- Identity: accounts, provider mappings, sessions, workspace membership, authentication provenance, and whole-request data-rights evidence. +- Planning: Goals, Projects, Tasks, search, Today, and Planning contributor receipts. +- Habit: recurring definitions/completions and Habit contributor receipts. +- Review: guided-review completion/projection records and protected Review contributor receipts. +- Calendar Integration: connection/sync metadata, active OAuth ceremony state where implemented, and opaque credential/verifier references. +- Notification: reminder occurrences, claims, delivery outcomes, inbox evidence, and active contributor receipts. +- AI Proposal: inert proposals/evidence/decisions and active contributor receipts. +- Privacy: access decisions, bounded grants, and audit events. +- Plugin Integration: installation/grant/credential-binding/delivery-origin/operator replay evidence; active hosted runtime secret material remains Vault-owned. +- Operators: bounded logs/metrics, backup, migration, CI, provenance, signature, and release evidence. + +Provider credentials, PKCE verifier plaintext, Vault credentials, browser cookies, private signing keys, raw model prompts/responses, and hidden reasoning are protected secret/transient material. They do not belong in public responses, logs, metrics, model evidence, CI artifacts, or portable exports. + +## Lifecycle rules + +1. **Collect:** accept only bounded fields required by an owning-service contract. +2. **Authorize:** derive workspace/actor from authenticated or signed context; client ownership fields are untrusted data. +3. **Use:** constrain sensitive access to explicit purpose/resource/lifetime and exact request authority. +4. **Persist:** store only under the owning service's schema/role/migrations; never cross-mutate another service's tables. +5. **Secret handling:** persist only opaque references where external credential material is required; secret plaintext lifetime is bounded to the owning adapter call. +6. **Observe:** logs, metrics, traces, CI, and review evidence remain bounded and credential-free. +7. **Retain:** classify mutable records, immutable audit/receipt evidence, consumed/expired authorization state, legal hold, and backup expiry separately. +8. **Export/Delete:** recent-authenticated whole requests invoke explicit registered service-owned contributors. +9. **Recover:** retries preserve exact idempotency/fencing authority and never fabricate terminal success or restore revoked authority. +10. **Release:** privacy claims bind one exact protected source and deployed artifact/provenance/signature identity. + +## Data-rights lifecycle + +**Status:** Partial + +Protected main includes: + +- preserved authentication ceremony time and recent-authentication enforcement; +- durable request identity and immutable terminal aggregate receipt evidence; +- tenant/requesting-user scoped non-cacheable status lookup; +- deterministic per-section and whole-export integrity evidence; +- versioned `life-os.data-rights-contributor.v1` from PR #159; +- Planning contribution from PR #179 and authenticated request-bound transport from PR #194; +- Habit contribution from PR #184 and replay-safe authenticated transport from PR #192; +- Review contribution from PR #195. + +Notification contribution in PR #198 and AI contribution in PR #199 are **Implemented on active PR**. Their active branch migrations and receipts remain non-shipped until integration. + +Issue #55 remains **Partial** because required Identity-owned erasure, Calendar, Privacy, Plugin Integration, remaining service inventory, durable asynchronous reconciliation, operator recovery, retention/legal hold, backup expiry, protected streamed/encrypted export delivery, expiry/deletion/download audit, and exact terminal participant-set completion are not all protected. + +### Deletion semantics + +No service may claim whole-workspace deletion because its own records were erased. Complete deletion requires: + +- an exact immutable request and explicit required-participant inventory; +- successful preflight for every participant; +- owner-controlled replay-safe erasure in safe order; +- post-erasure verification by every owner; +- deterministic reconciliation of partial, unavailable, and unknown outcomes; +- retention/legal-hold and backup-expiry evidence; +- one final immutable whole-product receipt only after all required evidence is reconciled. + +Unknown or missing participants fail closed. Identity orchestration never receives another service's SQL credentials. + +## Calendar credentials, OAuth state, and connections + +**Status:** Partial + +Protected main includes signed workspace sync context (PR #139), workspace/user scoped metadata persistence (PR #150), atomic local revocation (PR #153), signed `life-os.calendar-user.v1` authority (PR #155), authenticated disconnect (PR #157), exact returned lookup validation (PR #176), authenticated credential-free read (PR #189), scoped materialization port (PR #193), authenticated secret-first creation (PR #197), returned-evidence compensation (PR #201), and Calendar-owned AES-256-GCM encrypted self-hosted secret storage (PR #203). + +Connection rows retain bounded provider/account/calendar metadata and opaque secret references only. Plaintext access/refresh material exists only within the reviewed secret-store/materialization boundary. Local record revocation does not prove provider-side OAuth revocation or secret destruction. + +Active #216 rejects deployment-wide Google/CalDAV credentials as hosted user authority. Active #228 adds a bounded OAuth authorization-state lifecycle with opaque UUIDv4 state, exact workspace/user/provider/redirect binding, expiry/one-time consumption, and an opaque PKCE verifier secret reference. PKCE verifier plaintext remains outside durable Calendar metadata, and consumed repository evidence is revalidated before materialization. + +Issue #129 remains **Partial** for hosted callback/token exchange, successful post-exchange verifier cleanup, concrete Calendar-owned PostgreSQL OAuth-state runtime, refresh fencing, provider-side revoke/delete recovery, discovery/selection, scoped synchronization composition, complete KMS/runtime rotation and operator recovery. Expired/consumed/revoked ceremony or connection authority cannot be revived by rollback. + +## Plugin installation, credentials, delivery origins, and outbound delivery + +**Status:** Partial + +Protected main separates manifest intent from host authority and includes: + +- explicit installation grants from PR #151; +- restart-safe persistence from PR #169; +- opaque secret-reference credential binding and compensation from PR #172; +- exact installation-evidence validation from PR #175; +- one-time request-bound operator authority/replay evidence from PR #191; +- fail-closed operator HTTP composition from PR #196. + +Plaintext plugin credentials never belong in manifests, LifeOS persistence, public/application views, logs, metrics, prompts, CI artifacts, or audit rows. Exact replay cannot rematerialize an existing secret. Revocation ends LifeOS authority before external deletion retry and never restores authority. + +The active #130 stack preserves those boundaries while adding concrete service-owned runtime pieces: #205 host-owned normalized HTTPS origin identity, #235 Integration-owned PostgreSQL delivery-origin grants and active-installation fencing, #241 credential/revocation consistency hardening, #242 Vault KV v2 secret storage, #243/#244 authenticated Vault and one Integration-owned PostgreSQL pool, #245 concrete hosted/default-entrypoint runtime, and #250 exact signed one-time delivery-origin grant/read/revoke application authority. + +Vault holds provider plaintext; durable LifeOS rows keep opaque references only. A stored delivery origin remains bounded identity metadata, not approval for a later DNS/IP, redirect, proxy or rebinding result. #250 does not expose public delivery-origin HTTP transport and does not perform outbound HTTPS. + +Issue #130 remains **Partial** until immutable released/versioned canonical egress authority provides connect-time DNS/IP and rebinding enforcement, redirect/proxy/size/time controls, and LifeOS owns delivery attempts/outcomes, bounded retry/dead-letter, delivery-time revocation fencing, operator recovery, migration, rollback and retention semantics. + +## First-party browser data lifecycle + +**Status:** Partial + +Issue #209's active browser/BFF stack starts with #214 and includes durable Goals #229 and Weekly Review #234. Browser memory/local storage is never authoritative tenant or durable-record state. Workspace/actor authority is derived server-side, downstream service requests are signed by trusted BFF code, and returned records are validated before durable acceptance is shown. + +The commercial path remains incomplete until all Goals → Projects → Tasks → Habits → Review descendants have current-head E2E for normal/loading/empty/error/permission/conflict/recovery states, stale-response suppression, keyboard/a11y/responsive behavior, Figma/Storybook traceability, authoritative Review projections, and KO/EN/JA/ZH/VI/ES/DE/FR DB-versioned screen-key translation resources. Translation resources remain separate from ontology labels. + +## Purpose-bound access + +**Status:** Implemented on protected main + +Privacy decisions bind exact actor, workspace, resource/resource class, purpose, and lifetime. Grants are bounded, signed/consumable where applicable, and auditable. Access denial and dependency failure remain credential-free. Masking can reduce disclosure but never replaces authorization. + +## AI and model-assisted evidence + +**Status:** Partial + +AI proposals remain inert until explicit authorized decision. Browser credentials and provider secrets are not model inputs. Protected #200 authorizes only the exact reviewed OpenCode bootstrap surface. + +Active #208 routes model capability through a released contextual-orchestrator API/client and virtual `orchestrator/free`; provider keys and model selection remain owner-side bootstrap authority. LifeOS does not copy mutable owner source or fall back to direct providers if the gateway cannot authenticate or supply the required capability. Retained model evidence excludes credentials, raw prompts/responses, and hidden reasoning. Model output cannot become product authorization, independent review, merge, or release authority. + +## Integrity, secrecy, and provenance + +- SHA-256 export/manifest/receipt digests detect deterministic content change but do not provide authorization, confidentiality, signer identity, or non-repudiation. +- Secret references identify least-authority external material; possession of metadata is not permission to materialize a secret. +- Provider/plugin IDs and delivery origins are metadata, not LifeOS primary identity or connect-time network authority. +- CI/SARIF/status evidence identifies the exact inspected source/integration identity; a green umbrella status is not privacy assurance for another tree. +- Release signatures require subject binding, verifiable trust roots and key lifecycle evidence; active #217/#236 verification does not by itself create immutable release authority. +- Backup retention and physical storage expiry remain explicit and cannot be hidden behind immediate logical deletion claims. + +## Privacy failure and recovery + +Dependency outages return sanitized unavailable evidence. Partial external cleanup retains replayable recovery identity without restoring revoked authority. Ambiguous persistence winners, mismatched durable evidence, malformed rows, and unavailable receipt storage fail closed. Recovery evidence never exposes plaintext secrets or tenant payloads. Consumed OAuth/operator replay evidence and revoked installation/credential/origin authority remain consumed/revoked across retries, restarts and rollback unless a separate audited recovery contract explicitly proves otherwise. diff --git a/docs/RELEASE_AND_MIGRATION.md b/docs/RELEASE_AND_MIGRATION.md new file mode 100644 index 000000000..c4aa745be --- /dev/null +++ b/docs/RELEASE_AND_MIGRATION.md @@ -0,0 +1,139 @@ +# LifeOS Release, Migration, Rollback, and Provenance + +**Status:** Implemented on active PR + +## Release rule + +Release only from one unchanged exact protected integrated head after every applicable repository policy and product acceptance class passes together. Feature-branch, synthetic-only, queued, predecessor, or model evidence cannot authorize release. + +Issue #210 is **Partial**. Active Draft #217 and stacked #236 narrow structural release-evidence and detached-signature verification, but neither is an immutable release and neither transfers ancestor checks to a later source identity. + +## Required release evidence + +- required exact-source CI and security checks plus independently classified compatibility evidence; +- zero actionable unresolved human/CodeRabbit/GHAS/Dependabot/OpenCode/Noema/Strix findings; +- exact configured production coverage and public-docstring gates; +- browser/accessibility/localization acceptance for affected journeys; +- package/container build and smoke evidence; +- migration compatibility, rollback/forward-fix, restart, and recovery evidence; +- backup/restore integrity and unsafe-target refusal where persistent state changes; +- version plus CHANGELOG plus immutable tag/package/release identity; +- SBOM, artifact attestation/provenance, detached signature verification, reproducibility, dependency integrity, and publish verification required by policy; +- trust-root distribution plus key custody/rotation/revocation evidence where signatures are relied upon; +- operator readiness, bounded telemetry, incident/recovery acceptance, and no production stub/fake-success path; +- installed/runtime artifact verification against the exact protected release source. + +## Evidence identity + +Release decisions retain separate: + +- `source_head_sha`; +- `pr_base_snapshot_sha`; +- independently resolved `live_base_tip_sha`; +- integration/synthetic tree identity; +- `workflow_checkout_sha`; +- `protected_main_sha`; +- `release_source_sha`; +- artifact/checksum/SBOM/provenance/signature identities. + +PR #154 protects local source/live-base separation. Issue #132 remains **Partial** for central reusable scanner taxonomy. A status is release evidence only for the tree and artifact it actually inspected. Self-retiring verification workflows may prove an exact parent and then delete only themselves through an ordinary descendant; that deletion does not convert the parent proof into unrelated descendant merge/release authority. + +## Service-owned schema migrations + +Every service sequences its own migrations under its own role. Cross-service migrations and direct cross-schema mutation are prohibited. Migrations preserve UUIDv4, tenant scope, immutability, secret-reference, replay, concurrency, and recovery invariants. + +For risky migrations: + +1. add failing migration/compatibility/privilege/restart evidence where practical; +2. define exact preconditions and current data-shape assumptions; +3. stage additive columns/constraints/backfill/validation where required; +4. prove old/new application compatibility for rolling deployment claims; +5. define rollback or explicit forward-fix behavior; +6. prove retry/restart/duplicate/malformed/corrupt evidence handling; +7. verify backup/restore and retention interactions; +8. record irreversible effects and recovery limits. + +## Active migration and release line + +| Pull request | Status | Migration/release obligation | +| --- | --- | --- | +| PR #195 | Implemented on protected main | Review-owned data-rights migration/contributor is shipped authority | +| PR #198 | Implemented on active PR | Notification erasure migration, claims/outcome immutability, owner-only deletion/replay evidence | +| PR #199 | Implemented on active PR | AI erasure migration, append-only trigger authority, cursor compatibility, owner-only atomic deletion | +| PR #200 | Implemented on protected main | no product schema; exact pinned OpenCode bootstrap and narrow lifecycle-script policy only | +| PR #216 | Implemented on active PR | hosted Calendar rejects deployment-global provider credential authority; no data migration by itself | +| PR #228 | Implemented on active PR | OAuth authorization-state/PKCE persistence semantics require eventual Calendar-owned PostgreSQL migration/runtime, expiry and cleanup acceptance | +| PR #235 | Implemented on active PR | Integration-owned PostgreSQL delivery-origin grant persistence and active-installation fencing | +| PR #242 | Implemented on active PR | Vault KV v2 adapter keeps provider plaintext outside LifeOS durable rows | +| PR #244 / #245 | Implemented on active PR | one Integration-owned PostgreSQL pool plus concrete hosted/default-entrypoint runtime; retained real-server ancestor acceptance is not current-head release authority | +| PR #250 | Implemented on active PR | signed delivery-origin operator application; exact route verification and full-suite runtime proof must complete before the temporary verifier retires | +| PR #217 | Implemented on active PR | machine-readable structural release-evidence index/validation | +| PR #236 | Implemented on active PR | detached Ed25519 release-evidence verification/operator CLI | + +Active work cannot enter a release until integrated and revalidated on the final protected head. + +## Application rollback + +Application rollback restores only reversible application/configuration state. It never claims to undo committed database migrations, destructive erasure, delivered notifications/calendar mutations, provider revocations, Vault/secret-store writes/deletes, or externally published release artifacts without a tested compensating contract. + +When rollback would reintroduce a binary unable to understand additive durable fields, OAuth-state semantics, delivery-origin grants, cursor semantics, receipt rows, one-time replay records, or current signature versions, forward-fix or staged compatibility is required instead. + +## Data-rights migration and release + +PR #159 protects the contributor contract. Planning PR #179/PR #194, Habit PR #184/PR #192, and Review PR #195 are protected contributors/transports. Notification #198 and AI #199 remain active. + +Issue #55 remains **Partial** until exact participant inventory, remaining owners, durable reconciliation/recovery, retention/legal hold, backup expiry, protected artifact streaming/encryption/expiry/deletion/download audit, and terminal whole-right receipt evidence pass on one protected head. + +No release may claim complete export/deletion from partial or unknown participants. + +## Calendar credential migration + +Protected PR #150, PR #153, PR #155, PR #157, PR #176, PR #189, PR #193, PR #197, PR #201 and PR #203 establish metadata, authority, disconnect, validation, read, materialization, creation/compensation and Calendar-owned encrypted self-hosted credential storage. + +Active #216 prevents process-global Google/CalDAV values from becoming hosted user authority. Active #228 adds bounded one-time OAuth state/PKCE authority with opaque verifier handles. Issue #129 still requires hosted callback/token exchange, successful verifier cleanup, Calendar-owned PostgreSQL OAuth-state runtime, refresh fencing, provider revoke/delete recovery, discovery/selection, scoped synchronization and end-to-end secret/KMS lifecycle acceptance. Rollback must preserve revoked/consumed authority and cannot resurrect deleted provider/KMS secrets or consumed authorization state. + +## Plugin runtime migration + +Protected PR #151, PR #169, PR #172, PR #175, PR #191, and PR #196 establish grants, durable installation, opaque secret binding, exact evidence, one-time operator replay, and fail-closed HTTP composition. + +The active #130 line adds host-owned origin identity (#205), Integration-owned PostgreSQL grant persistence/fencing (#235), credential/revocation hardening (#241), Vault KV v2 secret storage (#242), authenticated Vault/hosted PostgreSQL composition (#243/#244/#245), and signed delivery-origin operator application authority (#250). None of these may infer connect-time network authority from a stored manifest or origin. + +Issue #130 remains **Partial** until an immutable released/versioned canonical egress contract supplies connect-time DNS/IP and rebinding enforcement, redirect/proxy policy, bounded response/time behavior, and LifeOS owns durable delivery attempts/outcomes, retry/dead-letter, revocation fencing and operator recovery. Rollback cannot restore revoked installation/grant/credential authority or reveal/re-materialize a deleted Vault secret without an explicit reviewed recovery contract. + +## First-party buyer-journey release + +Issue #209 is **Partial**. Active first-party BFF/workspace descendants begin with #214 and include durable Goals #229 and Weekly Review #234. No release may advertise the complete buyer journey until the final dependency-ordered head has real browser E2E for normal/loading/empty/error/permission/responsive/interaction states, keyboard/focus/reduced-motion/a11y, Figma/Storybook traceability, authoritative Review read projections, and KO/EN/JA/ZH/VI/ES/DE/FR translation-ledger/font/text-expansion acceptance. + +## Package and model-automation changes + +Exact pinned development/review tooling is supply-chain-sensitive. Protected PR #200 allows only the reviewed `opencode-ai` bootstrap lifecycle needed to materialize the exact executable; it does not grant direct model-provider routing authority. + +Active #208 preserves exact OpenCode identity but routes model capability through contextual-orchestrator and virtual `orchestrator/free`. It cannot integrate or enter release evidence while the owner authentication/bootstrap contract is unrepaired or consumed from an unreleased mutable owner revision. The required order is owner RED → causal owner fix → immutable reviewed owner release → exact LifeOS consumer bump → hosted consumer acceptance. LifeOS does not copy mutable owner source or select a direct-provider fallback. + +## Release-evidence active stack + +Draft #217 validates a machine-readable exact release-evidence index and fails closed on malformed source/artifact/checksum/provenance/signature coverage and invalid nightly identity. Stacked Draft #236 adds detached Ed25519 verification and a bounded operator CLI. These are evidence-verification mechanisms, not publishing authority. + +Before #210 can close, one exact protected `release_source_sha` must produce and verify: + +1. source version and CHANGELOG identity; +2. immutable Git tag/release plus package/image identity; +3. checksums and SBOM bound to the actual retained artifacts; +4. provenance/attestation and detached signatures with explicit subject coverage; +5. distributed/verifiable trust roots and key lifecycle/custody evidence; +6. reproducible rebuild/compare evidence appropriate to the artifact class; +7. migration/upgrade/rollback/restore/recovery acceptance; +8. installed buyer-path/runtime verification; +9. exact protected-head CI/security/review/coverage/docstrings/accessibility/localization acceptance. + +Ancestor GREEN, a structurally valid evidence index, or a signature verifier does not satisfy this denominator alone. + +## Versioning and CHANGELOG + +Keep unreleased behavior under `CHANGELOG.md` -> `Unreleased`. Create version, tag, release notes, packages/images, SBOM, provenance and detached signatures only after the exact protected source passes release acceptance. Verify published artifact digests and installed/runtime behavior against recorded source/provenance before announcing release. + +Documentation-only governance changes must not be described as shipped product capability. Conversely, protected behavior must not remain labeled active after integration. + +## Recovery exercises + +Release acceptance includes relevant backup/restore, migration failure, stale-write conflict, worker replay, OAuth ceremony expiry/replay, provider outage, Vault/KMS partial failure, data-rights stuck participant, plugin secret cleanup, egress/delivery retry recovery, provenance/signature mismatch, trust-root/key-rotation failure, rollback, and forward-fix exercises. No fixed public RPO/RTO is claimed without measured deployment-specific evidence. diff --git a/docs/STANDARDS_TRACEABILITY.md b/docs/STANDARDS_TRACEABILITY.md new file mode 100644 index 000000000..c2318407b --- /dev/null +++ b/docs/STANDARDS_TRACEABILITY.md @@ -0,0 +1,83 @@ +# LifeOS Standards and Research Traceability + +**Status:** Implemented on active PR + +This document records normative/current standards and repository-wide research used for durable LifeOS decisions. Drafts, preprints, vendor claims, and product-release evidence are labeled explicitly and do not silently replace final standards or peer-reviewed evidence. Publication status below was refreshed against primary sources in September 2026. + +## Standards matrix + +| Source | Publication status | LifeOS use | +| --- | --- | --- | +| IETF RFC 9562, *Universally Unique IDentifiers (UUIDs)* | Final RFC, 2024 | UUIDv4 identifier syntax/semantics; LifeOS intentionally chooses version 4 rather than version 7 | +| IETF RFC 9700 / BCP 240, *Best Current Practice for OAuth 2.0 Security* | Final Best Current Practice, January 2025 | exact redirect matching, state/PKCE/token and mix-up/open-redirect security posture; unsafe legacy patterns are not copied into Calendar authority | +| W3C, *Web Content Accessibility Guidelines (WCAG) 2.2* | W3C Recommendation, latest Recommendation revision December 2024 | keyboard/focus/status/authentication accessibility and browser acceptance expectations | +| ISO/IEC 40500:2025, *W3C Web Content Accessibility Guidelines (WCAG) 2.2* | Published international standard, October 2025; identical to the October 2023 WCAG 2.2 text | international-standard traceability for the material UI acceptance baseline; W3C's later WCAG 2.2 Recommendation remains the current web-standard reference | +| PostgreSQL 18, §13.2 *Transaction Isolation* | Current supported PostgreSQL major-version documentation; 18.6 is the current minor release as of August 13, 2026 | Read Committed command-snapshot semantics; `ON CONFLICT DO NOTHING` can suppress an insert because of a concurrent winner not visible to that statement snapshot, so exact replay requiring the winner uses a subsequent bounded read rather than same-statement visibility assumptions | +| NIST SP 800-218, *Secure Software Development Framework (SSDF) Version 1.1* | Final, 2022 | secure-development, provenance and vulnerability-prevention practices | +| NIST SP 800-218 Rev. 1 / SSDF Version 1.2 | Initial Public Draft published December 17, 2025; public comment closed January 30, 2026; not final as of the September 2026 verification | watch item only; does not replace SSDF 1.1 normative use until NIST publishes a final revision | +| NIST AI 100-1, *Artificial Intelligence Risk Management Framework 1.0* | Final, 2023 | AI governance/evidence/risk framing | +| NIST AI 600-1, *AI RMF: Generative Artificial Intelligence Profile* | Final, 2024 | GenAI prompt/provider/evidence risk controls | + +## Repository-wide model-orchestration research matrix + +| Source | Publication status | LifeOS use | +| --- | --- | --- | +| Sakana AI, *Sakana Fugu: One model to command them all* | Primary vendor product/technical release, June 22, 2026; Fugu-Ultra v1.1 product update July 24, 2026 | motivates measuring direct-route versus dynamically coordinated expert execution rather than assuming one topology; vendor benchmark claims are not independent research evidence | +| Nielsen et al., *Learning to orchestrate agents in natural language with the Conductor* | Reported by Sakana AI as an ICLR 2026 paper in its June 2026 Fugu release | motivates explicit communication topology, targeted instructions, recursive selection and test-time-scaling evidence | +| Xu et al., *TRINITY: An evolved LLM coordinator* | Reported by Sakana AI as an ICLR 2026 paper in its June 2026 Fugu release | motivates explicit Thinker/Worker/Verifier roles and multi-turn coordination evidence | +| Xu et al., *Rethinking the value of multi-agent workflow: A strong single agent baseline* | arXiv preprint / conference-submission evidence in the repository bibliography; no final publication status is inferred here | counterevidence requiring a strong single-agent baseline before claiming value from homogeneous multi-agent workflows | + +These sources motivate the dimensions measured by LifeOS; they do **not** establish universal multi-agent superiority. ADR 0012 makes a repository-specific decision: a strong single-model route is the mandatory comparison baseline, deeper orchestration is admitted only from retained LifeOS evidence under reasonably comparable budgets, and deterministic LifeOS authorization/evaluation/review/merge/release authority remains separate from model execution. + +The runtime/provider implementation boundary is independently governed by the canonical `contextual-orchestrator` owner. LifeOS does not use the presence of any provider-specific research or benchmark as authority to seed provider credentials, choose provider/model/group names, or bypass the released gateway contract. Active PR #208 consumes the owner through virtual `orchestrator/free`; provider and multimodal capability discovery remain owner-side. + +## Decision traceability + +- **UUIDv4 invariant:** RFC 9562 permits UUID version 4 and defines modern UUID representation; LifeOS's choice of opaque random UUIDv4 is a repository architecture decision, not a claim that v4 is universally superior. +- **OAuth security:** RFC 9700 remains BCP 240 and requires exact registered redirect matching for redirect-based flows (with its documented native-localhost exception) and rejects unsafe open redirectors. Identity and active Calendar OAuth work bind state/PKCE/redirect/provider/user/workspace evidence accordingly. Issue #129 must not copy browser-login credentials or deployment-global provider credentials into end-user Calendar authority. +- **Accessibility:** material PWA journeys target current WCAG 2.2 keyboard/focus/non-color-only/status/authentication requirements. ISO/IEC 40500:2025 adds international-standard traceability but does not justify pinning LifeOS to the older October 2023 text when W3C publishes a newer WCAG 2.2 Recommendation revision. +- **PostgreSQL replay concurrency:** PostgreSQL's current Read Committed documentation explicitly distinguishes statement snapshots and notes that `INSERT ... ON CONFLICT DO NOTHING` can decline an insert because of a concurrent transaction whose effects are not visible to that INSERT snapshot. Active #252 therefore does not assume a same-statement fallback SELECT can always observe the exact idempotency winner; after a no-row conflict it performs a second exact-scope command and validates the returned durable evidence fail closed. +- **Secure SDLC:** exact-head CI/security evidence, immutable action pins, least privilege, bounded untrusted input, provenance and root-cause remediation align with final SSDF 1.1. SSDF 1.2 remains an Initial Public Draft at the latest primary-source verification and therefore remains a watch item. +- **AI governance:** model output remains untrusted and inert, deterministic authorization/validation is separate, gateway/provider availability is not fabricated as merge success, and retained artifacts exclude secrets/raw prompts/responses/hidden reasoning. +- **Test-time compute:** ADR 0012 defines the strong-route baseline and explicit reasoning/stage/decomposition/recursion/role/access-topology dimensions. Fugu/Conductor/TRINITY are research/product evidence for orchestration dimensions, not direct runtime dependencies or provider authority. +- **Model runtime ownership:** all LifeOS model-backed automation and product-facing model capability consumes a reviewed immutable contextual-orchestrator API/client/schema. GitHub Actions uses virtual `orchestrator/free` plus gateway authentication only; provider credentials and discovery are canonical-owner concerns. + +## Research traceability + +Feature-specific peer-reviewed and technical research remains in `docs/research/` and approved `docs/superpowers/specs/` documents. Historical NIM-specific specs/plans describe the evidence available when they were written and are not promoted into current provider-routing authority. When a research result becomes a repository-wide architectural requirement, an ADR links the primary source, assumptions, alternatives and executable acceptance evidence. + +Fugu/Conductor/TRINITY/single-agent research remains relevant to TTC ablation design. The live consumer architecture is separately constrained by ADR 0012, current LifeOS contracts, and the released contextual-orchestrator owner boundary. Unsupported orchestration controls remain explicit rather than simulated. + +## APA 7 references + +Internet Engineering Task Force. (2024). *Universally Unique IDentifiers (UUIDs)* (RFC 9562). RFC Editor. https://doi.org/10.17487/RFC9562 + +International Organization for Standardization & International Electrotechnical Commission. (2025). *Information technology—W3C Web Content Accessibility Guidelines (WCAG) 2.2* (ISO/IEC 40500:2025). https://www.iso.org/standard/91056.html + +Lodderstedt, T., Bradley, J., Labunets, A., & Fett, D. (2025). *Best current practice for OAuth 2.0 security* (RFC 9700; BCP 240). RFC Editor. https://doi.org/10.17487/RFC9700 + +National Institute of Standards and Technology. (2022). *Secure Software Development Framework (SSDF) version 1.1: Recommendations for mitigating the risk of software vulnerabilities* (NIST SP 800-218). https://doi.org/10.6028/NIST.SP.800-218 + +National Institute of Standards and Technology. (2025). *Secure Software Development Framework (SSDF) version 1.2: Recommendations for mitigating the risk of software vulnerabilities* (NIST SP 800-218 Rev. 1, Initial Public Draft). https://csrc.nist.gov/pubs/sp/800/218/r1/ipd + +National Institute of Standards and Technology. (2023). *Artificial Intelligence Risk Management Framework (AI RMF 1.0)* (NIST AI 100-1). https://doi.org/10.6028/NIST.AI.100-1 + +National Institute of Standards and Technology. (2024). *Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile* (NIST AI 600-1). https://doi.org/10.6028/NIST.AI.600-1 + +Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2026). *Learning to orchestrate agents in natural language with the Conductor* [ICLR 2026 paper]. https://arxiv.org/abs/2512.04388 + +PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Transaction isolation*. https://www.postgresql.org/docs/18/transaction-iso.html + +PostgreSQL Global Development Group. (2026, August 13). *PostgreSQL 18.6, 17.11, 16.15, 15.19, 14.24 and 19 Beta 3 released!*. https://www.postgresql.org/about/news/postgresql-186-1711-1615-1519-1424-and-19-beta-3-released-3365/ + +Sakana AI. (2026, June 22). *Sakana Fugu: One model to command them all* [Product and technical release]. https://sakana.ai/fugu-release/ + +Sakana AI. (2026, July 24). *Announcing Fugu-Ultra v1.1 and Claude Code interface for Fugu* [Product release update]. https://sakana.ai/fugu-1-1-claude-code-interface/ + +World Wide Web Consortium. (2024, December 12). *Web Content Accessibility Guidelines (WCAG) 2.2* [W3C Recommendation]. https://www.w3.org/TR/WCAG22/ + +World Wide Web Consortium. (2025, October 21). *Web Content Accessibility Guidelines 2.2 approved as ISO/IEC international standard*. https://www.w3.org/press-releases/2025/wcag22-iso-pas/ + +Xu, J., Koesdwiady, A., Bei, S., Han, Y., Huang, B., Wang, D., Chen, Y., Wang, Z., Wang, P., Li, P., & Ding, Y. (2026). *Rethinking the value of multi-agent workflow: A strong single agent baseline* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2601.12307 + +Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2026). *TRINITY: An evolved LLM coordinator* [ICLR 2026 paper]. https://doi.org/10.48550/arXiv.2512.04695 diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md new file mode 100644 index 000000000..d275fd8de --- /dev/null +++ b/docs/TEST_STRATEGY.md @@ -0,0 +1,158 @@ +# LifeOS Test Strategy + +**Status:** Implemented on active PR + +## Principles + +LifeOS tests prove domain behavior, authority, recovery, and evidence identity—not only implementation reachability. Source changes follow realistic RED -> smallest root-cause GREEN -> focused/full validation. Required checks are attributed to the exact revision and checkout they inspect. + +A test harness is part of the evidence contract. Missing runtime builds, wrong checkout identity, skipped environment prerequisites, or purpose-workflow defects are root-caused and repaired rather than papered over with `PYTHONPATH`, broad skips, sample reduction, stale evidence, or gate weakening. + +## Test layers + +### Unit and domain + +Validate UUIDv4, authority derivation, exact method/path signing, freshness, one-time evidence, digest/cursor normalization, recurrence, idempotency, fencing, stale preconditions, state transitions, and bounded failure classes with deterministic tests. + +### PostgreSQL integration + +Use real disposable PostgreSQL for service-owned migrations and repositories, including: + +- tenant isolation and fixed parameterized SQL; +- exact returned identity/evidence validation; +- transaction rollback and restart durability; +- concurrent/replayed requests and advisory/fencing semantics; +- immutable proposal/decision/reminder/receipt/audit evidence; +- owner-only destructive data-rights functions and post-erasure verification; +- privilege denial for ordinary application roles; +- compensation/recovery records where external secret material is introduced; +- active Calendar OAuth-state and Plugin origin/credential/grant persistence when those migrations are under test. + +Mock-only success is insufficient for persistence, privilege, concurrency, replay, migration, or recovery claims. + +### HTTP and application integration + +Exercise actual authenticated/signed boundaries, exact method/path/actor/workspace binding, malformed JSON/content type/UUID/signature/cursor, replay, conflict, not-found isolation, response-size/schema validation, dependency outage, and credential-free problem mapping. Tests must prove browser-selected ownership cannot create authority. + +Protected examples include Planning/Habit Today composition, Calendar read/create/disconnect/materialization, Planning/Habit/Review data-rights transport, integration events, and plugin operator HTTP composition. Active contracts add Calendar OAuth ceremony boundaries, first-party BFF/workspace flows, Vault-backed Integration composition, and delivery-origin operator application authority without promoting those active surfaces to protected truth. + +### Browser acceptance + +Use Playwright for material user journeys. The #209 commercial path is dependency ordered Goals → Projects → Tasks → Habits → Review and requires: + +- authenticated first-party BFF transport with no browser-derived tenant authority; +- durable create/read/update evidence rather than optimistic identifiers presented as accepted state; +- normal, loading, empty, error, permission, conflict and recovery states; +- desktop/mobile/intermediate widths without overflow, clipping or unusable touch targets; +- keyboard traversal, visible focus, semantic names, live-state announcements, contrast and reduced-motion behavior; +- Figma/Storybook component/page traceability where material UI is introduced; +- KO/EN/JA/ZH/VI/ES/DE/FR screen-key resource parity, CJK/font fallback and text-expansion acceptance; +- stale overlapping-response protection and prior-safe-evidence preservation on failure. + +Browser-local drafts remain visibly non-durable until server acceptance. A focused component test or ancestor browser run does not prove the final stacked current head. + +### Security regression + +Cover: + +- tenant/actor/resource substitution; +- signed-context replay, stale/future evidence, and exact request binding; +- SQL structure/privileges and corrupt persisted evidence; +- secret/reference/log/error/artifact leakage; +- OAuth state/redirect/origin/PKCE expiry, replay, consumed-row substitution and verifier lifetime; +- provider response limits and credential material lifetime; +- AI prompt injection, benign utility, inert proposal enforcement and model-gateway authentication/bootstrap failure; +- plugin manifest self-escalation, credential/origin revocation TOCTOU, signed route confusion, hostile URL/SSRF/DNS-rebinding cases before outbound networking can ship; +- Vault plaintext/credential non-retention in durable LifeOS metadata and public evidence; +- package lifecycle-script allowlisting, workspace runtime dependency builds, and exact dependency pins; +- source/base/integration/SARIF/status/release identity attribution; +- release evidence subject/signature coverage, trust-root/key-lifecycle and provenance mismatch. + +### Backup, deployment, migration, and release + +Executable tests validate Compose/reference deployment sources, liveness/readiness, graceful shutdown, backup checksum/restore refusal, migration compatibility, rollback/forward-fix, package/container build, SBOM/provenance/signature/reproducibility, and publish/install verification. + +## Coverage and docstrings + +Packages with exact configured gates retain meaningful 100% statement, branch, function, and line coverage. Coverage cannot be satisfied through deleted behavior, broad exclusions, unreachable branches, or mock-only assertions. Public production declarations require beginner-readable explanatory documentation under the owning package's gate. + +A green full test suite is not a 100% coverage claim. Coverage percentages and denominator evidence must be produced separately when the owning gate requires them. + +## Authority and replay matrices + +| Domain | Required adversarial/concurrency evidence | Status | +| --- | --- | --- | +| Today | duplicate idempotency, conflicting reuse, stale precondition, concurrent create/update, cleanup | Implemented on protected main | +| Planning/Habit/Review routes | workspace/actor/method/path substitution, stale/future signature, replay as applicable | Implemented on protected main | +| First-party buyer path | BFF authority, durable response validation, stale response suppression, all UI states/a11y/locales/current-head E2E | Partial | +| Notification | duplicate claim/delivery, expiry/recovery, immutable outcome | Implemented on protected main | +| AI proposals | malformed model output, stale/replayed decision, explicit confirmation, no mutation authority | Implemented on protected main | +| Privacy | purpose/resource/lifetime, exact expiry, single-use grant, bounded audit | Implemented on protected main | +| Data rights | request/idempotency collision, deterministic export, owner preflight/erase/verify, participant omission, whole-right non-completion | Partial | +| Calendar | exact connection/workspace/user evidence, secret-first compensation, handle substitution, OAuth state/PKCE replay/expiry, provider/KMS outage, local-vs-provider revoke | Partial | +| Plugin | manifest/grant conflict, exact installation/binding/origin/operator evidence, credential compensation, replay/revoke/TOCTOU; Vault+PostgreSQL lifecycle; delivery SSRF/retry when introduced | Partial | +| Release | index structure, artifact/checksum/provenance/signature subject binding, crypto verification, immutable publication, reproducibility, rollback/recovery | Partial | + +## Data-rights acceptance + +PR #159 protects the shared contract. Protected Planning evidence comes from PR #179 and PR #194; protected Habit evidence comes from PR #184 and PR #192; protected Review evidence comes from PR #195. PR #198 and PR #199 are **Implemented on active PR** and require exact-head real PostgreSQL, coverage, docstrings, security/review, and live-base compatibility before integration. + +Whole-product tests must fail when any required participant is missing, duplicate, unavailable, malformed, cross-tenant, partially completed, unverified after erasure, or absent from the exact participant registry. Export integrity evidence never substitutes for authorization or protected delivery. + +## Calendar acceptance + +Protected boundaries from PR #157, PR #176, PR #189, PR #193, PR #197, PR #201 and PR #203 require tests for authenticated disconnect, exact returned lookup identity, credential-free read, secret-handle validation/materialization, secret-first create, returned-evidence compensation and encrypted self-hosted storage. + +Active #216 must prove hosted multi-user runtime rejects deployment-wide Google/CalDAV credentials instead of silently treating them as user authority. Active #228 must prove exact workspace/user/provider/redirect state binding, bounded expiry, one-time consumption, hostile consumed-row rejection before PKCE secret materialization, and secret-store cleanup/partial-failure semantics within its implemented boundary. + +The remaining #129 lifecycle requires real PostgreSQL OAuth-state migration/runtime evidence, callback/token exchange, successful post-exchange verifier cleanup, refresh single-flight/fencing, provider cleanup partial-failure recovery, discovery/selection bounds, scoped sync, restart/rotation, and no process-global credential fallback. + +## Plugin acceptance + +Protected PR #151, PR #169, PR #172, PR #175, PR #191, and PR #196 require tests for explicit grants, durable exact installation identity, opaque credential binding, conflicting-winner compensation, one-time operator authority/replay, malformed JSON, unavailable composition, and credential-free errors. + +The active #130 stack additionally requires real Integration-owned PostgreSQL and Vault KV v2 lifecycle acceptance across installation, credential create/exact replay, runtime restart, installation/credential revocation and cleanup. Active delivery-origin persistence must reject inactive/mismatched installation evidence and retain exact normalized HTTPS origin identity. Active #250 must prove signed grant/read/revoke application authority, cross-route signature rejection, exact lowercase UUIDv4 route/method admission and unavailable-composition failure. + +A temporary #250 verifier must build `@life-os/plugin-sdk` before the full Integration suite because its runtime package entry is `dist/index.js`. Failing to build that declared dependency is a harness RED, not grounds to skip the full suite. The purpose workflow may delete only itself after the exact proof passes. + +Before #130 outbound delivery can ship, tests must cover loopback/RFC1918/ULA/link-local/cloud-metadata/IPv4-mapped/encoded addresses, DNS rebinding, connect-time resolution, redirect/proxy policy, TLS/HTTP failure, byte/time/rate/concurrency bounds, signing/rotation, delivery attempt/outcome durability, retry/dead-letter, restart, and revocation fencing. + +## Model-assisted development acceptance + +Protected #200 proves only the exact reviewed OpenCode bootstrap surface. Active #208 must fail closed if the released contextual-orchestrator client/gateway cannot authenticate or supply required capability. The required acceptance sequence is canonical-owner RED, owner causal fix, immutable reviewed owner release, exact LifeOS consumer bump, then hosted consumer acceptance through `orchestrator/free`. Provider/model/group hard-coding, mutable source copy, direct-provider fallback, or elapsed-time-only reasoning/tool termination are not substitutes. + +## Documentation consistency + +Machine-checkable contracts validate: + +- required canonical files and README/index links; +- local Markdown links; +- exact maturity vocabulary; +- ADR index/targets/status/required sections; +- balanced Markdown/Mermaid fences; +- protected chronology versus active PR scope; +- current buyer gaps #55/#129/#130/#209/#210 and closed/superseded issue state; +- conceptual versus persisted/active data-model labels; +- UUIDv4, service ownership, browser durability, inert AI, purpose-bound privacy, and evidence-identity invariants; +- model credential/orchestration/review authority boundaries; +- stale predecessor PRs cannot reappear as active truth. + +## Evidence identity + +A check must identify whether it inspected: + +1. exact contributor source head; +2. PR-base snapshot; +3. independently resolved live-base tip; +4. synthetic/integration tree; +5. workflow checkout/source identity; +6. protected main; +7. release source/artifact/provenance identity. + +Evidence from one class cannot satisfy another. PR #154 protects local source/live-base separation. Issue #132 remains **Partial** for residual central reusable scanner checkout/SARIF/status taxonomy. + +Pending, queued, skipped, cancelled, absent, neutral, failed, stale, predecessor, synthetic-only, model-only, or rate-limited evidence is non-passing. + +## Release acceptance + +Issue #210 remains Partial. Active Draft #217 provides structural release-evidence validation and stacked #236 detached Ed25519 verification. Final acceptance requires one unchanged protected release source to pass required CI/security/review, exact configured coverage/docstrings, browser/accessibility/localization, package/container build, version/CHANGELOG/tag/immutable publication, checksums, SBOM/provenance/signatures/trust-root lifecycle/reproducibility, compatibility, migration/rollback/recovery, backup/restore, installed runtime/buyer-path verification, and protected-main operational acceptance together. diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md new file mode 100644 index 000000000..d4199b9b5 --- /dev/null +++ b/docs/THREAT_MODEL.md @@ -0,0 +1,169 @@ +# LifeOS Threat Model + +**Status:** Implemented on active PR + +Protected-main source and tests are the current control evidence. Active PR controls remain non-shipped until integration. + +## Assets + +- tenant-owned Planning, Habit, Review, Calendar, Notification, AI, Privacy, and Plugin data; +- account, workspace membership, sessions, and authentication-age provenance; +- provider credentials, secret references, signing/MAC keys, PKCE verifier material, Vault/KMS authority, and gateway authentication material; +- AI proposals, evidence, and explicit decisions; +- data-rights requests, contributor exports/receipts, aggregate terminal evidence, and protected artifacts; +- plugin installations, grants, credential bindings, delivery-origin grants, operator replay evidence, and future delivery outcomes; +- database migrations, backups, release artifacts, SBOM/provenance/signatures, CI/SARIF/status evidence, and operator recovery records. + +## Trust boundaries + +```mermaid +flowchart LR + Browser[Untrusted browser] --> Web[Authenticated Web / Gateway] + Web --> Identity[Identity] + Web --> Planning[Planning] + Web --> Habit[Habit] + Web --> Review[Review] + Web --> Calendar[Calendar Integration] + Web --> Notification[Notification] + Web --> AI[AI Proposal] + Web --> Privacy[Privacy] + Web --> Plugin[Plugin Integration] + + Identity --> IDB[(Identity-owned DB)] + Planning --> PDB[(Planning-owned DB)] + Habit --> HDB[(Habit-owned DB)] + Review --> RDB[(Review-owned DB)] + Calendar --> CDB[(Calendar-owned DB)] + Notification --> NDB[(Notification-owned DB)] + AI --> ADB[(AI-owned DB)] + Privacy --> VDB[(Privacy-owned DB)] + Plugin --> XDB[(Integration-owned DB)] + + Calendar --> CalendarProvider[Untrusted calendar provider] + Calendar --> CalendarSecrets[Calendar secret store / KMS] + AI --> Orchestrator[contextual-orchestrator] + Orchestrator --> Model[Untrusted model providers] + Plugin --> Vault[Vault / Plugin secret store] + Plugin -. future bounded delivery .-> Egress[Canonical egress authority] + Egress -. connect-time policy .-> Network[Untrusted network endpoint] +``` + +Co-location on one PostgreSQL cluster never creates shared-table authority. Every service owns its schema/role/migrations and cannot borrow another service's credentials. A stored URL/origin is identity evidence only; it is not resolved-network authority. + +## Threats and controls + +| Threat | Boundary | Primary controls | Status | +| --- | --- | --- | --- | +| Tenant/workspace/actor injection | Browser -> services | authenticated session or exact signed context; reject client-selected authority | Implemented on protected main | +| Method/path replay of signed context | Gateway -> services | exact method/path/version/issuance binding; one-time evidence where destructive | Implemented on protected main | +| Cross-service database privilege confusion | Service -> PostgreSQL | service-owned roles/schemas/migrations; no cross-table access | Implemented on protected main | +| OAuth state/redirect confusion | Identity -> provider | bounded transaction, state, provider, redirect/origin validation | Implemented on protected main | +| Calendar workspace/user substitution | Gateway -> Calendar | `life-os.calendar-user.v1`, exact returned evidence validation | Implemented on protected main | +| Hosted process-global Calendar credential substitution | Runtime -> Calendar | active #216 rejects deployment-wide Google/CalDAV credentials as user authority | Implemented on active PR | +| Calendar OAuth state/PKCE replay or verifier disclosure | Browser/provider -> Calendar/secret store | active #228 exact workspace/user/provider/redirect binding, expiry/one-time state, opaque verifier handle, consumed-row revalidation before materialization | Implemented on active PR | +| Orphaned calendar credential material | Calendar -> secret store/repository | secret-first persistence, reverse-order compensation, no caller-visible handles | Implemented on protected main | +| Calendar credential theft/replay | Calendar -> provider/KMS | opaque references, encrypted self-hosted store #203; hosted callback/token/refresh/provider-cleanup lifecycle remains incomplete | Partial | +| Stale multi-device overwrite | Web -> Planning | strong preconditions, versioning, ordered locks, explicit reconciliation | Implemented on protected main | +| First-party browser durable-state fabrication | Browser -> BFF/services | server-derived workspace authority, exact signed downstream request, strict returned evidence, stale/duplicate rejection | Implemented on active PR | +| Review/Habit/Planning authority replay | Gateway/contributor -> owner | exact request-bound signatures and atomic destructive replay guards | Implemented on protected main | +| Reminder duplicate delivery | Notification worker | fenced/expiring claims, idempotency, immutable outcomes | Implemented on protected main | +| AI prompt injection or silent mutation | Model -> AI/product | untrusted inert proposal, deterministic validation, explicit decision, no mutation bus | Implemented on protected main | +| Model credential/provider-routing escalation | LifeOS -> contextual-orchestrator/model | active #208 routes through virtual `orchestrator/free`; provider credentials remain owner-side bootstrap material; immutable owner release/authentication remains required | Partial | +| Sensitive-data overexposure | Privacy/public/CI | tenant/purpose/resource/lifetime grants; bounded credential-free evidence | Implemented on protected main | +| Data-rights participant omission/false completion | Identity -> contributors | explicit versioned registry, owner verification, immutable aggregate receipt | Partial | +| Data-rights cross-tenant export/erase | Identity/contributor -> owner DB | exact workspace/actor/request binding, owner SQL only, deterministic evidence | Partial | +| Plugin manifest self-escalation | Manifest -> host | explicit host grant subset; manifest is intent only | Implemented on protected main | +| Plugin credential leakage | Host -> Vault/DB/public view | plaintext only at Vault/secret-store port; durable rows retain opaque references; compensation/revocation fencing | Implemented on active PR | +| Plugin installation/revocation TOCTOU | Integration app -> repository/Vault | active stack revalidates installation authority around credential/origin admission and fails closed on revoked/mismatched durable evidence | Implemented on active PR | +| Plugin operator replay/identity substitution | Operator -> integration | exact one-time signed request, durable atomic replay evidence, fail-closed HTTP | Implemented on protected main | +| Delivery-origin signature confusion | Operator -> Integration | active #250 exact canonical lowercase UUIDv4 delivery-origin paths and POST/GET/POST methods; cross-route signatures rejected | Implemented on active PR | +| Stored delivery origin promoted to network authority | Integration -> egress | explicit separation of durable origin identity from connect-time DNS/IP/redirect/proxy authority | Partial | +| Plugin SSRF/DNS rebinding/outbound abuse | Integration/egress -> network | immutable released/versioned egress authority, connect-time address checks, rebinding controls, redirect/proxy/size/time limits | Partial | +| Dependency lifecycle-script escalation | Package install -> runner | exact pinned package and narrow build allowlist | Implemented on protected main | +| CI evidence identity confusion | GitHub workflows | explicit source/base/integration/checkout/protected/release identities | Partial | +| Temporary verification workflow becomes permanent/self-modifying authority | GitHub workflow -> branch | purpose-bounded writer, exact changed-file denominator, ordinary descendant self-retirement only after proof; no force push/gate mutation | Partial | +| Backup corruption or unsafe restore | Operator -> storage | integrity manifest, safe-target refusal, readiness verification | Implemented on protected main | +| Release provenance/signature mismatch | GitHub -> artifacts/deployment | active #217 structural index + #236 detached verification; exact protected release source/immutable publication/trust lifecycle still required | Partial | + +## Protected authority milestones + +- PR #168 and PR #188 protect Planning tenant and request binding. +- PR #173 protects Habit tenant authority. +- PR #185 protects Review request-bound authority. +- PR #190 protects integration event request binding. +- PR #191 and PR #196 protect plugin operator one-time authority and HTTP composition. +- PR #157, PR #176, PR #189, PR #193, PR #197, PR #201 and PR #203 protect Calendar disconnect, returned evidence, read, materialization, create/compensation and self-hosted encrypted storage. +- PR #159 protects the contributor contract; PR #179/PR #194 protect Planning contribution/transport; PR #184/PR #192 protect Habit contribution/transport; PR #195 protects Review contribution. +- PR #200 protects the exact reviewed OpenCode bootstrap surface only; it is not direct-provider routing authority. + +These milestones narrow but do not erase parent-gap threats. + +## Calendar abuse cases + +- forged workspace/user/connection UUIDs fail before secret materialization or SQL; +- returned connection rows whose identity differs from the exact lookup fail closed; +- connection reads omit secret handles and plaintext material; +- local disconnect cannot be interpreted as provider revoke success; +- secret-first create failure compensates newly written handles without returning them; +- PR #201 protects compensation when persistence returns invalid durable evidence; +- active #216 cannot silently fall back to deployment-global hosted provider credentials; +- active #228 state is bounded to one exact ceremony and keeps PKCE verifier plaintext outside durable Calendar metadata; +- an expired/replayed/corrupt OAuth state cannot become callback/token authority; +- hosted token exchange, refresh fencing, successful verifier cleanup, provider revoke/delete and scoped sync remain explicit #129 gaps. + +## Data-rights abuse cases + +- forged request/workspace/user/contributor UUIDs fail before SQL; +- cross-workspace or cross-requesting-user status lookup returns no existence signal; +- duplicate, corrupt, ambiguous, or malformed persisted rows fail closed; +- session rotation cannot reset recent-authentication age; +- a contributor cannot read or delete another service's tables; +- exact destructive replay returns bounded existing evidence; conflicting reuse fails; +- unknown, unavailable, or omitted contributors prevent terminal whole-product success; +- export digests are not treated as authorization, confidentiality, or signer identity; +- Review PR #195 is protected; Notification PR #198 and AI PR #199 remain active evidence until integration. + +## First-party UI abuse cases + +- browser-provided workspace IDs, cookies, or durable object identities do not become downstream service authority; +- stale overlapping reads cannot replace a newer projection or newly accepted durable record; +- malformed/duplicate/non-canonical server evidence fails closed instead of being rendered as durable truth; +- error/conflict paths preserve prior safe durable evidence rather than fabricating success; +- material UI cannot be declared complete without current-head normal/loading/empty/error/permission/responsive/interaction and keyboard/a11y evidence; +- locale fallback cannot silently change resource identity or collapse the DB-versioned screen-key translation ledger into ontology labels. + +## Plugin abuse cases + +- a manifest requesting undeclared or ungranted capabilities cannot self-escalate; +- cross-workspace/installer/installation/binding/grant identifiers fail closed; +- mismatched returned installation, credential or origin evidence cannot become authority; +- exact credential replay cannot rematerialize or overwrite a secret; +- revocation ends durable authority before external cleanup and retries never restore it; +- Vault provider plaintext and Vault credentials do not enter LifeOS durable metadata or public evidence; +- operator authority is bound to exact request path/method and one-time evidence; +- active #250 delivery-origin grant/read/revoke signatures are not credential signatures and aliases/case variants/wrong methods fail closed; +- no operator route grants arbitrary SQL, filesystem, subprocess, tool, or network access; +- no durable origin grant is treated as approval for a later resolved IP, redirect, proxy route, or rebinding result; +- outbound URLs remain untrusted until the separate canonical egress/network slice exists under #130. + +## AI and development-model controls + +AI proposals remain inert and auditable. Model content cannot authorize product mutation. Live-provider availability cannot fabricate deterministic merge success. + +A strong single-route baseline precedes deeper orchestration. Protected #200 is bootstrap hardening only. Active #208 preserves exact OpenCode identity while routing model calls through contextual-orchestrator and virtual `orchestrator/free`; LifeOS does not choose direct providers or copy mutable owner source. The lane fails closed while the owner authentication/bootstrap contract and immutable release are unavailable. Raw prompts/responses, hidden reasoning and credentials are not retained as public evidence. + +## Verification and supply-chain controls + +PR #154 separates exact contributor-source evidence from independently reconstructed live-base compatibility. Issue #132 remains **Partial** because central reusable scanner checkout/SARIF/status taxonomy is not yet fully machine-auditable. + +Pending, queued, skipped, cancelled, absent, neutral, stale, predecessor, status-only, synthetic-only, model-only, and rate-limited evidence is non-passing. Package lifecycle scripts remain denied except for an exact reviewed need; PR #200 is **Implemented on protected main** for the pinned OpenCode package only. + +Temporary proof workflows are verification mechanisms, not product authority. Their failure is root-caused as code/config/runtime evidence; they may self-retire only after the intended exact proof succeeds and only by deleting their own purpose-complete workflow through an ordinary descendant. A successful ancestor proof does not silently become unrelated current-head merge authority. + +## Failure and recovery + +Dependency outages return sanitized unavailable evidence and never false durable success. Partial external cleanup retains exact retry identity without restoring revoked authority. Corrupt durable evidence triggers fail-closed classification. Restore/migration/release claims require integrity, compatibility, rollback/recovery, and exact source/provenance evidence appropriate to the changed state. + +## Review triggers + +Update this threat model whenever a service gains persistence, credential, network, destructive, model, or release authority; a provider or contract version changes; a parent gap closes; an active PR integrates; required verification identity semantics change; or a recovery path can create orphaned external material. diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md new file mode 100644 index 000000000..ae42e7776 --- /dev/null +++ b/docs/TRACEABILITY.md @@ -0,0 +1,138 @@ +# LifeOS Requirements and Evidence Traceability + +**Status:** Implemented on active PR + +Protected-main source/migrations/tests and live repository policy outrank this index. Active-PR evidence remains non-shipped until integration. + +## Requirement traceability + +| Requirement / decision | Status | Representative evidence | Open follow-up | +| --- | --- | --- | --- | +| PRD-ID-001 login/session/workspace/authentication-age authority | Implemented on protected main | Identity runtime and migrations | — | +| PRD-ID-002 opaque UUIDv4 internal/public product IDs | Implemented on protected main | validators/migrations + ADR 0001 | — | +| PRD-PLAN-001 durable Goals/Projects/Tasks/search | Implemented on protected main | Planning repositories/migrations | — | +| PRD-PLAN-002 durable Today synchronization | Implemented on protected main | PR #127 | — | +| PRD-PLAN-003 signed and exact request-bound Planning authority | Implemented on protected main | PR #168 and PR #188 | — | +| PRD-HAB-001 recurring habits/completion evidence | Implemented on protected main | Habit persistence/tests | — | +| PRD-HAB-002 signed Habit authority and replay-safe contributor transport | Implemented on protected main | PR #173 and PR #192 | — | +| PRD-REV-001 guided Review projection/persistence boundary | Implemented on protected main | Review service tests | — | +| PRD-REV-002 exact request-bound signed Review authority | Implemented on protected main | PR #185 | — | +| PRD-CAL-001 conflict-safe Google/CalDAV synchronization | Implemented on protected main | Calendar provider tests | — | +| PRD-CAL-002 signed workspace synchronization context | Implemented on protected main | PR #139 | — | +| PRD-CAL-003 complete per-user encrypted provider credential lifecycle | Partial | protected #203 plus active #216/#228 hosted/OAuth-state boundaries | issue #129 callback/token exchange, durable OAuth-state persistence, refresh/provider cleanup/discovery/scoped sync | +| PRD-CAL-004 workspace+user connection metadata and opaque handles | Implemented on protected main | PR #150 | issue #129 | +| PRD-CAL-005 atomic local connection revocation | Implemented on protected main | PR #153 | provider cleanup #129 | +| PRD-CAL-006 signed workspace+user hosted authority | Implemented on protected main | PR #155 | — | +| PRD-CAL-007 authenticated disconnect/read/materialization/create with exact returned evidence | Implemented on protected main | PR #157, PR #176, PR #189, PR #193, PR #197 | issue #129 | +| PRD-CAL-008 returned-create-evidence secret compensation | Implemented on protected main | PR #201 | — | +| PRD-NOT-001 bounded reminders and durable outcomes | Implemented on protected main | Notification migrations/scheduler tests | — | +| PRD-AI-001 inert auditable proposals and explicit decisions | Implemented on protected main | AI proposal/audit tests | — | +| PRD-AI-002 deterministic/live-provider separation | Implemented on protected main | evaluator/live-conformance split | — | +| PRD-PRIV-001 purpose-bound sensitive access | Implemented on protected main | Privacy service | — | +| PRD-PRIV-002 recent-auth + durable request/receipt/status | Implemented on protected main | Identity data-rights foundations | issue #55 parent remains | +| PRD-PRIV-003 complete cross-domain export/deletion/reconciliation/delivery | Partial | protected and active contributors | issue #55 | +| PRD-PRIV-004 deterministic export integrity evidence | Implemented on protected main | export manifest tests | issue #55 parent remains | +| PRD-PRIV-005 versioned service-owned contributor lifecycle | Implemented on protected main | PR #159 | issue #55 | +| PRD-PRIV-007 Planning contributor and authenticated transport | Implemented on protected main | PR #179 and PR #194 | issue #55 | +| PRD-PRIV-008 Habit contributor and authenticated transport | Implemented on protected main | PR #184 and PR #192 | issue #55 | +| PRD-PRIV-009 Review/Notification/AI contributors | Partial | Review protected in PR #195; Notification PR #198 and AI PR #199 active | integrate remaining contributors/reconciliation | +| PRD-INT-001 plugin SDK/manifest/event validation | Implemented on protected main | Plugin SDK/integration tests | — | +| PRD-INT-002 complete concrete secret/outbound delivery runtime | Partial | protected foundations plus active #205/#235/#241/#242/#243/#244/#245/#250 | issue #130 outbound HTTPS/connect-time controls/outcomes/retry/recovery | +| PRD-INT-003 explicit host-owned installation grants | Implemented on protected main | PR #151 | issue #130 parent remains | +| PRD-INT-004 durable exact plugin installation authority | Implemented on protected main | PR #169 and PR #175 | issue #130 | +| PRD-INT-005 opaque credential-binding secret references | Implemented on protected main | PR #172 | issue #130 | +| PRD-INT-006 one-time request-bound operator authority and HTTP composition | Implemented on protected main | PR #191 and PR #196 | issue #130 | +| PRD-WEB-001 accessible localized PWA | Implemented on protected main | browser/accessibility/localization tests | — | +| PRD-WEB-002 authenticated real Planning/Habit Today composition | Implemented on protected main | PR #186 and PR #187; Issue #163 completed | — | +| PRD-WEB-003 complete first-party authenticated buyer journey | Partial | active BFF/workspace stack including PR #214, PR #229, PR #234 | issue #209 Figma/Storybook, all states, full locale parity, authoritative Review projections, release E2E | +| PRD-OPS-001 logical backup/restore integrity | Implemented on protected main | scripts/tests/runbook | — | +| PRD-OPS-002 provider-neutral deployment/readiness/metrics | Implemented on protected main | infrastructure/observability tests | — | +| PRD-GOV-001 buyer-gap vs capability-maturity separation | Implemented on protected main | Commercial Readiness registry | — | +| PRD-GOV-002 exact source/live-base/integration evidence separation | Implemented on protected main | PR #154 + ADR 0010 | Issue #132 remains Partial | +| PRD-GOV-003 contextual-orchestrator model authority with exact OpenCode identity | Partial | protected #200 bootstrap; active #208 `orchestrator/free` consumer lane | immutable contextual-orchestrator authentication/bootstrap release + exact consumer GREEN | +| PRD-REL-001 immutable commercial release evidence | Partial | active Draft #217 structural index + stacked #236 signature verification | issue #210 immutable release, trust roots/key lifecycle, packaging/SBOM/provenance/recovery | +| Integration event exact request authority | Implemented on protected main | PR #190 | — | + +## Architecture decisions + +| Decision | Status | Evidence | +| --- | --- | --- | +| Server-backed self-hostable modular MSA supersedes browser-only primary architecture | Accepted architecture | Architecture + ADR 0009 | +| UUIDv4 supersedes UUIDv7 | Accepted architecture | ADR 0001 | +| Service-owned persistence/no cross-table authority | Accepted architecture | ADR 0003 | +| AI remains inert proposal evidence | Accepted architecture | ADR 0004 | +| Purpose-bound sensitive access | Accepted architecture | ADR 0005 | +| Capability maturity differs from buyer-gap exhaustion | Accepted architecture | ADR 0008 | +| Canonical documentation uses exact maturity vocabulary | Accepted architecture | ADR 0007 | +| Verification identities remain separate | Accepted architecture | ADR 0010 + PR #154 | +| Integration identity, metadata, secret references, and grants remain separate | Accepted architecture | ADR 0011 + protected/active Calendar and Plugin lines | +| Model capability is owner-routed and deterministic review/merge/release authority remains separate | Accepted architecture | ADR 0012 + protected #200 + active #208 owner boundary | +| Service persistence/migrations/credentials remain service-owned | Accepted architecture | ADR 0013 + active Integration PostgreSQL/Vault composition | + +## Protected-main authority chronology since the prior canonical snapshot + +- PR #157: authenticated Calendar disconnect. +- PR #159: versioned data-rights contributor lifecycle. +- PR #168 and PR #188: Planning signed/request-bound authority. +- PR #169, PR #172, and PR #175: plugin durable installation, opaque credential binding, and exact evidence validation. +- PR #173: Habit signed authority. +- PR #176 and PR #189: Calendar exact lookup and authenticated read. +- PR #179 and PR #194: Planning contributor and authenticated transport. +- PR #184 and PR #192: Habit contributor and authenticated transport. +- PR #185: Review request-bound authority. +- PR #186 and PR #187: real authenticated Today composition. +- PR #190: request-bound integration event authority. +- PR #191 and PR #196: plugin operator one-time authority and fail-closed HTTP composition. +- PR #193: scoped Calendar credential materialization port. +- PR #195: Review-owned data-rights contributor. +- PR #197: authenticated Calendar connection creation. +- PR #200: exact pinned OpenCode bootstrap allowlist. +- PR #201: returned-create-evidence validation and reverse-order secret compensation. +- PR #203: Calendar-owned encrypted self-hosted credential storage. + +## Active-PR evidence + +| Pull request | Status | Bounded meaning | +| --- | --- | --- | +| PR #145 | Implemented on active PR | canonical whole-product documentation successor | +| PR #198 | Implemented on active PR | Notification-owned data-rights contributor | +| PR #199 | Implemented on active PR | AI-owned data-rights contributor and additive cursor contract | +| PR #204 | Implemented on active PR | read-only exact-tree Actions workflow-registry detector | +| PR #205 | Implemented on active PR | host-owned delivery-origin authority foundation | +| PR #208 | Implemented on active PR | exact OpenCode identity with contextual-orchestrator `orchestrator/free` routing; owner release/authentication blocker remains | +| PR #214 | Implemented on active PR | authenticated first-party Goal BFF foundation for #209 | +| PR #216 | Implemented on active PR | hosted Calendar rejects deployment-wide provider credentials pending user-owned composition | +| PR #217 | Implemented on active PR | structural release-evidence index/validator for #210 | +| PR #228 | Implemented on active PR | scoped Google OAuth state/PKCE authority; no callback/token exchange yet | +| PR #229 | Implemented on active PR | durable browser-safe Goals workspace on authenticated BFF stack | +| PR #234 | Implemented on active PR | durable Weekly Review workspace with persistence-aligned period uniqueness; prerequisite stack remains Draft | +| PR #236 | Implemented on active PR | detached Ed25519 release-evidence verification/operator CLI stacked on #217 | +| PR #245 | Implemented on active PR | concrete hosted Plugin Vault + Integration-owned PostgreSQL runtime with retained real-server acceptance ancestry | +| PR #250 | Implemented on active PR | signed delivery-origin operator authority stacked on #245; HTTP/outbound delivery intentionally absent | + +No active row is shipped truth. Pending CI, draft state, unresolved review, branch movement, predecessor evidence, and merge compatibility remain independently evaluated. + +## Buyer-gap state + +Canonical buyer gaps remain #55, #129, #130, #209, and #210. Protected and active capability slices narrow them but do not close them. Issue #132 remains **Partial** as verification governance rather than buyer-visible product capability. Issue #148 remains documentation integration work. Issue #163 is completed by protected real Planning/Habit Today composition. + +- #55: all-owner data-rights participant inventory/reconciliation, retention/legal hold, backup expiry, protected delivery and terminal whole-right evidence. +- #129: complete per-user Calendar OAuth/provider credential lifecycle, discovery/selection and scoped synchronization. +- #130: complete Plugin secret/outbound network/delivery outcome/retry/recovery lifecycle. +- #209: complete first-party product journey, Figma/Storybook traceability, responsive/a11y states and KO/EN/JA/ZH/VI/ES/DE/FR parity. +- #210: exact protected-head immutable release with package/tag/SBOM/provenance/signature/reproducibility/rollback/recovery evidence. + +## Evidence hierarchy + +1. protected-main source, migrations, tests, and live policy; +2. exact current active-PR source/tests labeled active; +3. accepted Architecture and ADRs; +4. canonical product/technical/data/UML/security/privacy/operability documents; +5. issues/plans/research for incomplete work; +6. historical chat, old PR bodies, old checks, and old SHAs as rationale only. + +`source_head_sha`, `pr_base_snapshot_sha`, `live_base_tip_sha`, integration/synthetic identity, `workflow_checkout_sha`, `protected_main_sha`, and `release_source_sha` remain distinct. A green result never transfers across identities. + +## Update rule + +When maturity changes, reconcile PRD, TRD, Architecture, Data Model, UML, API, Threat Model, Privacy, Operability, Release, Standards, this index, Documentation Assessment, README/CLAUDE/CHANGELOG discoverability, and executable documentation contracts. Never promote active work before protected integration. diff --git a/docs/TRD.md b/docs/TRD.md new file mode 100644 index 000000000..f4bee0c79 --- /dev/null +++ b/docs/TRD.md @@ -0,0 +1,163 @@ +# LifeOS Technical Requirements Document + +**Status:** Implemented on active PR + +This TRD defines repository-wide technical requirements. Protected-main code, migrations, tests, workflow policy, and owning-service runbooks remain the implementation authority. + +## Runtime baseline + +LifeOS is a TypeScript-first monorepo with a Next.js PWA/BFF, independently bounded services, service-owned PostgreSQL persistence, and NATS JetStream where durable asynchronous delivery is required. Optional providers include Google/GitHub identity and Google/CalDAV calendar. Model capability is consumed through the reviewed contextual-orchestrator boundary rather than direct provider selection in product/runtime code; plugins use versioned host-owned contracts. + +## Bounded contexts + +- **Web/PWA:** interaction state, accessibility/localization, and explicitly local drafts/cache; no database authority. The complete first-party buyer journey remains **Partial** under #209. +- **Gateway/BFF:** authenticated public composition and short-lived service-context derivation; no shared domain store. +- **Identity:** users, provider mappings, sessions, workspace authority, authentication provenance, data-rights request/receipt orchestration, and export-integrity composition. +- **Planning:** Goals, Projects, Tasks, search, durable Today, and a protected data-rights contributor. +- **Habit:** recurring definitions/completions and a protected data-rights contributor. +- **Review:** guided-review persistence/projections and a protected Review-owned contributor from PR #195. +- **Calendar Integration:** synchronization, connection metadata, workspace/user authority, credential ports, read/create/disconnect surfaces; complete hosted provider lifecycle remains **Partial** under #129, with active #216/#228 narrowing hosted credential and OAuth state/PKCE boundaries. +- **Notification:** reminder occurrences/claims/outcomes; PR #198 is **Implemented on active PR** for its contributor. +- **AI Proposal:** inert proposals/evidence/decisions/evaluation; PR #199 is **Implemented on active PR** for its contributor. +- **Privacy:** purpose-bound sensitive-access decisions/grants/events. +- **Plugin Integration:** contracts, installation/grant/credential/operator authority; active #205/#235/#241/#242/#243/#244/#245/#250 narrow durable origin, Vault, PostgreSQL and signed operator composition while outbound delivery remains **Partial** under #130. + +## Persistence and data requirements + +1. Each service owns schemas/roles, migrations, repositories, credentials, transaction boundaries, backup semantics, and shutdown behavior. +2. Cross-service table reads, writes, joins, foreign keys, triggers, and shared mutation roles are prohibited. +3. Internal durable identifiers are opaque UUIDv4. +4. Product-owned database objects use descriptive multiword `snake_case`. +5. Instants use UTC; civil-time behavior also retains explicit IANA timezone/local-calendar evidence. +6. Immutable audit/decision/completion/receipt evidence rejects mutation. Mutable state uses revision, digest, ETag, idempotency, advisory locking, or fencing where loss/replay is plausible. +7. Browser-local state is not durable until the owning service accepts it. +8. External credentials remain behind least-authority secret-store/KMS ports and never become identity or primary-key material. +9. Persisted external identifiers are bounded metadata; opaque secret references are separate fields with separate authority. +10. Corrupt or ambiguous persisted evidence fails closed before it can become application authority. +11. A service-owned PostgreSQL pool may back multiple repositories inside one bounded context; cross-service SQL or mutable sibling-source coupling is still prohibited. + +## Authentication and authorization + +- OAuth callbacks validate state, provider, redirect/origin, bounded transaction lifetime, exact user/workspace authority, and one-time consumption before credential exchange. +- Browser sessions are revocable and server-verifiable. +- Authentication ceremony time survives compatible session rotation. +- Browser-selected workspace, actor, installation, connection, request, grant, or credential identifiers are never ownership authority. +- Signed private contexts bind exact workspace/actor, method, path, issuance, version, and one-time evidence where destructive replay matters. +- Planning protected authority comes from PR #168 and exact request binding from PR #188. +- Habit protected authority comes from PR #173; destructive contributor transport is protected by PR #192. +- Review exact request-bound authority is protected by PR #185. +- Calendar user-sensitive operations use `life-os.calendar-user.v1` from PR #155. +- Integration event authority is exact-request-bound through PR #190. +- Plugin operator authority is one-time and replay-protected through PR #191 and fail-closed HTTP composition through PR #196. Active #250 extends the internal application verifier to exact signed delivery-origin collection/item/revoke routes but does not yet expose their HTTP transport. + +## HTTP and application boundaries + +- Bound request bodies and provider/model responses before retention. +- Derive authority from authenticated or signed context. +- Reject unsupported media types and malformed JSON with bounded credential-free problems. +- Use explicit replay and stale-write controls. +- Do not forward browser cookies or provider secrets to downstream services. +- Never expose dependency bodies, stack traces, credentials, internal origins, secret handles, or raw tenant payloads in public failures. +- Version breaking shared-contract semantics; unknown versions fail closed. +- Sensitive status resources are non-cacheable and omit unrelated authority/digest/idempotency internals. + +### Today composition + +**Status:** Implemented on protected main + +PR #186 composes authenticated Planning Today state and PR #187 composes authenticated Habit Today state. The Gateway derives authority from the authenticated session, signs exact downstream requests, validates bounded responses, and does not fabricate success. Issue #163 is completed. + +### Data-rights contributor transport + +**Status:** Partial + +PR #159 defines `life-os.data-rights-contributor.v1` with explicit export, erase-preflight, erase, and verify-erased operations. Planning production contribution is protected through PR #179 and authenticated request-bound transport through PR #194. Habit production contribution is protected through PR #184 and transport/replay hardening through PR #192. Review production contribution is protected through PR #195. + +PR #198 and PR #199 are **Implemented on active PR** for Notification and AI contributions. They remain non-shipped until integration. Whole-product completion remains **Partial** under #55. + +### First-party buyer journey + +**Status:** Partial + +Issue #209 requires a dependency-ordered authenticated Goals → Projects → Tasks → Habits → Review journey. Active PR #214 establishes the first-party Goal BFF and keeps workspace authority/request signing server-side. The stacked buyer-path line reaches the durable `/goals` workspace at PR #229 and `/review` at PR #234. Browser reducers accept only bounded durable server evidence, reject stale/duplicate/malformed authority, preserve safe prior evidence on failures, and do not manufacture durable IDs. + +The stack remains non-shipped. Final acceptance requires exact-head browser E2E after prerequisite restacks, Figma/Storybook traceability, normal/loading/empty/error/permission/responsive/interaction states, keyboard/focus/reduced-motion/a11y coverage, authoritative Review read projections, and KO/EN/JA/ZH/VI/ES/DE/FR DB-versioned translation-ledger/font/text-expansion parity. + +### Calendar connection lifecycle + +**Status:** Partial + +Protected main includes: + +- workspace/user scoped metadata persistence from PR #150; +- atomic local revoke from PR #153; +- signed user authority from PR #155; +- authenticated disconnect from PR #157; +- exact lookup evidence validation from PR #176; +- authenticated bounded read from PR #189; +- scoped credential materialization port from PR #193; +- authenticated secret-first create from PR #197; +- reverse-order compensation on mismatched returned durable evidence from PR #201; +- Calendar-owned AES-256-GCM encrypted self-hosted credential storage from PR #203. + +Active PR #216 fails hosted multi-user startup closed when deployment-wide Google/CalDAV credentials would otherwise substitute for user-owned authority. Stacked PR #228 adds five-minute OAuth state/PKCE authority with opaque durable state and secret-store-held verifier material and revalidates consumed repository evidence before secret materialization. + +Hosted callback/token exchange, successful post-exchange verifier cleanup, concrete PostgreSQL OAuth-state persistence, refresh fencing, provider revoke/delete recovery, discovery/selection, scoped synchronization, and complete KMS/runtime composition remain **Partial** under #129. + +### Plugin installation and operator lifecycle + +**Status:** Partial + +Protected main includes explicit host grants (PR #151), durable installation persistence (PR #169), opaque credential binding (PR #172), exact installation-evidence validation (PR #175), one-time operator authority/replay storage (PR #191), and authenticated fail-closed operator HTTP composition (PR #196). + +The active stack adds host-owned delivery-origin authority (#205), PostgreSQL grant persistence/active-installation fencing (#235), credential/revocation hardening (#241), Vault KV v2 secret storage (#242), authenticated Vault composition (#243), one Integration-owned hosted PostgreSQL pool (#244), and a concrete hosted/default-entrypoint runtime with retained exact ancestor real Vault + migrated PostgreSQL lifecycle evidence (#245). Draft #250 adds exact signed delivery-origin grant/read/revoke application authority and canonical route verification. + +No active slice yet authorizes arbitrary outbound HTTP. #130 still requires immutable/versioned egress authority, connect-time DNS/IP/rebinding checks, redirect/proxy controls, bounded time/response handling, delivery attempt/outcome persistence, retries/dead-letter, revocation fencing at the network boundary, and operator-visible recovery. Durable origin grant identity is necessary but not sufficient network authority. + +## Domain concurrency and idempotency + +- **Today:** strong create/update preconditions, ordered locking, exact replay, stale conflict, and explicit reconciliation. +- **Habit completion:** tenant-scoped replay-safe persistence. +- **Notification:** expiring/fenced claims and duplicate-delivery refusal. +- **Calendar:** exact connection/workspace/user authority, secret-first create compensation, bounded OAuth ceremony state, deterministic provider preconditions, and local revoke replay. +- **AI decisions:** exact proposal digest/revision, actor/workspace, and idempotency binding. +- **Data rights:** exact request/workspace/actor/contributor/replay identity, immutable terminal evidence, and owner-controlled erasure verification. +- **Plugin installation/operator:** exact installation/workspace/installer/manifest/grant/secret-binding/request evidence, atomic replay refusal, and active-installation revalidation across credential/origin admission races. + +## AI and repository automation requirements + +ADR 0012 is authoritative. Model output is untrusted structured data. Deterministic validators, authorization, tests, independent review, merge, and release gates remain authoritative. + +A strong single-route baseline precedes conducted/deeper orchestration. Evaluation records supported workflow stage, reasoning effort, decomposition, recursion depth, role-specific reasoning effort, model/worker selection, verifier topology, and access/communication topology. Unsupported controls remain explicit rather than simulated. + +Protected PR #200 preserves the exact reviewed OpenCode bootstrap boundary. The target scheduled-development architecture is the active #208 line: exact OpenCode identity with model calls routed only through a reviewed immutable contextual-orchestrator API/client and virtual `orchestrator/free`. Provider credentials are contextual-orchestrator bootstrap material, not LifeOS model-selection authority. The current LifeOS consumer remains Draft until the canonical owner fixes its authentication/bootstrap contract, publishes an immutable reviewed release, and the exact released consumer passes hosted acceptance. Mutable owner source copying and direct-provider fallback are prohibited. + +## Security and privacy requirements + +- Treat external responses, stored JSON, environment values, model output, and connector results as untrusted. +- Keep SQL structure static and parameterized. +- Use least-privilege GitHub/runtime/database/network/file/subprocess permissions. +- No credential, browser session, secret reference, raw prompt/response, hidden reasoning, or unbounded tenant content enters public/CI/release evidence. +- Sensitive access is tenant/actor/purpose/resource/lifetime/audit bound. +- No service claims whole-right completion from partial or unknown contributor state. +- No manifest self-authorizes plugin capability or delivery origin. +- No durable delivery-origin grant self-authorizes a resolved IP, redirect target, proxy route, or later rebinding result. +- No local calendar revoke is promoted to provider revoke. + +## Accessibility, localization, and offline behavior + +Core journeys remain keyboard-operable with visible focus, semantic names, non-color-only state, and bounded localized feedback. The complete commercial locale target is KO/EN/JA/ZH/VI/ES/DE/FR with CJK/font fallback and text-expansion acceptance. Translation resources use a DB-versioned screen-key ledger/cache and remain distinct from ontology label ledgers. Offline/local drafts remain visibly distinct from durable workspace state. Stale asynchronous responses cannot overwrite newer owned UI state. + +## Observability and operations + +Services expose bounded health/readiness reflecting actual dependencies. Metrics are operator-only in production exposure. Logs are structured and credential-free. Logical backup/restore proves integrity and unsafe-target refusal but does not claim PITR. Compose is a self-hosted profile; Kubernetes is a provider-neutral reference, not managed surrounding infrastructure. + +## Verification model + +**Status:** Accepted architecture + +`source_head_sha`, `pr_base_snapshot_sha`, independently resolved `live_base_tip_sha`, `integration_tree_sha`/synthetic identity, `workflow_checkout_sha`, `protected_main_sha`, and `release_source_sha` are separate authorities. PR #154 implements exact source and live-base compatibility separation. Issue #132 remains **Partial** for residual central scanner attribution taxonomy. + +## Release requirements + +Issue #210 is **Partial**. Active Draft #217 adds a structural release-evidence index/validator and stacked #236 adds detached Ed25519 verification/operator tooling. A release still requires one unchanged integrated protected head with version/CHANGELOG/tag/package/immutable publication plus required CI/security/review, exact configured coverage/docstrings, package/container build, SBOM/provenance/signatures/reproducibility, compatibility, migration/rollback/recovery, accessibility/localization, deployment, and operational acceptance. A feature PR, queued job, ancestor GREEN, documentation line, or model result is not release readiness. diff --git a/docs/UML.md b/docs/UML.md new file mode 100644 index 000000000..3233277de --- /dev/null +++ b/docs/UML.md @@ -0,0 +1,407 @@ +# LifeOS UML, C4, Authority, and Recovery Views + +**Status:** Implemented on active PR + +Protected-main behavior is labeled explicitly. Active-PR diagrams describe reviewed branch scope only and are not shipped truth. + +## C4 bounded-context topology + +**Status:** Implemented on protected main + +```mermaid +flowchart LR + User[User / Operator] --> Web[Web / PWA] + Web --> Gateway[Gateway / BFF] + Gateway --> Identity[Identity] + Gateway --> Planning[Planning] + Gateway --> Habit[Habit] + Gateway --> Review[Review] + Gateway --> Calendar[Calendar Integration] + Gateway --> Notification[Notification] + Gateway --> AI[AI Proposal] + Gateway --> Privacy[Privacy] + Gateway --> Plugin[Plugin Integration] + + Planning -. versioned events .-> NATS[(NATS JetStream)] + Habit -. versioned events .-> NATS + Review -. projections/events .-> NATS + NATS -. reminder inputs .-> Notification + + Identity --> IDB[(Identity-owned PostgreSQL)] + Planning --> PDB[(Planning-owned PostgreSQL)] + Habit --> HDB[(Habit-owned PostgreSQL)] + Review --> RDB[(Review-owned PostgreSQL)] + Calendar --> CDB[(Calendar-owned PostgreSQL)] + Notification --> NDB[(Notification-owned PostgreSQL)] + AI --> ADB[(AI-owned PostgreSQL)] + Privacy --> VDB[(Privacy-owned PostgreSQL)] + Plugin --> XDB[(Integration-owned PostgreSQL)] +``` + +No arrow authorizes cross-service SQL. Every service retains migrations, credentials, transactions, backup semantics, observability, and recovery ownership. + +## Identity and workspace authority + +**Status:** Implemented on protected main + +```mermaid +sequenceDiagram + actor User + participant Web + participant Identity + participant Provider as Google/GitHub + User->>Web: begin bounded login + Web->>Identity: create OAuth transaction + Identity->>Provider: authorize with exact redirect/state + Provider-->>Identity: callback + Identity->>Identity: validate provider/state/redirect + Identity->>Identity: map user, workspace, authentication instant + Identity-->>Web: revocable session + Note over Identity: session rotation preserves authentication age +``` + +## Planning, Habit, Review, Today, and first-party journey + +### Protected authority + +**Status:** Implemented on protected main + +```mermaid +sequenceDiagram + actor User + participant Web + participant Identity + participant Gateway + participant Planning + participant Habit + participant Review + + User->>Web: open Today + Web->>Identity: validate session + Identity-->>Web: actor + workspace + Web->>Gateway: authenticated Today request + Gateway->>Planning: exact signed request context + Planning-->>Gateway: bounded durable Today state + Gateway->>Habit: exact signed request context + Habit-->>Gateway: bounded Today habit state + Gateway-->>Web: real composed Today response + User->>Review: complete guided review + Review->>Review: verify request-bound signed workspace context +``` + +PR #168 and PR #188 protect Planning authority; PR #173 protects Habit authority; PR #185 protects Review authority; PR #186 and PR #187 protect real Planning/Habit Gateway composition. Issue #163 is completed. + +```mermaid +stateDiagram-v2 + [*] --> LocalDraft + LocalDraft --> DurableToday: explicit server acceptance + strong precondition + DurableToday --> DurableToday: exact replay or versioned update + DurableToday --> Conflict: stale precondition + Conflict --> DurableToday: explicit reconciliation + DurableToday --> Completed + Completed --> [*] +``` + +### Active first-party buyer journey + +**Status:** Partial + +```mermaid +sequenceDiagram + actor User + participant Browser as Web/PWA + participant Identity + participant BFF as First-party BFF + participant Owner as Planning/Habit/Review + + User->>Browser: load workspace + Browser->>BFF: browser-safe request + BFF->>Identity: authenticate session/workspace + Identity-->>BFF: exact actor + workspace + BFF->>Owner: signed exact method/path authority + Owner-->>BFF: bounded durable evidence + BFF->>BFF: validate ownership/schema/evidence + BFF-->>Browser: browser-safe durable projection + User->>Browser: explicit mutation + Browser->>BFF: bounded mutation input + BFF->>Owner: authorized exact mutation + Owner-->>BFF: durable acceptance evidence + BFF-->>Browser: accepted durable record +``` + +Issue #209 is the complete Goals → Projects → Tasks → Habits → Review journey. PR #214 starts the active line with the authenticated Goal BFF; PR #229 adds the durable Goals workspace; stacked PR #234 is the current Weekly Review workspace. Browser state never creates workspace authority or durable identity. Completion still requires the full dependency stack, final current-head E2E, Figma/Storybook traceability, normal/loading/empty/error/permission/responsive/interaction states, keyboard/focus/reduced-motion/a11y, authoritative Review read projections, and KO/EN/JA/ZH/VI/ES/DE/FR parity. + +## Calendar connection and credential lifecycle + +### Protected-main lifecycle + +**Status:** Implemented on protected main + +```mermaid +stateDiagram-v2 + [*] --> MaterializingSecrets: authenticated create (PR #197) + MaterializingSecrets --> PersistingMetadata: opaque handles only + MaterializingSecrets --> Compensating: secret-store failure + PersistingMetadata --> Active: exact returned authority validated + PersistingMetadata --> Compensating: persistence throw or invalid evidence + Active --> Active: authenticated read (PR #189) + Active --> MaterializedForUse: exact handle validation (PR #193) + MaterializedForUse --> Active: plaintext lifetime ends + Active --> Revoked: authenticated local disconnect (PR #157) + Revoked --> Revoked: exact replay + Compensating --> [*]: reverse-order cleanup proven +``` + +PR #150 protects connection metadata, PR #153 protects atomic local revocation, PR #155 protects `life-os.calendar-user.v1`, PR #176 protects exact lookup evidence, PR #189 protects bounded read, PR #193 protects materialization, PR #197 protects authenticated creation, PR #201 protects returned-evidence compensation, and PR #203 protects the Calendar-owned encrypted self-hosted secret-store profile. + +### Active hosted OAuth authority + +**Status:** Partial + +```mermaid +stateDiagram-v2 + [*] --> HostedAdmission + HostedAdmission --> RejectedGlobalCredential: deployment-wide provider credential supplied + HostedAdmission --> AuthorizationState: authenticated user-owned ceremony + AuthorizationState --> PendingCallback: opaque state + PKCE verifier secret reference + PendingCallback --> Consumed: exact state/workspace/user/provider/redirect + expiry accepted + PendingCallback --> Rejected: expired/replayed/malformed/mismatched evidence + Consumed --> VerifierMaterialized: revalidate consumed row before secret read + VerifierMaterialized --> TokenExchangePending: active boundary ends +``` + +Active PR #216 provides the fail-closed hosted admission boundary. Stacked PR #228 provides five-minute OAuth state/PKCE authority with verifier plaintext outside durable metadata. `TokenExchangePending` is deliberately not implemented by this stack: hosted callback/token exchange, successful verifier cleanup, concrete PostgreSQL OAuth-state runtime, refresh fencing, provider revoke/delete recovery, discovery/selection and scoped synchronization remain **Partial** under #129. + +## Data-rights orchestration and contributor authority + +### Protected contributor contract + +**Status:** Partial + +```mermaid +sequenceDiagram + actor User + participant Identity + participant Registry as Explicit participant registry + participant Contributor as Owning service contributor + participant Ledger + + User->>Identity: recent-authenticated export/delete request + Identity->>Ledger: create/replay exact request + Identity->>Registry: resolve exact required participants + loop each owner + Identity->>Contributor: versioned exact signed request + Contributor->>Contributor: use owner persistence only + Contributor-->>Identity: bounded export/preflight/erase/verify evidence + end + Identity->>Identity: reconcile exact participant set + alt all required evidence verified + Identity->>Ledger: append immutable terminal receipt + Identity-->>User: bounded status/artifact lifecycle + else partial/unavailable/unknown + Identity-->>User: non-terminal or bounded failure + end +``` + +PR #159 protects the shared contract. Planning is protected through PR #179 and PR #194. Habit is protected through PR #184 and PR #192. Review is protected through PR #195. Notification PR #198 and AI PR #199 are **Implemented on active PR**. Issue #55 remains **Partial**. + +### Contributor maturity + +```mermaid +flowchart LR + Contract[PR #159 contributor v1] --> Planning[Planning: protected #179/#194] + Contract --> Habit[Habit: protected #184/#192] + Contract --> Review[Review: protected #195] + Contract --> Notification[Notification: active #198] + Contract --> AI[AI: active #199] + Contract --> Remaining[Remaining owners + reconciliation/delivery] + Remaining --> Gap[Issue #55 Partial] +``` + +## Plugin installation, credential, delivery-origin, and operator authority + +**Status:** Partial + +### Protected foundation + +```mermaid +stateDiagram-v2 + [*] --> ValidatedManifest + ValidatedManifest --> Granted: explicit host subset (PR #151) + Granted --> Persisted: exact durable authority (PR #169/#175) + Persisted --> CredentialBound: opaque secret reference (PR #172) + CredentialBound --> OperatorAuthorized: exact one-time request (PR #191) + OperatorAuthorized --> OperatorResult: fail-closed HTTP composition (PR #196) + OperatorAuthorized --> ReplayDenied: reused evidence + CredentialBound --> Revoked: durable authority ends first + Revoked --> CleanupRetry: external secret cleanup retry + CleanupRetry --> Revoked: authority never restored +``` + +### Active #130 persistence and operator stack + +```mermaid +flowchart LR + Manifest[Manifest intent] --> HostGrant[Explicit host grant] + HostGrant --> Installation[Installation authority] + Installation --> Credential[Opaque credential binding] + Installation --> Origin[Exact HTTPS origin grant] + Credential --> Vault[Vault KV v2] + Installation --> IPG[(Integration-owned PostgreSQL)] + Origin --> IPG + Operator[One-time signed operator context] --> Credential + Operator --> Origin + Origin -. identity only .-> Egress[Future canonical egress authority] + Egress -. connect-time policy .-> Network[Untrusted network] +``` + +Active #205 establishes the origin aggregate; #235 adds PostgreSQL grant persistence and installation fencing; #241 strengthens credential/revocation authority; #242 adds the Vault KV v2 secret store; #243/#244 compose Vault and one Integration-owned PostgreSQL pool; #245 supplies the concrete hosted/default-entrypoint runtime; #250 adds exact signed grant/read/revoke application authority for delivery origins. + +```mermaid +sequenceDiagram + participant Operator + participant Verify as One-time operator verifier + participant App as Delivery-origin application + participant Install as Installation repository + participant Origin as Delivery-origin store + + Operator->>Verify: signed exact method/path + actor/workspace/installation + Verify->>Verify: validate freshness/signature + consume replay identity + Verify->>App: exact authorized operation + App->>Install: read active installation authority + Install-->>App: exact durable evidence + App->>Origin: grant/read/revoke exact origin evidence + Origin-->>App: exact durable result + App-->>Operator: bounded result +``` + +#250 deliberately stops here. No public delivery-origin HTTP transport or outbound HTTPS is implied. #130 remains **Partial** until immutable released/versioned canonical egress authority enforces connect-time DNS/IP/rebinding, redirect/proxy and bounded time/response policy and LifeOS persists delivery attempts/outcomes with retry/dead-letter, revocation fencing and operator recovery. + +## AI proposal and explicit decision + +**Status:** Implemented on protected main + +```mermaid +sequenceDiagram + actor User + participant Web + participant Identity + participant AI + participant Audit + User->>Web: request proposal + Web->>Identity: validate session + Identity-->>Web: actor + workspace + Web->>AI: exact signed bounded context + AI->>AI: validate untrusted model result + AI->>Audit: persist inert proposal evidence + AI-->>User: proposal requiring confirmation + User->>AI: explicit accept/reject bound to exact evidence + AI->>Audit: append decision + Note over AI,Audit: no generic Planning mutation authority +``` + +## Model-assisted development and repository authority + +**Status:** Partial + +```mermaid +flowchart LR + OpenCode[Exact reviewed OpenCode] --> CO[contextual-orchestrator released API/client] + Secrets[Provider credentials] --> CO + CO --> Free[orchestrator/free] + Free --> Model[Provider selected by owner] + Model --> Evidence[Bounded credential-free retained evidence] + Evidence --> CI[Deterministic CI/security] + CI --> Review[Independent review authority] + Review --> Merge[Protected merge authority] + Merge --> Release[Release authority] + Evidence -. no independent authority .-> Review +``` + +Protected #200 covers only the exact OpenCode bootstrap allowlist. Active #208 is the target routing line: exact OpenCode identity plus contextual-orchestrator/`orchestrator/free`, with provider credentials/model selection remaining owner-side. It fails closed pending a repaired authentication/bootstrap owner contract, immutable reviewed owner release, and exact released consumer acceptance. Mutable owner source or direct-provider fallback is not authorized. + +## Verification evidence authority + +**Status:** Implemented on protected main + +```mermaid +flowchart LR + Source[source_head_sha] --> SourceChecks[Exact-source checks] + Snapshot[pr_base_snapshot_sha] --> Historical[Historical metadata] + LiveBase[live_base_tip_sha] --> Integration[integration_tree_sha] + Source --> Integration + Integration --> Compatibility[Merge compatibility] + SourceChecks --> Policy[Live policy decision] + Compatibility --> Policy + Policy --> Main[protected_main_sha] + Main --> ReleaseSource[release_source_sha] +``` + +PR #154 protects exact-source/live-base separation. Issue #132 remains **Partial** for central reusable scanner checkout/attribution taxonomy. A green result never transfers across evidence identities. + +## Release-evidence authority + +**Status:** Partial + +```mermaid +flowchart LR + RS[Exact protected release_source_sha] --> Index[Release evidence index] + RS --> Artifact[Package / image] + Artifact --> Checksum[Checksums] + Artifact --> SBOM[SBOM] + Artifact --> Provenance[Provenance / attestation] + Artifact --> Signature[Detached signatures] + Checksum --> Verify[Structural + cryptographic verification] + Provenance --> Verify + Signature --> Verify + Verify --> Publish[Immutable tag/package/release] + Publish --> Install[Installed runtime acceptance] + Install --> Recovery[Upgrade/rollback/restore/recovery] +``` + +Active Draft #217 provides structural index validation and #236 adds detached Ed25519 verification/operator tooling. The diagram's `Publish`, trust-root/key lifecycle, installed acceptance and recovery nodes remain **Partial** under #210 until proved on one unchanged protected release source. + +## Deployment and recovery + +**Status:** Implemented on protected main + +```mermaid +flowchart TB + Client --> Ingress[Operator-owned TLS/DNS/ingress] + Ingress --> Web + Web --> Services[Independent LifeOS services] + Services --> Stores[(Service-owned PostgreSQL authority)] + Services <--> NATS[(NATS JetStream)] + Services --> Providers[Identity / Calendar / Model / Plugin providers] + Backup[Logical backup + integrity manifest] --> Restore[Validated safe-target restore] + Restore --> Stores +``` + +```mermaid +stateDiagram-v2 + [*] --> Healthy + Healthy --> Degraded: optional provider unavailable + Healthy --> FailClosed: owning persistence/authority unavailable + Degraded --> Healthy: bounded retry/recovery + FailClosed --> Recovery: operator restores dependency/evidence + Recovery --> Healthy: readiness + integrity verified + Recovery --> FailClosed: evidence incomplete +``` + +Logical backup/restore does not claim PITR. External provider cleanup/recovery and release rollback preserve explicit partial-state evidence rather than fabricate success. + +## Degraded-mode matrix + +| Failure | Required behavior | Status | +| --- | --- | --- | +| Identity/calendar/model provider unavailable | bounded dependency failure; unrelated domains remain usable where safe | Accepted architecture | +| Owning PostgreSQL unavailable | durable mutation fails closed; local draft remains visibly non-durable | Implemented on protected main | +| Vault/secret store unavailable | credential/origin-dependent operation fails closed; no plaintext persistence fallback | Implemented on active PR | +| NATS unavailable | no fabricated delivery success; replay/recovery evidence remains | Implemented on protected main | +| Stale write | explicit conflict/revision evidence, never silent overwrite | Implemented on protected main | +| Malformed/forged service context | fail closed without reflecting identifiers or secrets | Implemented on protected main | +| Unknown/stale verification identity | non-passing evidence, never promoted success | Implemented on protected main | +| Partial external secret/provider cleanup | retain retry identity without restoring revoked authority | Partial | +| Missing canonical egress authority | plugin outbound delivery remains unavailable rather than treating stored origin as network authorization | Partial | +| Missing immutable contextual-orchestrator release/authentication contract | model-assisted lane fails closed; no direct-provider bypass | Partial | +| Release evidence mismatch or missing trust/recovery evidence | no immutable release promotion | Partial | diff --git a/docs/adr/0001-opaque-non-numeric-identifiers.md b/docs/adr/0001-opaque-non-numeric-identifiers.md index 9b900e250..c16bcca2e 100644 --- a/docs/adr/0001-opaque-non-numeric-identifiers.md +++ b/docs/adr/0001-opaque-non-numeric-identifiers.md @@ -1,33 +1,63 @@ -# ADR 0001: Opaque non-numeric identifiers +# ADR 0001: Opaque UUIDv4 internal identifiers -- **Status:** Accepted -- **Date:** 2026-08-02 +**Status:** Accepted architecture +**Date:** 2026-08-02 ## Context -Sequential numeric identifiers expose record counts, creation order, and easily enumerable resource locators. They also encourage accidental trust in client-supplied IDs and make insecure direct object reference attacks easier to probe. +Sequential numeric identifiers expose cardinality/order and make authorization-locator enumeration easier. The original product exploration also proposed UUIDv7, which exposes temporal ordering. LifeOS is now a public multi-user server-backed product whose identifiers appear across APIs, events, logs, exports and persistence. -LifeOS is a public multi-user service, so identifiers visible in APIs, events, URLs, logs, exports, and database relationships must not reveal sequence or cardinality. +## Decision drivers -## Decision +- opaque non-enumerable internal identity; +- consistent service/API/event representation; +- no reuse of external provider identifiers; +- tenant authorization independent from identifier knowledge; +- compatibility with PostgreSQL `uuid` and current source contracts. + +## Alternatives considered -1. Internal entity identifiers use cryptographically random UUIDv4 values represented as strings in application code and as PostgreSQL `uuid` columns in persistence. -2. Numeric primary keys, auto-increment columns, database sequences, and numeric-only public identifiers are prohibited. -3. Workspace, user, session, goal, project, task, habit, review, event, correlation, causation, export-job, and integration identifiers follow the same rule. -4. Client-supplied identifiers are validated as non-empty, non-numeric opaque strings before repository access. -5. Third-party identifiers are never reused as LifeOS primary keys. Provider identity is stored separately as `(provider, provider_subject)` text and mapped to an independent LifeOS UUIDv4 user ID. -6. OAuth provider subjects that happen to be numeric, such as some GitHub account IDs, remain external attributes only and are never exposed as internal resource IDs. -7. Public pagination uses opaque signed or encrypted cursors rather than offsets or row IDs. -8. IDs are authorization locators, not authorization evidence. Every lookup remains workspace- and actor-scoped. +1. **Auto-increment integers:** rejected for enumeration/cardinality leakage and cross-service coupling pressure. +2. **UUIDv7:** rejected as the repository-wide internal invariant because time ordering is unnecessary and leaks creation ordering. +3. **Provider-native IDs:** rejected because providers can be numeric, mutable in semantics, or collide across providers. +4. **UUIDv4:** selected as the current protected-main contract. -## Why UUIDv4 +## Decision -UUIDv4 is preferred over sequential integers and time-ordered identifiers because it does not reveal creation time or ordering through the identifier itself. The collision probability is negligible for this system when generated with a cryptographically secure source. +1. Internal entity identifiers use cryptographically random UUIDv4 values represented as strings in application code and PostgreSQL `uuid` in persistence. +2. Numeric primary keys, auto-increment columns, database sequences and numeric-only internal identifiers are prohibited for product-owned domain objects. +3. Third-party identifiers remain explicit provider metadata and map to independent LifeOS UUIDv4 IDs. +4. IDs are locators, never authorization evidence; all access remains actor/workspace scoped. +5. Public pagination uses opaque bounded cursors instead of exposing row offsets/primary keys where cursor pagination is implemented. ## Consequences -- Database indexes are larger than integer indexes. -- Logs and URLs are less human-readable. -- Tests must verify generated IDs are UUIDv4 and reject numeric-only supplied identifiers. -- Foreign keys remain explicit and tenant-aware; opaque IDs do not replace authorization or tenant isolation. -- The earlier design note proposing UUIDv7 is superseded by this ADR. +Indexes are larger and identifiers less human-readable than integers, but provider coupling and ordering leakage are reduced. Tests must validate UUIDv4 at trust boundaries and continue tenant authorization independently. + +## Failure and recovery + +Malformed or non-v4 identifiers fail before persistence access where the shared invariant applies. Existing invalid data requires an explicit migration rather than runtime coercion. Provider IDs are never silently converted into internal IDs. + +## Security and privacy impact + +Opaque UUIDs reduce enumeration/order leakage but do not replace authorization. Logs and exported references remain potentially sensitive tenant metadata and follow normal retention/access controls. + +## Acceptance evidence + +Protected-main `AGENTS.md`, service validators, migrations and integration tests require/use UUIDv4 internal identifiers. RFC 9562 defines UUID version 4 representation/semantics; the choice of v4 over v7 is a LifeOS architecture decision. + +## Migration and rollback + +The earlier UUIDv7 proposal was never the protected-main invariant. New code uses UUIDv4. Any future identifier-version migration requires versioned API/data migration and cannot reinterpret existing IDs in place. + +## Supersession + +This ADR supersedes the original UUIDv7 design language. It may be superseded only by a reviewed repository-wide identifier ADR with compatibility, privacy, migration and authorization evidence. + +## References + +Davis, K. R., Peabody, B. G., & Leach, P. J. (2024). *Universally unique IDentifiers (UUIDs)* (RFC 9562) [Published Standards Track RFC]. RFC Editor. https://doi.org/10.17487/RFC9562 + +OWASP Foundation. (n.d.). *Insecure direct object reference (IDOR)*. https://owasp.org/www-community/attacks/insecure_direct_object_reference + +OWASP Foundation. (2021). *A01:2021 – Broken access control*. https://owasp.org/Top10/2021/A01_2021-Broken_Access_Control/index.html diff --git a/docs/adr/0002-oauth-transactions-and-session-tokens.md b/docs/adr/0002-oauth-transactions-and-session-tokens.md index 5d18cb60c..fbecb9143 100644 --- a/docs/adr/0002-oauth-transactions-and-session-tokens.md +++ b/docs/adr/0002-oauth-transactions-and-session-tokens.md @@ -1,50 +1,77 @@ # ADR 0002: OAuth transactions and session tokens -- **Status:** Accepted -- **Date:** 2026-08-03 +**Status:** Accepted architecture +**Date:** 2026-08-03 ## Context -LifeOS accepts Google and GitHub sign-in while maintaining provider-neutral internal identity records. Authorization callbacks must resist cross-site request forgery, authorization-code injection, authorization-server mix-up, replay, redirect substitution, and bearer-token disclosure. Internal identifiers must remain opaque, non-numeric, and non-sequential. +LifeOS supports Google and GitHub sign-in while maintaining provider-neutral internal identity. Authorization callbacks must resist CSRF, code injection, mix-up, replay, redirect substitution and bearer-token disclosure. Session rotation must not erase authentication provenance needed by sensitive operations such as data-rights requests. -The repository already contained provider authorization and token-exchange builders. This decision hardens the shared `auth-security` transaction and session layer rather than introducing a second implementation. +## Decision drivers + +- provider-neutral internal identity; +- current OAuth security best practice; +- server-verifiable and revocable browser sessions; +- tenant/workspace binding independent from browser-selected IDs; +- recent-authentication evidence that survives token/session rotation. + +## Alternatives considered + +1. Trust client OAuth state/callback metadata: rejected. +2. Persist raw session bearer values: rejected. +3. Reuse provider account IDs as LifeOS IDs: rejected by ADR 0001. +4. Treat session rotation time as authentication time: rejected because it weakens sensitive-operation recency semantics. +5. Server-owned OAuth transaction + hashed bearer/session lifecycle: selected. ## Decision ### Authorization transactions -- Every authorization attempt receives a cryptographically random, one-time `state` value. -- The server persists only a SHA-256 digest of `state`. -- The transaction is bound to the selected provider, a digest of the initiating browser session identifier, and the normalized redirect URI. -- The same redirect URI must be used when building the authorization request and exchanging the authorization code. -- Transactions expire after ten minutes by default and are consumed once. -- Provider adapters must consume transactions atomically. A PostgreSQL adapter must use a conditional update or delete with `RETURNING`, scoped to the provider, state digest, browser-session digest, unconsumed status, and expiry. -- Authorization requests use PKCE with the `S256` method. The verifier is a 64-byte random base64url value and the challenge is `BASE64URL(SHA256(verifier))`. -- The verifier and Google OIDC nonce are server-side material. Persistent implementations must encrypt them at rest; neither is returned to the browser except that the nonce is included in the Google authorization request. -- Each provider uses a distinct callback route or equivalent issuer verification to prevent authorization-server mix-up. -- Redirect URIs require HTTPS, except for loopback HTTP during local development, and may not contain credentials or fragments. +- Use cryptographically random one-time `state`, storing only its SHA-256 digest. +- Bind transaction to provider, initiating browser-session digest and normalized redirect URI. +- Use the same exact redirect URI for authorization and token exchange. +- Expire transactions after a bounded lifetime and consume them once atomically. +- Use PKCE `S256` with server-held verifier and validate Google OIDC nonce where applicable. +- Encrypt persistent PKCE verifier/nonce material at rest. +- Use distinct provider callback/issuer validation to prevent mix-up. +- Require HTTPS redirects except documented loopback HTTP development cases. ### Application sessions -- Session bearer tokens are cryptographically random base64url values and are not entity identifiers. -- Only a SHA-256 digest of a session token is persisted. -- Session records use random UUIDv4 primary keys and bind both a user and one of that user's workspaces. -- The database enforces workspace ownership with a composite foreign key. -- Session rotation revokes the previous token before issuing a replacement and records the previous session ID. -- Revocation is idempotent and does not disclose whether a supplied token existed. -- Browser delivery uses `Secure`, `HttpOnly`, and an explicit `SameSite` policy. Production deployments must never place session tokens in URLs, logs, local storage, analytics payloads, or application telemetry. +- Session bearer tokens are random base64url secrets; only SHA-256 digests persist. +- Session records use UUIDv4 and bind user plus authorized workspace. +- Rotation revokes the predecessor before replacement and preserves lineage/authentication provenance. +- Authentication age is not reset merely by rotation. +- Browser delivery uses `Secure`, `HttpOnly` and explicit `SameSite`; tokens never enter URLs, logs, local storage or telemetry. -## Standards basis +## Consequences -- RFC 7636, *Proof Key for Code Exchange by OAuth Public Clients*: https://www.rfc-editor.org/rfc/rfc7636 -- RFC 9700 / BCP 240, *Best Current Practice for OAuth 2.0 Security*: https://www.rfc-editor.org/rfc/rfc9700 -- GitHub OAuth authorization flow: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps +Production identity persistence requires cleanup and encryption-key management. Callers must supply exact transaction/session context instead of reconstructing authority from browser headers. Sensitive flows can enforce recent authentication correctly across rotation. -## Consequences +## Failure and recovery + +Unknown/expired/consumed/malformed transactions fail closed. Revocation remains idempotent and non-enumerating. Migration of authentication provenance is staged/validated so legacy rows cannot silently gain fresh-auth status. + +## Security and privacy impact + +Database compromise does not directly expose usable state/session bearer values. Authentication provenance itself is security-sensitive metadata and follows identity-service access/retention controls. + +## Acceptance evidence + +Protected-main identity source/migrations/tests cover OAuth transaction security, session hashing/rotation, authentication-age persistence and recent-authentication policy. RFC 9700 is the current OAuth 2.0 security BCP; PKCE remains part of the provider flow contract. + +## Migration and rollback + +Migrations introduce workspace/session binding and authentication provenance with validation before final enforcement. Rollback cannot reinterpret newer authentication evidence as fresh; use explicit forward-fix/migration procedures. + +## Supersession + +This ADR is superseded only by a reviewed identity/session architecture change with provider, migration, recent-auth and browser-security compatibility evidence. + +## References + +GitHub. (n.d.). *Authorizing OAuth apps*. https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps + +Lodderstedt, T., Bradley, J., Labunets, A., & Fett, D. (2025). *Best current practice for OAuth 2.0 security* (RFC 9700; BCP 240) [Published Best Current Practice]. RFC Editor. https://doi.org/10.17487/RFC9700 -- Stolen database rows do not directly reveal usable `state` or session bearer values. -- OAuth transactions and sessions require expiry cleanup jobs. -- A production repository needs encryption-key management for PKCE verifiers and OIDC nonces. -- Existing callers must supply the initiating browser-session identifier and the exact redirect URI when creating and consuming transactions. -- Existing sessions are backfilled to their owners' personal workspaces by migration `0003_oauth_binding_and_session_rotation.sql`. -- Provider callback adapters remain responsible for network exchange, provider response validation, ID-token validation for Google, and profile retrieval; this ADR supplies the transaction and session primitives they must use. +Sakimura, N., Bradley, J., & Agarwal, N. (2015). *Proof key for code exchange by OAuth public clients* (RFC 7636) [Published Standards Track RFC]. RFC Editor. https://doi.org/10.17487/RFC7636 diff --git a/docs/adr/0004-inert-auditable-ai-proposals.md b/docs/adr/0004-inert-auditable-ai-proposals.md new file mode 100644 index 000000000..3d8430665 --- /dev/null +++ b/docs/adr/0004-inert-auditable-ai-proposals.md @@ -0,0 +1,36 @@ +# ADR 0004: Inert, auditable AI proposals + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +LifeOS can use models to propose planning assistance, but personal state must remain user/product-authorized and model/provider output is untrusted. + +## Decision drivers +User authority, auditability, provider independence, prompt-injection resistance, deterministic safety, graceful provider failure. + +## Alternatives considered +- model directly mutates planning state — rejected; +- model emits generic executable commands — rejected; +- model returns bounded proposal evidence with explicit accept/reject — selected. + +## Decision +AI output is inert structured proposal data. The AI service validates and persists proposal evidence before return, records explicit decisions, and has no generic planning mutation repository/command bus. Decisions bind actor/workspace and exact proposal revision/digest. Deterministic validation/authorization remains authoritative and live provider availability is not a deterministic PR merge gate. + +## Consequences +The product needs proposal/evidence/decision storage and explicit UX, but provider or orchestration changes cannot silently change user-owned state. + +## Failure and recovery +Malformed/unsafe/provider-unavailable output returns sanitized failure/unavailable evidence. Stale/replayed proposal decisions fail closed. Provider retry must not duplicate decisions. + +## Security and privacy impact +Browser credentials, provider credentials, raw prompts/responses and hidden reasoning are excluded from retained public artifacts. Page/document/model content cannot elevate itself to policy authority. + +## Acceptance evidence +Protected-main AI proposal/audit persistence, same-origin authenticated BFF, signed context, proposal-quality evaluator and explicit decision tests. + +## Migration and rollback +Proposal schema changes are versioned. Rollback preserves immutable historical proposal/decision evidence and cannot reinterpret old proposal content as commands. + +## Supersession +A future AI execution architecture requires a separate reviewed capability/authorization ADR and cannot silently widen this proposal authority. \ No newline at end of file diff --git a/docs/adr/0005-purpose-bound-sensitive-data-access.md b/docs/adr/0005-purpose-bound-sensitive-data-access.md new file mode 100644 index 000000000..8c9b7c9f1 --- /dev/null +++ b/docs/adr/0005-purpose-bound-sensitive-data-access.md @@ -0,0 +1,36 @@ +# ADR 0005: Purpose-bound sensitive-data access + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +LifeOS contains personal planning, identity and integration data. Blanket masking would destroy legitimate product workflows while unrestricted access would undermine privacy and auditability. + +## Decision drivers +Business utility, tenant isolation, least privilege, controlled disclosure, auditability, retention, CSAP/SOC 2 evidence readiness without false certification claims. + +## Alternatives considered +- blanket PII masking — rejected as primary control; +- broad authenticated access — rejected; +- tenant/resource/purpose/lifetime-scoped authorization with audit evidence — selected. + +## Decision +Sensitive access requires authenticated actor/workspace authority plus bounded purpose/resource/lifetime rules. Grants are time-bounded/single-use where designed and access/decision evidence is append-only. Encryption/secret boundaries, least-privilege service roles and retention controls complement authorization. + +## Consequences +Callers must carry explicit purpose/resource context and systems need auditable grant/decision lifecycle, but valid workflows retain usable data. + +## Failure and recovery +Malformed/expired/replayed/cross-tenant grants fail closed. Provider or audit-store outage cannot be interpreted as authorization success. + +## Security and privacy impact +Raw credentials, prompts/responses and unnecessary tenant content remain outside public artifacts/logs. Pseudonymous identifiers are still sensitive metadata and retain access/retention controls. + +## Acceptance evidence +Protected-main privacy-service decisions/grants/events and exact-expiry/concurrency/immutability tests. + +## Migration and rollback +New sensitive resources must be registered with explicit purpose/authority semantics before use. Rollback may disable a new access path but must preserve immutable historical audit evidence. + +## Supersession +A future privacy model may supersede this only with equivalent or stronger tenant/purpose/audit evidence and a migration plan. \ No newline at end of file diff --git a/docs/adr/0006-work-conserving-autonomous-maintenance.md b/docs/adr/0006-work-conserving-autonomous-maintenance.md new file mode 100644 index 000000000..e782fbb80 --- /dev/null +++ b/docs/adr/0006-work-conserving-autonomous-maintenance.md @@ -0,0 +1,37 @@ +# ADR 0006: Work-conserving autonomous maintenance + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +LifeOS maintenance includes PR review/fixes, CI, documentation, buyer-gap development and release preparation. Treating one waiting PR or one completed action as run completion wastes available execution and lets product gaps persist. + +## Decision drivers +Exact-head evidence, safe single-writer behavior, bounded autonomy, auditability, progress during CI/reviewer latency, no gate bypass. + +## Alternatives considered +- stop after one useful action — rejected; +- poll one blocked PR until completion — rejected; +- parallel uncontrolled repository writers — rejected; +- one writer with a work-conserving queue and branch-local deferral — selected. + +## Decision +The dedicated LifeOS loop repeatedly selects the highest-value safe executable item. Waiting is local to an exact PR/head/run/review identity. Before each write it refetches target head/base/blob/review state. It fixes valid findings test-first, never fabricates approval/check evidence, and merges only unchanged exact heads satisfying live policy. Documentation and prompt changes are intermediate actions. + +## Consequences +Runs may perform several non-conflicting actions and require fresh-state discipline. Historical summaries/SHAs cannot be treated as current evidence. + +## Failure and recovery +If a branch moves under another writer, freeze that branch for the run and rotate elsewhere. Failed repair mechanisms become RCA evidence for another remedy. Scheduler/control-plane errors do not disable the recurring task unless truly unrecoverable. + +## Security and privacy impact +Least privilege and exact identity checks reduce accidental cross-branch or stale-state mutation. Model-assisted development remains separated from merge authority and uses approved NVIDIA/OpenCode boundaries. + +## Acceptance evidence +Protected-main `AGENTS.md`, commercial-development automation, buyer-gap/readiness tooling and merge behavior embody the queue/exact-head/no-bypass contract. + +## Migration and rollback +Automation prompt/workflow changes preserve a single LifeOS writer lease and hourly cadence. Rollback restores the last reviewed policy without weakening repository gates. + +## Supersession +A successor automation ADR must preserve equivalent safety/evidence semantics or explicitly justify each weakened/changed control. \ No newline at end of file diff --git a/docs/adr/0007-canonical-documentation-graph.md b/docs/adr/0007-canonical-documentation-graph.md new file mode 100644 index 000000000..8c300cf59 --- /dev/null +++ b/docs/adr/0007-canonical-documentation-graph.md @@ -0,0 +1,36 @@ +# ADR 0007: Canonical documentation graph + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +Feature specs, runbooks, PR bodies and chat history accumulated faster than a whole-product source of truth. Historical architecture also changed from local-first/single-app/UUIDv7 exploration to server-backed MSA/UUIDv4 behavior. + +## Decision drivers +Discoverability, code-current truth, explicit maturity, reviewability, machine-checkable consistency and acquisition diligence. + +## Alternatives considered +- rely on README/PR bodies/chat — rejected; +- duplicate architecture in many feature specs — rejected; +- one indexed canonical graph plus scoped feature docs — selected. + +## Decision +Maintain canonical PRD, TRD, root Architecture, ADR index/records, Data Model/ERD, UML, API/event contracts, Security/Threat Model, Privacy Lifecycle, Test Strategy, Operability, Release/Migration, Standards/Research and Traceability. Status fields use only the exact repository vocabulary and qualifiers/PR numbers belong in evidence prose. Diagrams distinguish conceptual/planned from actually persisted/shipped entities. + +## Consequences +Material product/authority changes require multi-view reconciliation, but GitHub can reconstruct product truth without conversation archaeology. + +## Failure and recovery +A stale/diverged canonical docs PR is not kept alive merely for ancestry. Create/reuse one clean successor from exact current main, preserve/reconcile unique content, prove it, then close the obsolete line as superseded. Resolved historical reviews are not permanent correctness evidence. + +## Security and privacy impact +Canonical docs must not embed credentials, raw tenant data, prompts/responses or hidden reasoning. Security boundaries and ownership are documented without exposing secret material. + +## Acceptance evidence +The canonical documentation consistency test validates required files/links/statuses/ADR targets/diagram fences and key source-aligned claims before protected-main integration. + +## Migration and rollback +When canonical names/paths change, update README/index/test links atomically. Rollback returns to the last coherent graph, not to chat-only authority. + +## Supersession +A future documentation architecture may supersede this ADR only if it preserves one discoverable code-current authority graph and explicit implementation maturity. \ No newline at end of file diff --git a/docs/adr/0008-separate-capability-maturity-from-buyer-gap-exhaustion.md b/docs/adr/0008-separate-capability-maturity-from-buyer-gap-exhaustion.md new file mode 100644 index 000000000..30bd7f5d3 --- /dev/null +++ b/docs/adr/0008-separate-capability-maturity-from-buyer-gap-exhaustion.md @@ -0,0 +1,36 @@ +# ADR 0008: Separate capability maturity from buyer-gap exhaustion + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +The configured readiness capability manifest could reach 100% while accepted customer journeys still had explicit open gaps. Treating capability-evidence maturity as whole-product completeness creates misleading commercial readiness claims. + +## Decision drivers +Truthful product readiness, deterministic repository-owned gap identity, fail-closed unknown issue state, auditability and non-executable untrusted issue prose. + +## Alternatives considered +- use one aggregate capability score as completion — rejected; +- parse arbitrary issue text as product policy — rejected; +- maintain a versioned repository-owned buyer-gap registry and reconcile bounded issue state separately — selected. + +## Decision +Capability evidence maturity and canonical buyer-gap state are separate dimensions. The buyer-gap registry owns durable gap identity and links to issue/capability IDs. Issue title/body/comment/model/review prose is untrusted and never becomes executable policy. Gap state is `open`, `resolved` or `unknown`; missing/ambiguous evidence fails closed to unknown. + +## Consequences +A repository may correctly report 22/22 configured capabilities while still listing open buyer gaps. Product/release decisions must inspect both dimensions plus end-to-end/operational evidence. + +## Failure and recovery +GitHub evidence collection failure cannot turn a gap into resolved. Registry validation rejects duplicates, malformed IDs and unknown capability links. Recovery reruns bounded state collection without rewriting policy from remote prose. + +## Security and privacy impact +Only bounded issue identifiers/state are needed for readiness; untrusted bodies and unnecessary content are excluded from retained policy evidence. + +## Acceptance evidence +Protected-main buyer-gap registry/validation/rendering and issue #21 currently report configured maturity separately from #55/#129/#130 buyer gaps. + +## Migration and rollback +Existing capability maturity fields remain compatible. Buyer-gap fields are additive; rollback must not reinterpret absent buyer-gap evidence as zero gaps. + +## Supersession +A successor readiness model must preserve explicit distinction between configured evidence maturity and whole-product/customer outcome completeness. \ No newline at end of file diff --git a/docs/adr/0009-product-hosting-and-data-evolution.md b/docs/adr/0009-product-hosting-and-data-evolution.md new file mode 100644 index 000000000..c242521d2 --- /dev/null +++ b/docs/adr/0009-product-hosting-and-data-evolution.md @@ -0,0 +1,37 @@ +# ADR 0009: Product hosting and data-authority evolution + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +Early LifeOS exploration proposed a private/login-free local-first PWA and later a simple single-Docker application. The product then moved to a public multi-user cloud/self-hostable service with Google/GitHub login, PostgreSQL durability and domain-oriented services. Retaining all historical options as current created contradictory architecture claims. + +## Decision drivers +Cross-device durability, explicit account/workspace authority, public/self-hosted deployment, independent service ownership, privacy/auditability and modular CWL composition. + +## Alternatives considered +1. Browser-only IndexedDB as system of record — superseded as primary architecture. +2. Single durable application owning all domains — superseded as durable architecture. +3. Provider-hosted proprietary backend — rejected as product dependency. +4. Multi-user server-backed self-hostable modular MSA with explicit local-draft boundary — selected. + +## Decision +LifeOS is a multi-user server-backed and self-hostable modular MSA. Google/GitHub identity establishes server-authorized accounts/workspaces. Domain services own durable state in PostgreSQL and may use NATS/versioned HTTP/event contracts. Browser-local state is explicit draft/cache/offline UX until accepted by the owning service. Compose remains a valid self-hosted composition profile but does not collapse service authority. + +## Consequences +Operators provision durable infrastructure/secrets/provider registration, while users gain cross-device durability and auditable authority. Offline UX requires explicit reconciliation rather than pretending local state is globally durable. + +## Failure and recovery +Loss of browser-local draft does not imply loss of durable service state. Service/provider outages degrade bounded workflows without changing ownership. Backup/restore protects durable PostgreSQL within documented scope. + +## Security and privacy impact +Multi-user operation requires tenant isolation, server-derived authority, purpose-bound sensitive access and credential separation. Local drafts remain locally scoped until explicit upload/sync. + +## Acceptance evidence +Protected-main service layout, identity/workspace persistence, durable planning/Today, PostgreSQL/NATS composition, Kubernetes reference and `ARCHITECTURE.md`. + +## Migration and rollback +Historical local-first data is migrated only through explicit user-controlled/import/sync flows. Do not silently upload local drafts. Deployment rollback cannot collapse service-owned data authority into a shared monolith. + +## Supersession +Only a reviewed product-hosting/data-authority ADR with migration, privacy, offline, deployment and compatibility evidence may supersede this architecture. \ No newline at end of file diff --git a/docs/adr/0010-verification-evidence-identity.md b/docs/adr/0010-verification-evidence-identity.md new file mode 100644 index 000000000..addce20d7 --- /dev/null +++ b/docs/adr/0010-verification-evidence-identity.md @@ -0,0 +1,67 @@ +# ADR 0010: Separate verification evidence identities + +**Status:** Accepted architecture + +## Context + +GitHub pull-request workflows can evaluate more than one commit identity. A contributor branch head, the base snapshot recorded when the pull request was created or updated, the current live base-branch tip, GitHub's synthetic merge tree, the commit actually checked out by a workflow job, protected main, and a released artifact answer different questions. Treating one of those identities as a substitute for another can create stale or false verification claims. + +Issue #132 identified this as a repository-governance reliability gap. PR #147 implements the bounded workflow correction as active-PR evidence; it is not protected-main behavior until merged. + +## Decision drivers + +- exact attribution of source-verification evidence; +- explicit integration-compatibility evidence; +- resistance to stale base assumptions; +- auditable merge and release decisions; +- no promotion of queued, predecessor or synthetic-only evidence into exact-head success. + +## Considered alternatives + +1. **Use the pull-request event/base metadata as the current base everywhere.** Rejected because a PR base snapshot can become stale as the protected base branch moves. +2. **Treat the synthetic merge tree as the contributor source head.** Rejected because it proves a different tree and can hide which source revision was directly evaluated. +3. **Run only source-head checks and ignore integration compatibility.** Rejected because a clean source branch can still fail when integrated with the current base. +4. **Track evidence identities separately and require the appropriate identity for each gate.** Selected. + +## Decision + +LifeOS verification and release evidence distinguishes at least: + +- `source_head_sha`: exact contributor/source branch head whose source correctness is being evaluated; +- `pr_base_snapshot_sha`: GitHub's immutable base snapshot associated with the pull-request representation/event; +- `live_base_tip_sha`: independently resolved current tip of the actual base branch immediately before a base-sensitive decision; +- `merge_tree_sha`: synthetic integration tree used for merge-compatibility evidence; +- `workflow_checkout_sha`: commit/tree a specific workflow job actually checked out; +- `protected_main_sha`: exact integrated protected-main revision; +- `release_source_sha`: protected source identity bound to a published release artifact. + +A check is evidence only for the tree it actually inspects. Source correctness checks bind to `source_head_sha`. Merge/integration compatibility may bind to `merge_tree_sha`, but that evidence is labeled separately. Base-sensitive merge decisions independently resolve `live_base_tip_sha`; `pr_base_snapshot_sha` cannot silently substitute for it. + +Required merge/release decisions may consume multiple evidence classes, but they do not collapse them into one generic green status. + +## Consequences + +- Workflow configuration and evidence payloads become more explicit. +- Existing required context names may remain stable while their checked-out revision contract is corrected. +- Operators can determine whether a failure belongs to source correctness, integration compatibility, current-base drift, infrastructure or release packaging. +- A synthetic merge success cannot prove that the exact contributor source head itself was directly checked where exact-head evidence is required. + +## Failure and recovery + +If the source head, live base, or relevant checked-out tree changes after evaluation, the affected evidence is stale and must be reacquired. If an evidence-producing workflow cannot determine which commit it inspected, it fails closed or is classified unavailable rather than being promoted to passing evidence. A failed integration tree blocks integration only; it does not create an invented source-code finding without source-backed evidence. + +## Security and privacy impact + +Explicit evidence identity reduces stale-check and confused-deputy risk in repository governance. Evidence remains credential-free and records opaque commit identities/status classifications rather than secrets or tenant content. It does not grant additional repository, review, merge or release authority. + +## Acceptance evidence + +Acceptance requires deterministic tests that distinguish source-head and synthetic-merge checkout semantics and hosted workflow evidence showing each lane evaluates the intended tree. PR #147 is `Implemented on active PR` for the initial CI/AppGuardrail/source-versus-merge correction while issue #132 remains open until protected-main integration and any remaining required-workflow attribution is reconciled. + +## Migration and rollback + +Adopt the distinction incrementally without renaming existing required check contexts unless repository policy requires it. A rollback must not restore ambiguous claims: if a source/merge separation is removed, affected evidence is explicitly classified unavailable or the older behavior is accompanied by an equivalent proof of exact source identity. No historical check is reinterpreted as evidence for a different commit tree. + +## Supersession + +This ADR remains authoritative until a later accepted decision provides an equal or stronger evidence-identity model and updates workflow contracts, documentation, merge governance and release acceptance together. diff --git a/docs/adr/0011-external-integration-authority-and-secret-references.md b/docs/adr/0011-external-integration-authority-and-secret-references.md new file mode 100644 index 000000000..044d01e49 --- /dev/null +++ b/docs/adr/0011-external-integration-authority-and-secret-references.md @@ -0,0 +1,71 @@ +# ADR 0011: External integration authority and secret references + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context + +LifeOS integrates with external calendar providers and versioned plugins. Both domains need enough metadata and authority evidence to operate without turning external credentials or untrusted requested capabilities into ambient authority. + +Protected #150 introduced a workspace-and-user-scoped calendar connection registry, protected #151 introduced tenant-scoped plugin installation grants, protected #153 added atomic calendar connection revocation, and active #155 adds a distinct signed workspace+user context for hosted calendar operations. Parent gaps #129 and #130 remain incomplete, so these bounded foundations must not be confused with complete provider/plugin runtime lifecycles. + +## Decision drivers + +- least privilege and explicit tenant/user authority; +- separation of internal identity, external metadata and secret material; +- no authority escalation from provider/plugin input; +- revocation and replay safety; +- service-owned persistence and no cross-service table mutation; +- truthful protected-vs-active maturity; +- replaceable managed secret/KMS and outbound-delivery adapters. + +## Considered alternatives + +1. **Persist provider credential plaintext in integration metadata rows.** Rejected: metadata and secrets have different access, rotation, retention and audit boundaries. +2. **Treat a plugin manifest's requested capabilities as grants.** Rejected: untrusted extension metadata cannot self-authorize host operations. +3. **Reuse provider account IDs as LifeOS identity/primary keys.** Rejected: external identifiers are mappings, not internal authority. +4. **Use LifeOS-owned UUIDv4 integration identity, separate secret references and explicit host-granted authority.** Selected. + +## Decision + +1. LifeOS-owned integration records use opaque UUIDv4 identity and are scoped by authenticated workspace and, where personal, owning user. +2. Provider/account/calendar/plugin identifiers remain bounded metadata and never replace LifeOS internal identity. +3. Persistent integration metadata may reference credential material only through an opaque secret handle or equivalent least-authority secret-store reference. The metadata row is not a credential store. +4. A plugin manifest expresses requested intent. The host grants an explicit capability subset; requested-but-ungranted capabilities have no authority. +5. Exact replay may return the same result only when authority-relevant evidence matches; conflicting identity reuse fails closed. +6. Revocation ends future active local authority while retaining bounded audit/reconciliation evidence. Local record revocation is not automatically provider-side OAuth revocation or secret destruction. +7. User-sensitive internal calendar operations require authority that binds both workspace and requesting user; workspace-only context remains a distinct sync contract. +8. Outbound delivery, provider refresh/revoke, managed secret storage, discovery and related runtime features are separately gated and cannot be inferred from the existence of an integration record or grant. +9. Every owning service retains migration/repository/API authority. Cross-service relationships use versioned contracts rather than direct table access. + +## Consequences + +- Calendar metadata and plugin authority can evolve independently from managed secret backends. +- Product code requires exact tenant/actor scoped lookups instead of identifier-only retrieval. +- A stored integration record or grant does not prove the full provider/plugin runtime is production complete. +- Secret-store/KMS and delivery adapters can change without changing internal integration identity when contracts remain compatible. + +## Failure and recovery + +Malformed authority, identifier substitution, stale/future signed context, revoked integration identity, capability escalation, incompatible replay and corrupted persistence fail closed. Secret-store/provider failure never widens authority. Recovery requires a newly authorized operation or bounded operator repair preserving audit/reconciliation evidence; editing another service's tables is not recovery. + +## Security and privacy impact + +The model reduces standing credential exposure and prevents untrusted metadata from self-authorizing. Tenant/user scope, revocation, least privilege and secret separation remain explicit. Provider identifiers/secret references are not authentication evidence for unrelated LifeOS domains. + +## Acceptance evidence + +- Protected #139: signed trusted workspace calendar context. +- Protected #150 (`1623df364925f84920c07c112f1ae96777277d20`): workspace+user calendar connection persistence with bounded metadata and opaque secret references. +- Protected #151 (`6971c4e11b3204ec41526c7c959a248e54440e1c`): explicit plugin capability grants, replay/conflict isolation and revocation semantics. +- Protected #153 (`b13413e571bad82535f63d478e40746d12c3e680`): atomic tenant+user calendar connection revocation. +- Active #155: distinct short-lived signed workspace+user calendar context; not protected-main evidence before merge. +- Parent #129/#130 remain incomplete until full credential/runtime delivery acceptance criteria are satisfied. + +## Migration and rollback + +Introduce opaque internal IDs/secret references before removing legacy development configuration. Rollback may disable an integration path but must not reintroduce client-selected tenant authority, plaintext credential persistence as a general contract, implicit plugin grants, provider-native primary-key authority or workspace-only authorization for user-sensitive operations. + +## Supersession + +A later ADR may replace this model only with equal-or-stronger separation of internal identity, external metadata, secret material and granted authority, plus explicit migration/rollback and protected-main acceptance evidence. diff --git a/docs/adr/0012-test-time-compute-and-model-development-authority.md b/docs/adr/0012-test-time-compute-and-model-development-authority.md new file mode 100644 index 000000000..dbf1cb13d --- /dev/null +++ b/docs/adr/0012-test-time-compute-and-model-development-authority.md @@ -0,0 +1,84 @@ +# ADR 0012: Test-time compute and model-assisted development authority + +**Status:** Accepted architecture + +## Context + +LifeOS uses deterministic product logic for authorization, persistence, proposal validation, safety checks, merge eligibility and release acceptance, while selected development and live-conformance workflows may call language models. The repository already has a strong single-route proposal-quality baseline and a bounded contextual-orchestrator evaluation path. Repository-wide agent guidance also requires model-backed work to use `NVIDIA_NIM_API_KEY`, prohibits `COPILOT_GITHUB_TOKEN`, and keeps independent review credentials separate. + +Recent orchestration evidence does not justify a universal rule that more agents are better. Sakana Fugu exposes query-adaptive direct-or-orchestrated execution; the ICLR 2026 Conductor work learns worker selection, targeted instructions, communication topology and recursive orchestration; TRINITY assigns Thinker, Worker and Verifier roles over multiple turns. Counterevidence from a strong-single-agent baseline shows that homogeneous multi-agent workflows can sometimes be matched by one multi-turn agent with efficiency advantages. LifeOS therefore needs an explicit evidence-driven allocation rule rather than a fixed multi-agent preference. + +The detailed live-conformance implementation and references remain in `docs/superpowers/specs/2026-08-06-ai-nim-live-conformance-design.md`. This ADR promotes only the durable repository-wide authority decision. + +## Decision drivers + +- Product correctness, evidence quality, controllability and security outrank latency. +- Additional test-time compute must be justified against a strong simpler baseline under a comparable declared budget. +- Workflow stages, task decomposition, recursion depth, model/role selection, role-specific reasoning effort, verifier topology and access lists must remain explicit experimental variables instead of hidden orchestration defaults. +- Model/provider availability must not become authorization, review, merge or release authority. +- Live-provider evidence must be reproducible, bounded, credential-safe and separable from deterministic pull-request gates. +- LifeOS and contextual-orchestrator must remain independently deployable. + +## Alternatives + +### Always use a single model + +This minimizes orchestration complexity and is a necessary baseline, but it can prevent measured gains from heterogeneous specialization, parallel exploration or independent verification. + +### Always use a fixed multi-agent workflow + +This provides predictable topology but spends extra compute on tasks where it may not help, can hide which design dimension produced a gain, and conflicts with evidence that strong single-agent workflows may equal homogeneous multi-agent systems. + +### Adapt compute from measured evidence — selected + +Measure a strong single-model route first, compare additional bounded cells under explicit budgets and controls, and authorize deeper orchestration only when retained LifeOS evidence shows a meaningful quality/evidence gain without deterministic safety or conformance regression. + +## Decision + +1. **Strong baseline first.** Every material model-assisted evaluation includes a strong single-model route before any claim that a conducted or multi-agent profile is preferable. +2. **Explicit test cells.** Reasoning effort, workflow stage, decomposition, recursion depth, worker/model assignment, role-specific reasoning effort, verifier topology, access list/topology and total provider-call/token budget are explicit configuration or evidence fields where supported. +3. **Comparable budgets.** Claims about orchestration benefit compare cells under documented comparable budgets or clearly disclose the budget difference as a limitation. More agents or tokens are never counted as an intrinsic product improvement. +4. **Quality-first selection.** Latency, tokens and provider cost are measured for capacity and commercial review but are not the primary optimization objective. Correctness, evidence quality, safety, reliability and controllability determine acceptance. +5. **Unsupported controls stay unavailable.** A pinned orchestrator that cannot expose a requested recursion, role-effort or generated-topology control returns explicit unsupported evidence. Tests do not simulate or fabricate that ablation. +6. **Credential boundary.** Model-backed LifeOS development/live tests use GitHub Secret `NVIDIA_NIM_API_KEY`, preferably through the exact reviewed contextual-orchestrator integration. `COPILOT_GITHUB_TOKEN` is prohibited for development-model execution. +7. **Independent reviewer boundary.** Existing review-agent identities, credentials and keys remain independent and are never repurposed as development-model authority. +8. **Deterministic authority.** Model outputs are untrusted proposals/evidence. Deterministic LifeOS authorization, schema validation, proposal evaluation, CI/security checks, formal review rules, branch protection, merge decision and release gates remain authoritative even if all models/providers are unavailable. +9. **No hidden reasoning retention.** Retained evidence is bounded and credential-free and excludes prompts, model responses, hidden reasoning and provider secrets unless a separately reviewed product contract explicitly requires otherwise. +10. **Standalone/MSA compatibility.** Normal LifeOS runtime, deterministic tests and release artifacts do not require contextual-orchestrator or NVIDIA availability. The integration composes versioned public contracts only. + +## Consequences + +- A simple route remains the default comparison rather than a second-class fallback. +- Multi-agent/conducted execution can be used when measured LifeOS evidence supports it, including heterogeneous-model settings where single-agent equivalence is not assumed. +- Evaluation reports become more verbose because they record supported/unsupported cells and budget limitations explicitly. +- Orchestrator capability changes require a reviewed pin/update and fresh evidence instead of silently changing the experiment. +- Provider outages can make live-conformance cells unavailable without turning deterministic CI green or red by inference. + +## Failure and recovery + +- Missing provider credentials or model inventory produces explicit unavailable evidence and no fabricated quality result. +- Provider/orchestrator failure cannot bypass deterministic proposal validation or repository gates. +- If a conducted cell regresses injection resistance, operation conformance, grounding or other primary quality criteria, retain the strong single-route profile. +- If a newer orchestrator changes workflow semantics, freeze the old reviewed pin until the new source, contract tests and result schema are reviewed. +- If evidence later shows a different baseline or budget-allocation method is materially better, supersede this ADR rather than weakening the comparison contract ad hoc. + +## Security and privacy impact + +Only the credential-seeding step may receive `NVIDIA_NIM_API_KEY`. LifeOS application code and retained artifacts do not receive or serialize provider credentials. Model execution receives bounded fixture/user data according to the reviewed feature contract and never gains database, branch-protection, review, merge or release authority. Secrets, raw prompts/responses and hidden reasoning are excluded from retained evidence by default. The contextual-orchestrator dependency is pinned to an exact reviewed commit when used. + +## Acceptance evidence + +- `AGENTS.md` preserves the NVIDIA NIM/no-Copilot and explicit orchestration-variable rules. +- Root `ARCHITECTURE.md` preserves the strong-route-first and deterministic-authority boundaries. +- `docs/UML.md` shows credential seeding, route/conduct cells, deterministic LifeOS evaluation, credential-free evidence and governance authority separation. +- `docs/STANDARDS_TRACEABILITY.md` records Fugu, Conductor, TRINITY, strong-single-agent counterevidence and NVIDIA NIM primary documentation with publication status and APA 7 references. +- `packages/commercial-readiness/src/documentation-contract.test.mjs` fails if these canonical decisions disappear. +- The live-conformance harness validates bounded report schemas, unsupported cells and credential scoping without making live provider availability a pull-request merge gate. + +## Migration and rollback + +Existing deterministic proposal behavior needs no data migration. Model-assisted workflows should migrate by adding explicit profile/budget evidence fields while preserving previous report versions for dated evidence. A rollback disables or removes a model-assisted profile without changing deterministic product authorization or stored LifeOS user data. Credential names and independent reviewer credentials are not migrated by this ADR. + +## Supersession + +Supersede this ADR if LifeOS adopts a materially different model-development authority, eliminates model-assisted evaluation entirely, changes the deterministic-vs-model governance split, or obtains stronger product evidence that requires a different baseline/budget-selection contract. A provider or model change alone does not supersede the decision. diff --git a/docs/adr/0013-service-owned-persistence.md b/docs/adr/0013-service-owned-persistence.md new file mode 100644 index 000000000..2119cbf3d --- /dev/null +++ b/docs/adr/0013-service-owned-persistence.md @@ -0,0 +1,36 @@ +# ADR 0013: Domain-oriented service-owned persistence + +**Status:** Accepted architecture +**Date:** 2026-08-10 + +## Context +LifeOS evolved from a simple app concept into independently runnable bounded services. A shared database authority would make those boundaries nominal and increase tenant, migration and deployment coupling. + +## Decision drivers +Independent operation, modular MSA composition, least privilege, migration ownership, fault isolation, explicit versioned interoperability. + +## Alternatives considered +- shared tables/read access across services — rejected; +- one monolithic persistence layer — rejected as durable architecture; +- service-owned persistence with API/event/saga/plugin contracts — selected. + +## Decision +Each bounded service owns its persistence adapters, migrations and database credentials. Services never read or mutate another service's tables directly. Shared UUIDs are logical references only. Cross-service effects use versioned HTTP/event/saga/plugin contracts. + +## Consequences +More explicit integration contracts and eventual-consistency handling are required, but services remain independently deployable/testable and database privileges can be least-privilege. + +## Failure and recovery +A service/database outage fails only the affected authority where possible. Cross-service workflows retain idempotency/reconciliation evidence rather than bypassing ownership with emergency SQL. + +## Security and privacy impact +Compromise of one service credential must not imply access to every domain table. Tenant authorization remains enforced by the owning service. + +## Acceptance evidence +Protected-main service layout, per-service migrations/repositories and architecture tests; logical data model labels ownership explicitly. + +## Migration and rollback +Any shared-table legacy coupling must be inventoried and replaced with a versioned contract before privileges are removed. Rollback preserves service-owned authority. + +## Supersession +Only a reviewed repository-wide data-authority ADR with migration/security/operability evidence may supersede this decision. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 000000000..810345e95 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,31 @@ +# LifeOS Architecture Decision Records + +ADRs capture durable decisions that must not be reconstructed from chat history or old PR bodies. Protected-main source/tests remain implementation evidence. + +## Status vocabulary + +ADR status uses the same exact canonical documentation values: `Implemented on protected main`, `Implemented on active PR`, `Partial`, `Accepted architecture`, `Planned`, `Research only`, `Superseded`, `Out of scope`. + +## Index + +| ADR | Status | Decision | +| --- | --- | --- | +| [0001](0001-opaque-non-numeric-identifiers.md) | Accepted architecture | Internal identifiers are opaque UUIDv4; old UUIDv7 design language is superseded | +| [0002](0002-oauth-transactions-and-session-tokens.md) | Accepted architecture | Server-owned OAuth transaction/session security and authentication provenance | +| [0003](0003-adaptive-contextual-orchestrator-proposal-default.md) | Implemented on protected main | Production proposal requests use adaptive contextual-orchestrator authority rather than provider-native structured-output routing | +| [0004](0004-inert-auditable-ai-proposals.md) | Accepted architecture | AI output is inert auditable proposal evidence with explicit decisions | +| [0005](0005-purpose-bound-sensitive-data-access.md) | Accepted architecture | Sensitive access is tenant/resource/purpose/lifetime/audit bound | +| [0006](0006-work-conserving-autonomous-maintenance.md) | Accepted architecture | Autonomous maintenance is exact-state, single-writer and work-conserving | +| [0007](0007-canonical-documentation-graph.md) | Accepted architecture | One code-current canonical documentation graph with explicit maturity | +| [0008](0008-separate-capability-maturity-from-buyer-gap-exhaustion.md) | Accepted architecture | Capability maturity is separate from buyer-gap exhaustion | +| [0009](0009-product-hosting-and-data-evolution.md) | Accepted architecture | Server-backed self-hostable modular MSA supersedes browser-only/single-app primary architecture | +| [0010](0010-verification-evidence-identity.md) | Accepted architecture | Contributor source, PR-base snapshot, live base, synthetic merge, workflow checkout, protected-main and release identities remain separate evidence authorities | +| [0011](0011-external-integration-authority-and-secret-references.md) | Accepted architecture | External integration metadata uses LifeOS-owned identity, separate secret references and explicit host-granted capability authority | +| [0012](0012-test-time-compute-and-model-development-authority.md) | Accepted architecture | Model-assisted development remains subordinate to deterministic review/merge/release authority and a governed contextual-orchestrator boundary | +| [0013](0013-service-owned-persistence.md) | Accepted architecture | Bounded services own persistence/migrations/credentials and never cross-write tables | + +## ADR quality contract + +Material ADRs contain: context; decision drivers; alternatives; decision; consequences; failure/recovery; security/privacy/governance impact; acceptance evidence; migration/rollback; and supersession conditions. + +A feature plan is not a substitute for an ADR when authority, identity, persistence, security, deployment, interoperability or release criteria change. diff --git a/docs/operations/contextual-orchestrator-proposal-transport.md b/docs/operations/contextual-orchestrator-proposal-transport.md index ae0885bdf..c17370168 100644 --- a/docs/operations/contextual-orchestrator-proposal-transport.md +++ b/docs/operations/contextual-orchestrator-proposal-transport.md @@ -60,8 +60,7 @@ LifeOS sends: - temperature `0` - streaming disabled - no tools or function definitions -- explicit `orchestration_mode: auto` with trace disclosure disabled -- no provider-native `response_format`, because that gateway passthrough would pin the request to one worker instead of adaptive orchestration +- a strict JSON Schema response format The model may propose only: @@ -69,7 +68,7 @@ The model may propose only: - `prioritize_item` - `schedule_item` -The fixed system instruction requires one JSON object, and every output still passes the independent `ProposalService` validator. Removing provider-native structured-output passthrough does not relax the LifeOS trust boundary; malformed or unsupported output fails closed after adaptive orchestration. Unknown properties, empty or oversized text, unsupported operation kinds, malformed UUIDv4 targets, excessive arrays, invalid timestamps, and invalid identifiers fail closed. A successful proposal always carries `requiresConfirmation: true` and is persisted before return. +Every output still passes the independent `ProposalService` validator. Unknown properties, empty or oversized text, unsupported operation kinds, malformed UUIDv4 targets, excessive arrays, invalid timestamps, and invalid identifiers fail closed. A successful proposal always carries `requiresConfirmation: true` and is persisted before return. ## Resource bounds diff --git a/package.json b/package.json index 45f7a489e..019d73072 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ }, "pnpm": { "overrides": { - "nanoid": "3.3.18", "postcss": "8.5.23", "sharp": "0.35.0" } diff --git a/packages/commercial-readiness/package.json b/packages/commercial-readiness/package.json index db73c14f7..756d5efc6 100644 --- a/packages/commercial-readiness/package.json +++ b/packages/commercial-readiness/package.json @@ -4,9 +4,9 @@ "private": true, "type": "module", "scripts": { - "build": "node --check src/cli.mjs && node --check src/github-client.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs && node --check src/workflow-registry.mjs", - "lint": "node --check src/schema.mjs && node --check src/audit.mjs && node --check src/pr-gate.mjs && node --check src/render.mjs && node --check src/github-client.mjs && node --check src/cli.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs && node --check src/workflow-registry.mjs", + "build": "node --check src/cli.mjs && node --check src/github-client.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs", + "lint": "node --check src/schema.mjs && node --check src/audit.mjs && node --check src/pr-gate.mjs && node --check src/render.mjs && node --check src/github-client.mjs && node --check src/cli.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs", "test": "node --test src/*.test.mjs", - "typecheck": "node --check src/schema.mjs && node --check src/audit.mjs && node --check src/pr-gate.mjs && node --check src/render.mjs && node --check src/github-client.mjs && node --check src/cli.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs && node --check src/workflow-registry.mjs" + "typecheck": "node --check src/schema.mjs && node --check src/audit.mjs && node --check src/pr-gate.mjs && node --check src/render.mjs && node --check src/github-client.mjs && node --check src/cli.mjs && node --check src/buyer-gaps.mjs && node --check src/buyer-gap-cli.mjs" } } diff --git a/packages/commercial-readiness/src/cli.mjs b/packages/commercial-readiness/src/cli.mjs index 6ff263231..d8c09f04d 100644 --- a/packages/commercial-readiness/src/cli.mjs +++ b/packages/commercial-readiness/src/cli.mjs @@ -17,7 +17,6 @@ import { validateCommercialReadinessPolicy, validateGitHubSnapshot, } from './schema.mjs'; -import { collectWorkflowRegistrySnapshot } from './workflow-registry.mjs'; const COMMANDS = Object.freeze({ snapshot: { @@ -30,10 +29,6 @@ const COMMANDS = Object.freeze({ ]), booleans: new Set(), }, - 'workflow-registry': { - values: new Set(['repository', 'output', 'commit', 'generatedAt']), - booleans: new Set(), - }, audit: { values: new Set([ 'manifest', @@ -181,36 +176,6 @@ async function commandSnapshot(options) { ); } -/** - * Collects read-only Actions workflow-registry evidence for one exact repository - * commit and persists the complete bounded snapshot as JSON at the required output. - * - * `repository`, `output`, and `commit` are required command options. Collection is - * read-only; if the persisted snapshot reports any active orphan workflow identities, - * the command throws only after writing that evidence so operators retain the receipt. - */ -export async function commandWorkflowRegistry( - options, - client = githubClientFromEnvironment(), -) { - requireOptions(options, ['repository', 'output', 'commit']); - const snapshot = await collectWorkflowRegistrySnapshot( - client, - options.repository, - options.commit, - { generatedAt: options.generatedAt ?? new Date().toISOString() }, - ); - await writeJson(options.output, snapshot); - console.log( - `workflow registry: ${snapshot.workflow_count} identity record(s), ${snapshot.active_orphans.length} active orphan(s)`, - ); - if (snapshot.active_orphans.length > 0) { - throw new Error( - `Workflow registry contains ${snapshot.active_orphans.length} active orphan identity record(s)`, - ); - } -} - async function commandAudit(options) { requireOptions(options, [ 'manifest', @@ -324,9 +289,6 @@ async function commandDrain(options) { async function main(argv = process.argv.slice(2)) { const { command, options } = parseArguments(argv); if (command === 'snapshot') return await commandSnapshot(options); - if (command === 'workflow-registry') { - return await commandWorkflowRegistry(options); - } if (command === 'audit') return await commandAudit(options); if (command === 'publish') return await commandPublish(options); if (command === 'drain') return await commandDrain(options); diff --git a/packages/commercial-readiness/src/cli.test.mjs b/packages/commercial-readiness/src/cli.test.mjs index 2d85dace1..e12904ddc 100644 --- a/packages/commercial-readiness/src/cli.test.mjs +++ b/packages/commercial-readiness/src/cli.test.mjs @@ -3,64 +3,7 @@ import { mkdtemp, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, it } from 'node:test'; -import { - commandWorkflowRegistry, - parseArguments, - readJsonFile, -} from './cli.mjs'; - -const workflowCommit = 'b'.repeat(40); -const workflowTree = 'c'.repeat(40); -const workflowPath = '.github/workflows/commercial-readiness.yml'; - -function createWorkflowRegistryClient({ - treePaths = [workflowPath], - workflows = [ - { - id: 101, - name: 'Commercial Readiness', - path: workflowPath, - state: 'active', - }, - ], - workflowResponse, -} = {}) { - return { - async requestJson(path) { - if (path === '/repos/ContextualWisdomLab/life-os') { - return { default_branch: 'main' }; - } - if (path === '/repos/ContextualWisdomLab/life-os/branches/main') { - return { commit: { sha: workflowCommit } }; - } - if ( - path === - `/repos/ContextualWisdomLab/life-os/git/commits/${workflowCommit}` - ) { - return { sha: workflowCommit, tree: { sha: workflowTree } }; - } - if ( - path === - `/repos/ContextualWisdomLab/life-os/git/trees/${workflowTree}?recursive=1` - ) { - return { - truncated: false, - tree: treePaths.map((entryPath) => ({ - path: entryPath, - type: 'blob', - })), - }; - } - if ( - path === - '/repos/ContextualWisdomLab/life-os/actions/workflows?per_page=100&page=1' - ) { - return workflowResponse ?? { total_count: workflows.length, workflows }; - } - throw new Error(`Unexpected GitHub test request: ${path}`); - }, - }; -} +import { parseArguments, readJsonFile } from './cli.mjs'; describe('parseArguments', () => { it('parses bounded command options without interpreting values as shell syntax', () => { @@ -88,31 +31,6 @@ describe('parseArguments', () => { ); }); - it('parses the read-only workflow registry evidence command', () => { - assert.deepEqual( - parseArguments([ - 'workflow-registry', - '--repository', - 'ContextualWisdomLab/life-os', - '--commit', - 'b'.repeat(40), - '--generated-at', - '2026-08-13T11:30:00.000Z', - '--output', - 'out/workflow-registry.json', - ]), - { - command: 'workflow-registry', - options: { - repository: 'ContextualWisdomLab/life-os', - commit: 'b'.repeat(40), - generatedAt: '2026-08-13T11:30:00.000Z', - output: 'out/workflow-registry.json', - }, - }, - ); - }); - it('rejects unknown commands, duplicate options, missing values, and positional arguments', () => { for (const argv of [ ['unknown'], @@ -129,102 +47,6 @@ describe('parseArguments', () => { }); }); -describe('commandWorkflowRegistry', () => { - it('persists realistic orphan-free workflow registry evidence', async () => { - const root = await mkdtemp(join(tmpdir(), 'life-os-workflow-registry-')); - const output = join(root, 'workflow-registry.json'); - - await commandWorkflowRegistry( - { - repository: 'ContextualWisdomLab/life-os', - commit: workflowCommit, - generatedAt: '2026-08-13T11:30:00.000Z', - output, - }, - createWorkflowRegistryClient(), - ); - - const evidence = await readJsonFile(output); - assert.equal(evidence.schema, 'life-os.workflow-registry-snapshot.v1'); - assert.equal(evidence.commit_sha, workflowCommit); - assert.equal(evidence.tree_sha, workflowTree); - assert.equal(evidence.workflow_count, 1); - assert.deepEqual(evidence.active_orphans, []); - assert.deepEqual(evidence.present, [ - { - id: 101, - name: 'Commercial Readiness', - path: workflowPath, - state: 'active', - }, - ]); - }); - - it('persists active-orphan evidence before failing the command', async () => { - const root = await mkdtemp(join(tmpdir(), 'life-os-workflow-registry-')); - const output = join(root, 'workflow-registry.json'); - const orphan = { - id: 202, - name: 'Legacy repair', - path: '.github/workflows/legacy-repair.yml', - state: 'active', - }; - - await assert.rejects( - () => - commandWorkflowRegistry( - { - repository: 'ContextualWisdomLab/life-os', - commit: workflowCommit, - generatedAt: '2026-08-13T11:30:00.000Z', - output, - }, - createWorkflowRegistryClient({ treePaths: [], workflows: [orphan] }), - ), - /contains 1 active orphan identity record/, - ); - - const evidence = await readJsonFile(output); - assert.deepEqual(evidence.active_orphans, [orphan]); - assert.equal(evidence.registry_receipt.total_count, 1); - }); - - it('fails closed on incomplete registry collection without publishing a snapshot', async () => { - const root = await mkdtemp(join(tmpdir(), 'life-os-workflow-registry-')); - const output = join(root, 'workflow-registry.json'); - - await assert.rejects( - () => - commandWorkflowRegistry( - { - repository: 'ContextualWisdomLab/life-os', - commit: workflowCommit, - generatedAt: '2026-08-13T11:30:00.000Z', - output, - }, - createWorkflowRegistryClient({ - workflowResponse: { - total_count: 2, - workflows: [ - { - id: 101, - name: 'Commercial Readiness', - path: workflowPath, - state: 'active', - }, - ], - }, - }), - ), - /pagination was truncated/, - ); - await assert.rejects( - () => readJsonFile(output), - (error) => error?.code === 'ENOENT', - ); - }); -}); - describe('readJsonFile', () => { it('reads bounded regular JSON files and rejects symlinks or oversized input', async () => { const root = await mkdtemp(join(tmpdir(), 'life-os-cli-')); diff --git a/packages/commercial-readiness/src/documentation-contract.test.mjs b/packages/commercial-readiness/src/documentation-contract.test.mjs new file mode 100644 index 000000000..f341449cc --- /dev/null +++ b/packages/commercial-readiness/src/documentation-contract.test.mjs @@ -0,0 +1,301 @@ +import assert from 'node:assert/strict'; +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const ROOT = fileURLToPath(new URL('../../../', import.meta.url)); +const REQUIRED = Object.freeze([ + 'docs/PRD.md', + 'docs/TRD.md', + 'ARCHITECTURE.md', + 'docs/adr/README.md', + 'docs/DATA_MODEL.md', + 'docs/UML.md', + 'docs/API_CONTRACTS.md', + 'SECURITY.md', + 'docs/THREAT_MODEL.md', + 'docs/PRIVACY_DATA_LIFECYCLE.md', + 'docs/TEST_STRATEGY.md', + 'docs/OPERABILITY.md', + 'docs/RELEASE_AND_MIGRATION.md', + 'docs/STANDARDS_TRACEABILITY.md', + 'docs/TRACEABILITY.md', + 'docs/DOCUMENTATION_ASSESSMENT.md', +]); +const STATUSES = Object.freeze([ + 'Implemented on protected main', + 'Implemented on active PR', + 'Partial', + 'Accepted architecture', + 'Planned', + 'Research only', + 'Superseded', + 'Out of scope', +]); + +/** Reads a repository UTF-8 file. */ +function text(path) { + return readFileSync(join(ROOT, path), 'utf8'); +} + +/** Returns local Markdown link targets from one Markdown document. */ +function localLinks(relativePath) { + return [...text(relativePath).matchAll(/\[[^\]]+\]\(([^)]+)\)/gu)] + .map((match) => match[1].split('#', 1)[0]) + .filter((target) => target && !/^[a-z][a-z0-9+.-]*:/iu.test(target)); +} + +/** Resolves a local Markdown target from its containing document. */ +function resolveLocal(relativePath, target) { + return join(ROOT, dirname(relativePath), target); +} + +/** Extracts exact **Status:** metadata values. */ +function metadataStatuses(body) { + return [...body.matchAll(/^\*\*Status:\*\* ([^\r\n]+)$/gmu)].map((match) => + match[1].trim(), + ); +} + +/** Splits a Markdown table row into normalized cells. */ +function tableCells(line) { + return line + .trim() + .replace(/^\|/u, '') + .replace(/\|$/u, '') + .split('|') + .map((value) => value.trim()); +} + +/** Returns whether every cell is a Markdown table separator. */ +function isSeparatorRow(cells) { + return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/u.test(cell)); +} + +/** Extracts values only from table columns whose exact header is Status. */ +function tableStatuses(body) { + const lines = body.split('\n'); + const statuses = []; + for (let index = 0; index < lines.length - 1; index += 1) { + if (!lines[index].trimStart().startsWith('|')) continue; + const headers = tableCells(lines[index]); + const statusIndex = headers.indexOf('Status'); + if (statusIndex < 0) continue; + const separators = tableCells(lines[index + 1]); + if (separators.length !== headers.length || !isSeparatorRow(separators)) continue; + + for (let rowIndex = index + 2; rowIndex < lines.length; rowIndex += 1) { + if (!lines[rowIndex].trimStart().startsWith('|')) break; + const cells = tableCells(lines[rowIndex]); + if (cells.length !== headers.length && !isSeparatorRow(cells)) continue; + if (cells.length === headers.length && !isSeparatorRow(cells)) { + statuses.push(cells[statusIndex]); + } + } + } + return statuses; +} + +test('canonical documentation files exist and are linked from README', () => { + const readme = text('README.md'); + for (const path of REQUIRED) { + assert.equal(existsSync(join(ROOT, path)), true, `missing ${path}`); + assert.ok(readme.includes(`](${path})`), `README missing link to ${path}`); + } +}); + +test('local README and canonical-document links resolve to repository files', () => { + for (const path of ['README.md', ...REQUIRED.filter((item) => item.endsWith('.md'))]) { + for (const target of localLinks(path)) { + assert.equal( + existsSync(resolveLocal(path, target)), + true, + `${path} has broken local link ${target}`, + ); + } + } +}); + +test('canonical status metadata and requirement tables use the exact vocabulary', () => { + for (const path of REQUIRED.filter((item) => item.endsWith('.md'))) { + const body = text(path); + for (const status of [...metadataStatuses(body), ...tableStatuses(body)]) { + assert.ok(STATUSES.includes(status), `${path} has invalid status: ${status}`); + } + } +}); + +test('ADR index targets every material ADR and ADRs satisfy the quality contract', () => { + const index = text('docs/adr/README.md'); + const files = readdirSync(join(ROOT, 'docs/adr')) + .filter((name) => /^\d{4}-.+\.md$/u.test(name)) + .sort(); + const requiredNumbers = new Set([ + '0001', '0002', '0003', '0004', '0005', '0006', + '0007', '0008', '0009', '0010', '0011', '0012', + ]); + + for (const number of requiredNumbers) { + assert.ok(files.some((name) => name.startsWith(`${number}-`)), `missing ADR ${number}`); + } + + for (const file of files) { + const number = file.slice(0, 4); + assert.ok(index.includes(`[${number}](${file})`), `${file} missing exact index target`); + const body = text(`docs/adr/${file}`); + assert.ok(STATUSES.includes(metadataStatuses(body)[0]), `${file} has invalid status`); + for (const heading of [ + '## Context', + '## Decision', + '## Consequences', + '## Failure and recovery', + '## Security and privacy impact', + '## Acceptance evidence', + '## Migration and rollback', + '## Supersession', + ]) { + assert.ok(body.includes(heading), `${file} missing ${heading}`); + } + } +}); + +test('canonical Markdown keeps balanced fenced code blocks', () => { + for (const path of REQUIRED.filter((item) => item.endsWith('.md'))) { + const count = text(path) + .split('\n') + .filter((line) => line.trimStart().startsWith('```')).length; + assert.equal(count % 2, 0, `${path} has an unbalanced code fence`); + } +}); + +test('root architecture remains semantically anchored to protected-main authority', () => { + const architecture = text('ARCHITECTURE.md'); + const agents = text('AGENTS.md'); + const dataRights = text('apps/identity-service/src/data-rights.ts'); + const proposals = text('apps/ai-service/src/proposal-service.ts'); + + assert.match(agents, /Internal identifiers are opaque UUIDv4 strings/u); + assert.match(dataRights, /UUID_V4_PATTERN/u); + assert.match(architecture, /never read or mutate another service's tables directly/u); + assert.match(architecture, /Authentication-ceremony time is distinct/u); + assert.match(architecture, /Durable Today synchronization is protected-main behavior/u); + assert.match(architecture, /PR #150 added/u); + assert.match(architecture, /PR #153 added atomic/u); + assert.match(architecture, /PR #151/u); + assert.match(architecture, /Privacy owns purpose-bound sensitive-access decisions/u); + assert.match(architecture, /Notification owns reminder occurrences/u); + assert.match(architecture, /docs\/PRD\.md/u); + assert.match(proposals, /requiresConfirmation: true/u); + assert.match(proposals, /cannot execute its own operations/u); +}); + +test('protected lifecycle and remaining parent gaps are represented truthfully', () => { + const prd = text('docs/PRD.md'); + const traceability = text('docs/TRACEABILITY.md'); + const contracts = text('docs/API_CONTRACTS.md'); + const privacy = text('docs/PRIVACY_DATA_LIFECYCLE.md'); + const dataModel = text('docs/DATA_MODEL.md'); + + for (const protectedPr of [ + '#127', '#139', '#146', '#149', '#150', '#151', '#153', '#154', '#155', + ]) { + assert.match(prd, new RegExp(`PR ${protectedPr}`, 'u')); + } + assert.match(traceability, /PRD-CAL-004.*Implemented on protected main/u); + assert.match(traceability, /PRD-CAL-005.*Implemented on protected main/u); + assert.match(traceability, /PRD-INT-003.*Implemented on protected main/u); + assert.match(traceability, /PRD-PRIV-004.*Implemented on protected main/u); + assert.match(traceability, /PRD-PRIV-005.*Implemented on protected main/u); + assert.match(contracts, /Atomic calendar connection revocation.*Implemented on protected main/u); + assert.match(contracts, /Explicit plugin installation grants.*Implemented on protected main/u); + assert.match(privacy, /atomic local connection revocation \(#153\)/u); + assert.match(dataModel, /PR #153 added atomic tenant\+user-scoped revocation/u); + assert.match(traceability, /#55 data portability completion/u); + assert.match(traceability, /#129 hosted per-user calendar credentials/u); + assert.match(traceability, /#130 plugin runtime delivery/u); +}); + +test('current successor maturity follows protected main and active work', () => { + const prd = text('docs/PRD.md'); + const traceability = text('docs/TRACEABILITY.md'); + const contracts = text('docs/API_CONTRACTS.md'); + const uml = text('docs/UML.md'); + const architecture = text('ARCHITECTURE.md'); + const assessment = text('docs/DOCUMENTATION_ASSESSMENT.md'); + const dataModel = text('docs/DATA_MODEL.md'); + const integrationAuthority = text( + 'docs/adr/0011-external-integration-authority-and-secret-references.md', + ); + + for (const protectedPr of ['#154', '#155']) { + assert.match(prd, new RegExp(`PR ${protectedPr}`, 'u')); + assert.match(traceability, new RegExp(`PR ${protectedPr}`, 'u')); + assert.match(assessment, new RegExp(`PR ${protectedPr}`, 'u')); + } + assert.match(prd, /PR #156/u); + assert.match(traceability, /PR #156/u); + assert.match(assessment, /PR #156/u); + assert.match(dataModel, /PR #156/u); + assert.match(architecture, /PR #155.*Implemented on protected main/su); + assert.match(architecture, /PR #154.*Implemented on protected main/su); + assert.match(architecture, /Old PR #147 is \*\*Superseded\*\*/u); + assert.match(uml, /PR #154.*protected main/su); + assert.match(uml, /life-os\.calendar-user\.v1/u); + assert.match(assessment, /protected-main documentation insufficient/iu); + assert.match(integrationAuthority, /opaque secret handle/iu); + assert.match(integrationAuthority, /manifest expresses requested intent/iu); + assert.match(contracts, /PR #156/u); +}); + +test('model-assisted compute authority and counterevidence remain canonical', () => { + const architecture = text('ARCHITECTURE.md'); + const standards = text('docs/STANDARDS_TRACEABILITY.md'); + const uml = text('docs/UML.md'); + const traceability = text('docs/TRACEABILITY.md'); + const agents = text('AGENTS.md'); + const adr = text('docs/adr/0012-test-time-compute-and-model-development-authority.md'); + + assert.match(adr, /strong single-model route/iu); + assert.match(adr, /workflow stages/iu); + assert.match(adr, /decomposition/iu); + assert.match(adr, /recursion depth/iu); + assert.match(adr, /role-specific reasoning effort/iu); + assert.match(adr, /access (?:list|topology)/iu); + assert.match(adr, /NVIDIA_NIM_API_KEY/u); + assert.match(adr, /COPILOT_GITHUB_TOKEN/u); + assert.match(adr, /deterministic/iu); + + for (const evidenceName of ['Fugu', 'Conductor', 'TRINITY']) { + assert.match(standards, new RegExp(evidenceName, 'u')); + } + assert.match(standards, /Rethinking the value of multi-agent workflow/iu); + assert.match(standards, /preprint/iu); + assert.match(standards, /ICLR 2026/iu); + assert.match(standards, /NVIDIA NIM/iu); + assert.match(standards, /repository-specific/iu); + + assert.match(uml, /NVIDIA_NIM_API_KEY/u); + assert.match(uml, /contextual-orchestrator/iu); + assert.match(uml, /single-route/iu); + assert.match(uml, /conduct/iu); + assert.match(uml, /deterministic LifeOS proposal evaluator/iu); + assert.match(uml, /review.*merge.*release/isu); + + assert.match(traceability, /ADR 0012/u); + assert.match(traceability, /Fugu/iu); + assert.match(architecture, /A strong single-model route is measured before deeper orchestration/u); + assert.match(agents, /NVIDIA_NIM_API_KEY/u); + assert.doesNotMatch(agents, /use\s+COPILOT_GITHUB_TOKEN/iu); +}); + +test('canonical authority does not regress to superseded product identity', () => { + const canonical = REQUIRED + .filter((path) => path.endsWith('.md')) + .map((path) => text(path)) + .join('\n'); + + assert.doesNotMatch(canonical, /UUIDv7 is the (?:current|primary|required) LifeOS identifier/iu); + assert.doesNotMatch(canonical, /login-free local-first is the (?:current|primary|required) architecture/iu); + assert.doesNotMatch(canonical, /single[- ]application is the (?:current|primary|required) durable architecture/iu); +}); diff --git a/packages/commercial-readiness/src/documentation-currentness.test.mjs b/packages/commercial-readiness/src/documentation-currentness.test.mjs new file mode 100644 index 000000000..ac9d9ea16 --- /dev/null +++ b/packages/commercial-readiness/src/documentation-currentness.test.mjs @@ -0,0 +1,88 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const ROOT = fileURLToPath(new URL('../../../', import.meta.url)); +const read = (path) => readFileSync(join(ROOT, path), 'utf8'); +const canonical = [ + 'ARCHITECTURE.md', + 'docs/PRD.md', + 'docs/TRD.md', + 'docs/DATA_MODEL.md', + 'docs/API_CONTRACTS.md', + 'docs/UML.md', + 'docs/THREAT_MODEL.md', + 'docs/PRIVACY_DATA_LIFECYCLE.md', + 'docs/TRACEABILITY.md', + 'docs/DOCUMENTATION_ASSESSMENT.md', +].map(read).join('\n'); +const traceability = read('docs/TRACEABILITY.md'); +const assessment = read('docs/DOCUMENTATION_ASSESSMENT.md'); + +/** Requires one exact current-active pull-request row with the requested maturity. */ +function assertActiveAssessmentRow(pullRequest, status = 'Implemented on active PR') { + const prefix = `| PR #${pullRequest} |`; + const row = assessment.split('\n').find((line) => line.startsWith(prefix)); + assert.ok(row, `missing active assessment row for PR #${pullRequest}`); + assert.match(row, new RegExp(`\\| ${status} \\|`, 'u')); +} + +/** Requires protected-main reconciliation on the same bounded line as the PR identity. */ +function assertProtectedAssessmentEvidence(pullRequest) { + assert.match( + assessment, + new RegExp(`^[-|].*PR #${pullRequest}\\b[^\\n]*Implemented on protected main`, 'mu'), + ); +} + +test('canonical maturity follows protected main and current active work', () => { + for (const pullRequest of [ + 157, 159, 168, 169, 172, 173, 175, 176, 179, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 200, 201, 203, + ]) { + assert.match(canonical, new RegExp(`PR #${pullRequest}\\b`, 'u')); + } + + for (const requirement of [ + 'PRD-PLAN-003', 'PRD-HAB-002', 'PRD-REV-002', 'PRD-CAL-007', + 'PRD-CAL-008', 'PRD-PRIV-007', 'PRD-PRIV-008', 'PRD-INT-004', + 'PRD-INT-005', 'PRD-INT-006', 'PRD-WEB-002', + ]) { + assert.match( + traceability, + new RegExp(`${requirement}.*Implemented on protected main`, 'u'), + ); + } + + for (const pullRequest of [ + 145, 198, 199, 204, 205, 208, 214, 216, 217, 228, 229, 234, 236, 245, + 250, + ]) { + assertActiveAssessmentRow(pullRequest); + } + for (const pullRequest of [154, 155, 156, 195, 200, 203]) { + assertProtectedAssessmentEvidence(pullRequest); + } + + assert.doesNotMatch( + canonical, + /PR #(?:156|160|162|165|175|176|178|179|195|200|203) (?:is \*\*Implemented on active PR\*\*|\| Implemented on active PR \|)/iu, + ); + assert.match(canonical, /Issue #163.*completed/iu); +}); + +test('canonical gaps remain bounded and truthful', () => { + assert.match( + traceability, + /Canonical buyer gaps remain #55, #129, #130, #209, and #210/u, + ); + assert.match(assessment, /Issue #132.*Partial/su); + assert.match(assessment, /PR #205[^\n]*host-owned delivery-origin authority foundation/iu); + assert.match(assessment, /PR #204[^\n]*read-only Actions workflow-registry detector/iu); + assert.match(assessment, /PR #228[^\n]*OAuth state\/PKCE/iu); + assert.match(assessment, /PR #250[^\n]*signed delivery-origin operator authority/iu); + assert.match(assessment, /#209[^\n]*Partial/iu); + assert.match(assessment, /#210[^\n]*Partial/iu); +}); diff --git a/packages/commercial-readiness/src/exact-head-workflow.test.mjs b/packages/commercial-readiness/src/exact-head-workflow.test.mjs index 9fcddf1f5..28e1d21b2 100644 --- a/packages/commercial-readiness/src/exact-head-workflow.test.mjs +++ b/packages/commercial-readiness/src/exact-head-workflow.test.mjs @@ -17,19 +17,10 @@ describe('commercial readiness exact-head contract', () => { const sourceExpression = '\\$\\{\\{ github\\.event\\.pull_request\\.head\\.sha \\|\\| github\\.sha \\}\\}'; assert.match(workflow, new RegExp(`ref: ${sourceExpression}`)); - - const snapshotStart = workflow.indexOf( - 'node packages/commercial-readiness/src/cli.mjs snapshot', - ); - assert.notEqual(snapshotStart, -1); - const snapshotEnd = workflow.indexOf('\n\n', snapshotStart); - assert.notEqual(snapshotEnd, -1); - const snapshotCommand = workflow.slice(snapshotStart, snapshotEnd); - assert.match( - snapshotCommand, + workflow, new RegExp(`--commit "${sourceExpression}"`), ); - assert.doesNotMatch(snapshotCommand, /--commit "\$GITHUB_SHA"/); + assert.doesNotMatch(workflow, /--commit "\$GITHUB_SHA"/); }); }); diff --git a/packages/commercial-readiness/src/workflow-registry.mjs b/packages/commercial-readiness/src/workflow-registry.mjs deleted file mode 100644 index c5f6c1154..000000000 --- a/packages/commercial-readiness/src/workflow-registry.mjs +++ /dev/null @@ -1,297 +0,0 @@ -const REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u; -const SHA_PATTERN = /^[0-9a-f]{40}$/iu; -const REPOSITORY_WORKFLOW_PATH_PATTERN = - /^\.github\/workflows\/[^/%\\\u0000-\u001f\u007f]+\.ya?ml$/u; -const CONTROL_OR_ESCAPE_PATTERN = /[\\%\u0000-\u001f\u007f]/u; -const PAGE_SIZE = 100; -const MAXIMUM_PAGES = 10; - -function invalid(message) { - throw new Error(message); -} - -function requireRepository(value) { - if (typeof value !== 'string' || !REPOSITORY_PATTERN.test(value)) { - return invalid('Workflow registry repository is invalid'); - } - const [owner, repository] = value.split('/'); - if (owner === '.' || owner === '..' || repository === '.' || repository === '..') { - return invalid('Workflow registry repository is invalid'); - } - return value; -} - -function requireSha(value) { - if (typeof value !== 'string' || !SHA_PATTERN.test(value)) { - return invalid('Workflow registry commit SHA is invalid'); - } - return value.toLowerCase(); -} - -function requireGeneratedAt(value) { - if (typeof value !== 'string') { - return invalid('Workflow registry timestamp is invalid'); - } - const date = new Date(value); - if (!Number.isFinite(date.getTime()) || date.toISOString() !== value) { - return invalid('Workflow registry timestamp is invalid'); - } - return value; -} - -function requireWorkflowPath(value) { - if ( - typeof value !== 'string' || - value.length === 0 || - value.length > 512 || - CONTROL_OR_ESCAPE_PATTERN.test(value) || - value.split('/').some((segment) => segment === '..' || segment === '.') - ) { - return invalid('Workflow registry path is invalid'); - } - if (value.startsWith('.github/') && !REPOSITORY_WORKFLOW_PATH_PATTERN.test(value)) { - return invalid('Workflow registry path is invalid'); - } - return value; -} - -function requireWorkflowRecord(value) { - if ( - !value || - typeof value !== 'object' || - !Number.isSafeInteger(value.id) || - value.id <= 0 || - typeof value.name !== 'string' || - value.name.length === 0 || - value.name.length > 512 || - typeof value.state !== 'string' || - value.state.length === 0 || - value.state.length > 64 - ) { - return invalid('Workflow registry identity is invalid'); - } - return Object.freeze({ - id: value.id, - name: value.name, - path: requireWorkflowPath(value.path), - state: value.state, - }); -} - -function sortById(values) { - return values.sort((left, right) => left.id - right.id); -} - -/** - * Classifies a complete Actions workflow registry against one exact repository tree. - * - * Names are deliberately non-authoritative: only exact case-sensitive repository - * paths decide whether a repository workflow is still present. Dynamic GitHub-owned - * workflow identities are retained separately rather than guessed from their names. - */ -export function classifyWorkflowRegistry({ commitSha, treePaths, workflows }) { - const commit = requireSha(commitSha); - if (!Array.isArray(treePaths) || !Array.isArray(workflows)) { - return invalid('Workflow registry evidence is invalid'); - } - - const presentPaths = new Set(); - for (const value of treePaths) { - if (typeof value !== 'string') return invalid('Workflow registry path is invalid'); - if (!value.startsWith('.github/workflows/')) continue; - const path = requireWorkflowPath(value); - if (REPOSITORY_WORKFLOW_PATH_PATTERN.test(path)) presentPaths.add(path); - } - - const seenIds = new Map(); - const present = []; - const activeOrphans = []; - const disabledOrphans = []; - const dynamic = []; - - for (const raw of workflows) { - const record = requireWorkflowRecord(raw); - const previousPath = seenIds.get(record.id); - if (previousPath !== undefined) { - return invalid('Workflow registry identity is ambiguous'); - } - seenIds.set(record.id, record.path); - - if (!REPOSITORY_WORKFLOW_PATH_PATTERN.test(record.path)) { - dynamic.push(record); - } else if (presentPaths.has(record.path)) { - present.push(record); - } else if (record.state === 'active') { - activeOrphans.push(record); - } else { - disabledOrphans.push(record); - } - } - - return Object.freeze({ - schema: 'life-os.workflow-registry-snapshot.v1', - commit_sha: commit, - workflow_count: workflows.length, - present: Object.freeze(sortById(present)), - active_orphans: Object.freeze(sortById(activeOrphans)), - disabled_orphans: Object.freeze(sortById(disabledOrphans)), - dynamic: Object.freeze(sortById(dynamic)), - }); -} - -async function collectWorkflowRegistry(client, repository) { - const workflows = []; - let expectedTotal = null; - - for (let page = 1; page <= MAXIMUM_PAGES; page += 1) { - const payload = await client.requestJson( - `/repos/${repository}/actions/workflows?per_page=${PAGE_SIZE}&page=${page}`, - ); - if ( - !payload || - !Number.isSafeInteger(payload.total_count) || - payload.total_count < 0 || - !Array.isArray(payload.workflows) || - payload.workflows.length > PAGE_SIZE - ) { - return invalid('GitHub workflow registry response is invalid'); - } - if (expectedTotal === null) expectedTotal = payload.total_count; - if (payload.total_count !== expectedTotal) { - return invalid('GitHub workflow registry changed during pagination'); - } - - workflows.push(...payload.workflows); - if (workflows.length > expectedTotal) { - return invalid('GitHub workflow registry pagination is inconsistent'); - } - if (workflows.length === expectedTotal) { - return Object.freeze({ - workflows: Object.freeze([...workflows]), - pages: page, - total_count: expectedTotal, - }); - } - if (payload.workflows.length < PAGE_SIZE) { - return invalid('GitHub workflow registry pagination was truncated'); - } - } - - return invalid('GitHub workflow registry pagination exceeded the page limit'); -} - -/** - * Extracts validated repository-owned workflow YAML paths from one complete Git tree. - * - * Returns exact case-sensitive `.github/workflows/*.yml|yaml` blob paths. Malformed, - * truncated, or unsafe workflow-shaped tree evidence fails closed; unrelated tree - * entries are ignored. - */ -function workflowPathsFromTree(payload) { - if (!payload || payload.truncated !== false || !Array.isArray(payload.tree)) { - return invalid('GitHub workflow tree was truncated or invalid'); - } - const paths = []; - for (const entry of payload.tree) { - if (!entry || entry.type !== 'blob' || typeof entry.path !== 'string') continue; - if (entry.path.startsWith('.github/workflows/')) { - requireWorkflowPath(entry.path); - } - if (REPOSITORY_WORKFLOW_PATH_PATTERN.test(entry.path)) paths.push(entry.path); - } - return paths; -} - -/** - * Reads and validates the exact commit SHA currently named by a default branch. - * - * Returns a normalized 40-character hexadecimal SHA. Missing or malformed GitHub - * branch evidence fails closed through the shared SHA validator. - */ -async function readDefaultBranchHead(client, repository, defaultBranch) { - const payload = await client.requestJson( - `/repos/${repository}/branches/${encodeURIComponent(defaultBranch)}`, - ); - return requireSha(payload?.commit?.sha); -} - -/** - * Reads the Git tree SHA bound to an exact commit and verifies commit identity first. - * - * Returns a normalized 40-character hexadecimal tree SHA. Malformed responses or a - * response whose commit SHA differs from the requested immutable commit fail closed. - */ -async function readCommitTreeSha(client, repository, commitSha) { - const payload = await client.requestJson( - `/repos/${repository}/git/commits/${commitSha}`, - ); - if (requireSha(payload?.sha) !== commitSha) { - return invalid('GitHub workflow commit evidence is inconsistent'); - } - return requireSha(payload?.tree?.sha); -} - -/** - * Builds read-only, pagination-complete Actions registry evidence for one unchanged - * protected default-branch head and its exact Git tree. Any branch movement, - * incomplete tree, or incomplete registry response fails closed so an orphan - * workflow cannot disappear by omission. - */ -export async function collectWorkflowRegistrySnapshot( - client, - repositoryValue, - expectedCommitSha, - { generatedAt = new Date().toISOString() } = {}, -) { - if (!client || typeof client.requestJson !== 'function') { - return invalid('GitHub workflow registry client is invalid'); - } - const repository = requireRepository(repositoryValue); - const expected = requireSha(expectedCommitSha); - const evidenceTimestamp = requireGeneratedAt(generatedAt); - const metadata = await client.requestJson(`/repos/${repository}`); - const defaultBranch = metadata?.default_branch; - if ( - typeof defaultBranch !== 'string' || - defaultBranch.length === 0 || - defaultBranch.length > 255 || - defaultBranch === '.' || - defaultBranch === '..' || - defaultBranch.includes('/') || - CONTROL_OR_ESCAPE_PATTERN.test(defaultBranch) - ) { - return invalid('GitHub default branch is invalid'); - } - - const initialHead = await readDefaultBranchHead(client, repository, defaultBranch); - if (initialHead !== expected) { - return invalid('Protected default branch moved before workflow inventory'); - } - - const treeSha = await readCommitTreeSha(client, repository, expected); - const treePayload = await client.requestJson( - `/repos/${repository}/git/trees/${treeSha}?recursive=1`, - ); - const treePaths = workflowPathsFromTree(treePayload); - const registry = await collectWorkflowRegistry(client, repository); - - const finalHead = await readDefaultBranchHead(client, repository, defaultBranch); - if (finalHead !== expected) { - return invalid('Protected default branch moved during workflow inventory'); - } - - const classified = classifyWorkflowRegistry({ - commitSha: expected, - treePaths, - workflows: registry.workflows, - }); - return Object.freeze({ - ...classified, - tree_sha: treeSha, - generated_at: evidenceTimestamp, - registry_receipt: Object.freeze({ - pages: registry.pages, - total_count: registry.total_count, - }), - }); -} diff --git a/packages/commercial-readiness/src/workflow-registry.test.mjs b/packages/commercial-readiness/src/workflow-registry.test.mjs deleted file mode 100644 index 5ce905c99..000000000 --- a/packages/commercial-readiness/src/workflow-registry.test.mjs +++ /dev/null @@ -1,329 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; - -import { - classifyWorkflowRegistry, - collectWorkflowRegistrySnapshot, -} from './workflow-registry.mjs'; - -const SHA = 'f'.repeat(40); -const TREE_SHA = 'a'.repeat(40); -const GENERATED_AT = '2026-08-12T12:00:00.000Z'; -const REPOSITORY = 'ContextualWisdomLab/life-os'; - -function workflow(id, path, state = 'active', name = `workflow-${id}`) { - return { id, name, path, state }; -} - -function inventoryClient(overrides = {}) { - let branchReads = 0; - return { - async requestJson(path) { - if (overrides[path]) return overrides[path](branchReads++); - if (path === `/repos/${REPOSITORY}`) return { default_branch: 'main' }; - if (path === `/repos/${REPOSITORY}/branches/main`) return { commit: { sha: SHA } }; - if (path === `/repos/${REPOSITORY}/git/commits/${SHA}`) { - return { sha: SHA, tree: { sha: TREE_SHA } }; - } - if (path === `/repos/${REPOSITORY}/git/trees/${TREE_SHA}?recursive=1`) { - return { truncated: false, tree: [] }; - } - if (path.endsWith('per_page=100&page=1')) return { total_count: 0, workflows: [] }; - throw new Error(`unexpected ${path}`); - }, - }; -} - -test('classifies repository workflows by exact path without trusting names', () => { - const snapshot = classifyWorkflowRegistry({ - commitSha: SHA, - treePaths: [ - '.github/dependabot.yml', - '.github/workflows/ci.yml', - '.github/workflows/live-repair.yml', - ], - workflows: [ - workflow(1, '.github/workflows/ci.yml', 'active', 'Repair-looking production name'), - workflow(2, '.github/workflows/deleted-repair.yml', 'active', 'CI'), - workflow(3, '.github/workflows/old.yml', 'disabled_manually'), - workflow(4, 'dynamic/dependabot/dependabot-updates', 'active'), - workflow(5, '.github/workflows/CI.yml', 'active', 'case-confusion'), - ], - }); - - assert.equal(snapshot.schema, 'life-os.workflow-registry-snapshot.v1'); - assert.equal(snapshot.commit_sha, SHA); - assert.deepEqual(snapshot.present.map((entry) => entry.id), [1]); - assert.deepEqual(snapshot.active_orphans.map((entry) => entry.id), [2, 5]); - assert.deepEqual(snapshot.disabled_orphans.map((entry) => entry.id), [3]); - assert.deepEqual(snapshot.dynamic.map((entry) => entry.id), [4]); -}); - -test('rejects ambiguous workflow identities and unsafe repository paths', () => { - assert.throws( - () => - classifyWorkflowRegistry({ - commitSha: SHA, - treePaths: ['.github/workflows/ci.yml'], - workflows: [ - workflow(7, '.github/workflows/ci.yml'), - workflow(7, '.github/workflows/renamed.yml'), - ], - }), - /identity/i, - ); - - for (const path of [ - '.github/workflows/%2e%2e.yml', - '.github/workflows/../ci.yml', - '.github\\workflows\\ci.yml', - ]) { - assert.throws( - () => - classifyWorkflowRegistry({ - commitSha: SHA, - treePaths: [], - workflows: [workflow(9, path)], - }), - /path/i, - ); - } -}); - -test('rejects relative repository and default-branch API path segments', async () => { - const unexpectedClient = { - async requestJson(path) { - throw new Error(`unexpected request ${path}`); - }, - }; - for (const repository of [ - './life-os', - '../life-os', - 'ContextualWisdomLab/.', - 'ContextualWisdomLab/..', - ]) { - await assert.rejects( - collectWorkflowRegistrySnapshot(unexpectedClient, repository, SHA), - /repository.*invalid/i, - ); - } - - for (const defaultBranch of ['.', '..', 'feature/unsafe']) { - const client = { - async requestJson(path) { - if (path === `/repos/${REPOSITORY}`) return { default_branch: defaultBranch }; - throw new Error(`unexpected request ${path}`); - }, - }; - await assert.rejects( - collectWorkflowRegistrySnapshot(client, REPOSITORY, SHA), - /default branch.*invalid/i, - ); - } -}); - -test('paginates the complete registry and binds receipts to an unchanged default-branch tree', async () => { - const calls = []; - const client = { - async requestJson(path) { - calls.push(path); - if (path === `/repos/${REPOSITORY}`) return { default_branch: 'main' }; - if (path === `/repos/${REPOSITORY}/branches/main`) { - return { commit: { sha: SHA } }; - } - if (path === `/repos/${REPOSITORY}/git/commits/${SHA}`) { - return { sha: SHA, tree: { sha: TREE_SHA } }; - } - if (path === `/repos/${REPOSITORY}/git/trees/${TREE_SHA}?recursive=1`) { - return { - truncated: false, - tree: [ - { type: 'blob', path: '.github/dependabot.yml' }, - { type: 'blob', path: '.github/workflows/ci.yml' }, - ], - }; - } - if (path.endsWith('per_page=100&page=1')) { - return { - total_count: 101, - workflows: Array.from({ length: 100 }, (_, index) => - workflow(index + 1, `.github/workflows/deleted-${index + 1}.yml`), - ), - }; - } - if (path.endsWith('per_page=100&page=2')) { - return { total_count: 101, workflows: [workflow(101, '.github/workflows/ci.yml')] }; - } - throw new Error(`unexpected ${path}`); - }, - }; - - const result = await collectWorkflowRegistrySnapshot(client, REPOSITORY, SHA, { - generatedAt: GENERATED_AT, - }); - - assert.equal(result.commit_sha, SHA); - assert.equal(result.tree_sha, TREE_SHA); - assert.equal(result.generated_at, GENERATED_AT); - assert.deepEqual(result.registry_receipt, { pages: 2, total_count: 101 }); - assert.equal(result.workflow_count, 101); - assert.equal(result.active_orphans.length, 100); - assert.deepEqual(result.present.map((entry) => entry.id), [101]); - assert.equal(calls.filter((path) => path.includes('/actions/workflows?')).length, 2); - assert.equal(calls.at(-1), `/repos/${REPOSITORY}/branches/main`); -}); - -test('fails closed on incomplete or inconsistent workflow pagination', async () => { - const cases = [ - { - name: 'pagination truncation', - error: /pagination.*truncated/i, - pages: [ - { - total_count: 101, - workflows: Array.from({ length: 99 }, (_, index) => - workflow(index + 1, `.github/workflows/${index + 1}.yml`), - ), - }, - ], - }, - { - name: 'pagination inconsistency', - error: /pagination.*inconsistent/i, - pages: [ - { - total_count: 1, - workflows: [ - workflow(1, '.github/workflows/a.yml'), - workflow(2, '.github/workflows/b.yml'), - ], - }, - ], - }, - { - name: 'changing total_count', - error: /changed during pagination/i, - pages: [ - { - total_count: 101, - workflows: Array.from({ length: 100 }, (_, index) => - workflow(index + 1, `.github/workflows/${index + 1}.yml`), - ), - }, - { total_count: 102, workflows: [workflow(101, '.github/workflows/101.yml')] }, - ], - }, - { - name: 'malformed response', - error: /response.*invalid/i, - pages: [{ total_count: '1', workflows: [] }], - }, - ]; - - for (const scenario of cases) { - let page = 0; - const client = inventoryClient({ - [`/repos/${REPOSITORY}/actions/workflows?per_page=100&page=1`]: () => - scenario.pages[page++], - [`/repos/${REPOSITORY}/actions/workflows?per_page=100&page=2`]: () => - scenario.pages[page++], - }); - await assert.rejects( - collectWorkflowRegistrySnapshot(client, REPOSITORY, SHA), - scenario.error, - scenario.name, - ); - } - - const pageLimitClient = { - async requestJson(path) { - if (path === `/repos/${REPOSITORY}`) return { default_branch: 'main' }; - if (path === `/repos/${REPOSITORY}/branches/main`) return { commit: { sha: SHA } }; - if (path === `/repos/${REPOSITORY}/git/commits/${SHA}`) { - return { sha: SHA, tree: { sha: TREE_SHA } }; - } - if (path === `/repos/${REPOSITORY}/git/trees/${TREE_SHA}?recursive=1`) { - return { truncated: false, tree: [] }; - } - if (path.includes('/actions/workflows?')) { - return { - total_count: 1001, - workflows: Array.from({ length: 100 }, (_, index) => - workflow(index + 1, `.github/workflows/page-${path.at(-1)}-${index}.yml`), - ), - }; - } - throw new Error(`unexpected ${path}`); - }, - }; - await assert.rejects( - collectWorkflowRegistrySnapshot(pageLimitClient, REPOSITORY, SHA), - /exceeded the page limit/i, - ); -}); - -test('fails closed on tree, commit, branch, timestamp, and client evidence defects', async () => { - const treeTruncatedClient = inventoryClient({ - [`/repos/${REPOSITORY}/git/trees/${TREE_SHA}?recursive=1`]: () => ({ - truncated: true, - tree: [], - }), - }); - await assert.rejects( - collectWorkflowRegistrySnapshot(treeTruncatedClient, REPOSITORY, SHA), - /tree.*truncated/i, - ); - - const mismatchedCommitClient = inventoryClient({ - [`/repos/${REPOSITORY}/git/commits/${SHA}`]: () => ({ - sha: 'e'.repeat(40), - tree: { sha: TREE_SHA }, - }), - }); - await assert.rejects( - collectWorkflowRegistrySnapshot(mismatchedCommitClient, REPOSITORY, SHA), - /commit evidence.*inconsistent/i, - ); - - const movedBeforeClient = inventoryClient({ - [`/repos/${REPOSITORY}/branches/main`]: () => ({ commit: { sha: 'e'.repeat(40) } }), - }); - await assert.rejects( - collectWorkflowRegistrySnapshot(movedBeforeClient, REPOSITORY, SHA), - /moved before/i, - ); - - let branchReads = 0; - const movedDuringClient = { - async requestJson(path) { - if (path === `/repos/${REPOSITORY}`) return { default_branch: 'main' }; - if (path === `/repos/${REPOSITORY}/branches/main`) { - branchReads += 1; - return { commit: { sha: branchReads === 1 ? SHA : 'e'.repeat(40) } }; - } - if (path === `/repos/${REPOSITORY}/git/commits/${SHA}`) { - return { sha: SHA, tree: { sha: TREE_SHA } }; - } - if (path === `/repos/${REPOSITORY}/git/trees/${TREE_SHA}?recursive=1`) { - return { truncated: false, tree: [] }; - } - if (path.endsWith('per_page=100&page=1')) return { total_count: 0, workflows: [] }; - throw new Error(`unexpected ${path}`); - }, - }; - await assert.rejects( - collectWorkflowRegistrySnapshot(movedDuringClient, REPOSITORY, SHA), - /moved during/i, - ); - - await assert.rejects( - collectWorkflowRegistrySnapshot(inventoryClient(), REPOSITORY, SHA, { - generatedAt: 'not-an-iso-timestamp', - }), - /timestamp.*invalid/i, - ); - await assert.rejects( - collectWorkflowRegistrySnapshot({}, REPOSITORY, SHA), - /client.*invalid/i, - ); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac72584e4..951d5e368 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,6 @@ settings: excludeLinksFromLockfile: false overrides: - nanoid: 3.3.18 postcss: 8.5.23 sharp: 0.35.0 @@ -2147,8 +2146,8 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -4489,7 +4488,7 @@ snapshots: mute-stream@2.0.0: {} - nanoid@3.3.18: {} + nanoid@3.3.17: {} negotiator@1.0.0: {} @@ -4694,7 +4693,7 @@ snapshots: postcss@8.5.23: dependencies: - nanoid: 3.3.18 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1