Skip to content

M1 Implementation Plan: Engine and API

Milestone goal: the core versioning engine, the Keeper REST API, the CLI, docker compose and CI. At the end of M1 a team can define a Domain in YAML, import data, branch, change, diff, merge with Conflicts resolved through the API, and read Records in milliseconds, all through REST and CLI.

Reference: design.md. Section numbers below (for example "6.8") point to it.

How to use this plan

  • Tasks are ordered. Each task lists its dependencies; tasks without a dependency between them may run in parallel.
  • Each task is sized for one implementer and one pull request (one to four days).
  • Each task has Files, Behavior, Acceptance tests and Out of scope. A task is done when its acceptance tests pass in CI and the reviewer confirms the behavior.
  • Vocabulary follows glossary.md. Do not introduce other names for Kinds, Records, Revisions and so on.

Working rules for every task

  1. Configuration is external. No hardcoded endpoints, limits, credentials or business values. New settings go into lorebase_platform.settings with a documented default and an entry in config/lorebase.example.yaml.
  2. Respect package boundaries (design 17): core has no I/O and no imports from other Lorebase packages; store depends on core and platform; inference depends on core and platform; keeper depends on store and must never import inference (the read path is model-free); client is standalone; cli depends on client. Import-linter enforces this from T01.
  3. Test only the delta locally. While developing, run only the tests that cover the modules you changed (found by name or import) plus the fast validators you touched (ruff, mypy on changed packages). Do not run the full suite by default; CI runs it on every pull request. Live or end-to-end checks: the minimum number of runs that proves the point.
  4. Add dependencies only with a reason stated in the pull request. Prefer the standard library and the fixed stack (FastAPI, Pydantic v2, SQLAlchemy 2 async, asyncpg, Alembic, Typer, OpenTelemetry).
  5. Document only what is needed: public functions get concise docstrings; behavior changes update the relevant doc section.
  6. Every pull request keeps ruff, mypy --strict (for core, store, keeper), unit and integration tests green.
  7. No network and no model calls in tests. Anything model-related uses the deterministic fake provider (T02b). Tests never need an API key; test runs disable outbound network access.

Task overview

ID Task Depends on
T01 Repository scaffolding and tooling none
T02 Platform package: settings, logging, telemetry, secrets T01
T02b Inference interfaces, settings and fake provider T02
T03 Canonical JSON and hashing T01
T04 Type system and value encoding T03
T05 Schema element models and static validation T04
T06 Canonical HAMT tree with in-memory NodeStore T03
T07 Tree diff T06
T08 Revisions, ancestry and merge base T06
T09 Data validation against schema T05
T10 Three-way merge engine T05, T07, T08, T09
T11 Postgres schema and Alembic migrations T02
T12 Store repositories and PgNodeStore T06, T08, T11
T13 Commit pipeline T09, T12
T14 Materializer and verification T13
T15 Keeper application skeleton T02
T16 Authentication, principals, Domain roles, audit log T11, T15
T17 Domains and branches API T13, T14, T16
T18 Schema API and schema change sets T17
T19 Built-in Operations and execution endpoint T18
T20 Read API: records, links, query DSL, search, time travel T14, T18
T21 Revisions, log and diff API T17
T22 Proposals, merge and Conflict resolution API T10, T19, T21
T23 Lineage and history API T19
T24 Python client T17 to T23 (incremental)
T25 CLI T24
T26 Docker images and docker compose T15, T25
T27 CI workflows T01, T26
T28 Demo Domain and performance baseline T20, T25
T29 M1 documentation and release checklist all

T01. Repository scaffolding and tooling

Files - pyproject.toml (uv workspace root with members packages/*; shared ruff, mypy, pytest configuration) - packages/{core,platform,inference,store,keeper,client,cli}/pyproject.toml, each with src/lorebase_<name>/__init__.py and tests/ - .python-version (3.12), uv.lock - .importlinter (layer contracts from design 17, plus a forbidden contract: lorebase_keeper may not import lorebase_inference) - .pre-commit-config.yaml (ruff format, ruff check, end-of-file, trailing whitespace) - Makefile with targets fmt, lint, type, test-unit, test-int, test-changed - scripts/test_changed.py: maps changed files (git diff against the merge base) to the test files that import or are named after them and runs only those

Behavior - uv sync installs all workspace packages in editable mode. - make test-changed runs only the tests impacted by the current diff.

Acceptance tests - uv sync && make lint type passes on a clean checkout. - Import-linter fails if lorebase_core imports lorebase_store, or if lorebase_keeper imports lorebase_inference (verified with temporary fixtures in the linter test). - scripts/test_changed.py unit test: given a synthetic changed-file list, selects the expected test files.

Out of scope: web app scaffolding (M2), Scribe and Herald packages (M2, M3).

T02. Platform package: settings, logging, telemetry, secrets

Files - packages/platform/src/lorebase_platform/settings.py: Pydantic Settings model; sources in order: code defaults, YAML file from LOREBASE_CONFIG, environment variables LOREBASE__SECTION__KEY - .../secrets.py: resolver for secret://env/NAME and secret://file/PATH; secret values typed as SecretStr - .../logging.py: JSON logging with trace_id, span_id, request_id, redaction filter - .../telemetry.py: OpenTelemetry setup (traces and metrics, OTLP exporter configurable, disabled when no endpoint) - config/lorebase.example.yaml

Behavior - Invalid configuration fails at startup with a message naming the key and the reason. - Secrets never appear in repr, logs or exceptions.

Acceptance tests - Precedence: env overrides YAML overrides defaults. - Missing secret reference raises a clear error; a secret value placed in a log call is redacted. - Telemetry setup with no endpoint configured produces no exporter and no network calls.

T02b. Inference interfaces, settings and fake provider

M1 makes no model calls; this task fixes the contract so that every later model-backed feature is vendor-agnostic and testable for free (design 9.4).

Files - packages/inference/src/lorebase_inference/interfaces.py: ChatModel, EmbeddingModel, SpanExtractor protocols and their result types (parsed JSON, usage, model id, spans with offsets) - .../settings.py: the inference settings section (design 16) registered with lorebase_platform.settings, including the env aliases LLM_BASE_URL, LLM_MODEL, LLM_API_KEY and per-task overrides; embedding model and dims - .../registry.py: provider selection from configuration (openai_compatible, fastembed, fake); unknown providers fail at startup; real providers are registered in M4 - .../fake.py: deterministic fake provider: chat answers from fixtures keyed by prompt hash or a schema-valid rule-based responder; embeddings as unit vectors of the configured dimension from sha256(text); spans by dictionary matching - config/lorebase.example.yaml: inference section with the local defaults (Ollama base URL, fastembed) and commented examples for a hosted OpenAI-compatible endpoint and a LiteLLM proxy

Behavior - Switching provider is a configuration change only; no code path branches on a vendor name outside registry.py. - No vendor SDK is a dependency of any package.

Acceptance tests - Fake chat output validates against the requested JSON schema; same input gives byte-identical output. - Fake embeddings: configured dimension, unit norm, deterministic, different texts give different vectors. - LLM_BASE_URL, LLM_MODEL, LLM_API_KEY override the YAML values; LLM_API_KEY is optional and treated as a secret. - Test suite of this package passes with outbound network disabled.

Out of scope: the OpenAI-compatible HTTP client, fastembed, GLiNER and GLiREL adapters, compose profiles local-models and litellm (all M4).

T03. Canonical JSON and hashing

Files - packages/core/src/lorebase_core/canonical/jcs.py: RFC 8785 serializer - .../canonical/hashing.py: object_hash(object_type, payload) -> bytes, node_hash, revision_hash, scheme prefix lb1 - packages/core/tests/canonical/vectors/*.json: golden vectors

Behavior (design 6.1) - Deterministic bytes for any JSON-compatible value: sorted keys by UTF-16 code units, RFC 8785 number formatting, NFC strings, no insignificant whitespace. - Hash = sha256("lb1\x00" + object_type + "\x00" + canonical_bytes).

Acceptance tests - RFC 8785 published test vectors pass. - Property test (hypothesis): canonical form is stable under key reordering and round-tripping through json.loads. - Golden vectors for each object_type lock the hash scheme; changing them requires a scheme version bump (test asserts prefix).

T04. Type system and value encoding

Files - packages/core/src/lorebase_core/types/ with types.py (type definitions incl. array<T>, enum, decimal(p,s)), encode.py (API value to canonical value and back), casts.py (cast rules matrix: safe, checked, forbidden)

Behavior (design 5.4 and 6.1) - Integers and decimals canonicalized as strings; decimals normalized to declared scale; timestamps to UTC with six fractional digits; dates YYYY-MM-DD; strings NFC. - cast(value, from_type, to_type) returns a value or a typed CastError.

Acceptance tests - Round-trip property tests per type. - Full matrix test: every (from, to) pair has a declared rule; safe casts never fail on any valid value drawn by the property-test strategy; forbidden casts always raise. - Int64 bounds and decimal precision edge cases.

T05. Schema element models and static validation

Files - packages/core/src/lorebase_core/model/: ids.py (prefixed ULIDs), domain.py, kind.py, attribute.py, relation.py, trait.py, operation.py (built-in only in M1; custom Operation model present but CEL steps rejected until M3), derivation.py (model only), source.py (model only) - .../model/schema.py: Schema aggregate (all elements of a Revision), api name resolution - .../validate/schema_rules.py

Behavior (design 5.1 to 5.9) - Pydantic v2 models, frozen; canonical payload excludes api-name-independent fields as specified (keys are ids). - Static validation: api name uniqueness per scope, primary key Attributes exist and are required, title Attribute exists, Relation endpoints exist, Trait mappings type-compatible, no cycles where forbidden, reserved names rejected. - YAML schema format (human-friendly, api names) with a loader that assigns ids for new elements and resolves existing ones by api name on a given base schema.

Acceptance tests - One test per validation rule (positive and negative). - Loading examples/supply/schema.yaml (fixture copy) produces a valid Schema; reloading against itself produces zero changes. - Renaming a Kind in YAML keeps its id.

T06. Canonical HAMT tree with in-memory NodeStore

Files - packages/core/src/lorebase_core/tree/node.py (binary encoding of leaf and branch nodes), hamt.py (get, batch put/delete, iterate, counts), store.py (NodeStore protocol, MemoryNodeStore), root.py (partition map)

Behavior (design 6.2) - 256-way fanout on sha256(element_key) bytes; node is a leaf iff its subtree holds at most L entries; L configurable per tree (default 512). - Batch updates apply path copying and return the new root hash plus the list of new nodes. - Subtree counts in node headers.

Acceptance tests - Canonicity property test: for random sets and random insertion and deletion orders, the final root hash depends only on the final set. - Removing all entries yields the empty-tree hash. - With L = 4, splits and merges at boundaries behave as specified (unit tests with crafted keys). - 1M entries in-memory: batch insert completes and lookup is correct for sampled keys (marked slow, run in CI nightly).

T07. Tree diff

Files - packages/core/src/lorebase_core/tree/diff.py

Behavior (design 6.7) - diff(root_a, root_b) -> AsyncIterator[Change(partition, key, hash_a, hash_b)], skipping equal subtrees, streaming.

Acceptance tests - Property: applying diff(A, B) to A yields B. - Property: diff(A, A) is empty and touches only the root node (instrumented NodeStore counts reads). - Read count for a single changed key in a 100k tree is at most depth plus one per side.

T08. Revisions, ancestry and merge base

Files - packages/core/src/lorebase_core/revision/revision.py (Revision model and hash), ancestry.py (RevisionGraph protocol, generation numbers, lowest common ancestors), changeset.py

Behavior (design 6.3, 6.4, 6.8 step 0) - LCA search pruned by generation number; returns all lowest common ancestors. - Virtual base construction for multiple LCAs delegates to the merge engine (T10) through an interface.

Acceptance tests - Linear, diverged, fast-forward, criss-cross histories (fixtures) produce the expected bases. - Revision hash stable for identical content; changes with any field.

T09. Data validation against schema

Files - packages/core/src/lorebase_core/validate/data_rules.py, violations.py

Behavior (invariant I1) - Validate a set of Record and Link changes against a Schema plus a Lookup protocol (existing values for uniqueness, Link counts for cardinality, existence for referential integrity). - Rules: types, required, constraints, enum, uniqueness, cardinality, required_from, referential integrity with on_delete behavior, Trait conformance. - Violations are structured (code, kind, key, attribute, message, details).

Acceptance tests - One test per rule with an in-memory Lookup. - on_delete = cascade yields Link deletions as derived changes; restrict yields a violation.

T10. Three-way merge engine

Files - packages/core/src/lorebase_core/merge/schema_merge.py, data_merge.py, consistency.py, conflicts.py (taxonomy codes from design 6.9, fingerprints), policies.py (prefer, authority, newest_wins, cascade_dangling), engine.py (orchestration, resolution application)

Behavior (design 6.8 to 6.10) - Inputs: base, ours, theirs roots plus NodeStore and object loader; outputs: merged change set and Conflicts, or a merged root when clean. - Schema merged first; data merged under the merged schema; consistency checks produce XS-class Conflicts. - apply_resolutions(conflicts, resolutions) re-validates and may yield new Conflicts. - Resolution matching by fingerprint.

Acceptance tests - A fixture per Conflict code (DV1 to DV3, SC1 to SC3, XS1 to XS11) producing exactly that Conflict. - Laws (property tests): merge(base, X, base) = X; merge(base, X, X) = X; for non-overlapping changes merge is commutative in content (root hash equal regardless of side order). - Rename on one side plus Attribute edit on the other merges cleanly. - Stale attempt: resolutions keyed by fingerprint are re-applied after recomputation against a moved target.

T11. Postgres schema and Alembic migrations

Files - packages/store/src/lorebase_store/models/*.py (SQLAlchemy 2 typed models for design 7.1 to 7.4; M1 subset listed below) - packages/store/migrations/ (Alembic env with async engine, first revision) - packages/store/src/lorebase_store/rls.sql applied by migration

M1 tables: domains, objects, tree_nodes, revisions, revision_parents, changesets, provenance, refs, ref_log, mat_schema, mat_records, mat_links, mat_history, proposals, proposal_reviews, merge_attempts, conflicts, conflict_resolutions, principals, api_keys, grants, operation_invocations, audit_log, jobs (used by merge jobs). Extensions: pg_trgm; vector created if available (not required in M1).

Behavior - Hash partitioning on domain_id for objects, tree_nodes, mat_records, mat_links with partition count from configuration at install time. - RLS policies on domain_id keyed to current_setting('lorebase.domain_ids').

Acceptance tests - alembic upgrade head then downgrade base then upgrade head on an empty Postgres 16 (testcontainers or CI service). - RLS test: a session scoped to Domain a cannot read Domain b rows.

T12. Store repositories and PgNodeStore

Files - packages/store/src/lorebase_store/repos/objects.py, nodes.py (PgNodeStore implementing core NodeStore, with LRU cache), revisions.py (also implements core RevisionGraph), refs.py (CAS update, advisory lock helper), provenance.py - packages/store/src/lorebase_store/db.py (async engine, session factory, transaction helpers that set RLS settings)

Behavior - Bulk inserts with ON CONFLICT DO NOTHING; COPY path for large batches. - refs.compare_and_swap(domain, name, expected_head, expected_version, new_head) raises RefMoved on mismatch. - Advisory lock key derived from (domain_id, branch) with a stable 64-bit hash.

Acceptance tests (integration, Postgres) - Core tree property tests re-run against PgNodeStore (shared test suite parametrized by store). - Concurrent CAS: two transactions racing on the same ref, exactly one succeeds.

T13. Commit pipeline

Files - packages/store/src/lorebase_store/commit/pipeline.py, changes.py (change set model: record and link upserts and deletes, schema changes, provenance per change)

Behavior (design 6.6) - Single transaction: lock, read head, validate (T09 with a Lookup backed by the materialized head), hash and insert objects, update tree, insert Revision and changeset and provenance, call materializer (T14), CAS ref, write audit entry. - Schema changes compute data migrations (casts, defaults) and include them in the same Revision; cast failures abort with violations. - Batch API for large imports (configurable changes per Revision).

Acceptance tests - Commit visible atomically; a failure injected at each step leaves no visible change (parametrized fault injection). - Two concurrent commits to the same branch serialize; both succeed in sequence with correct parents. - Schema change string -> integer with all castable values succeeds; with one bad value fails with an XS3-style violation listing the Record.

T14. Materializer and verification

Files - packages/store/src/lorebase_store/materialize/apply.py (incremental changeset application), rebuild.py (full rebuild from tree), branch.py (copy materialization on branch creation), indexes.py (partial expression indexes and unique indexes per Attribute flags), search.py (tsvector computation per Kind search config), verify.py

Behavior (design 6.11) - Applies changesets to mat_records, mat_links, mat_history, mat_schema in the commit transaction. - Index management for indexed and unique Attributes runs when schema changes commit (unique index creation validates first). - verify(branch) compares each row's object_hash with the tree and partition counts.

Acceptance tests - After any sequence of commits in the integration suite, verify passes (pytest fixture runs it at teardown for every integration test). - Rebuild from tree equals incrementally maintained state (row-by-row comparison). - Branch creation with full materialization copies rows; 100k-row copy under 5 s on CI hardware (soft assertion, reported).

T15. Keeper application skeleton

Files - packages/keeper/src/lorebase_keeper/app.py (FastAPI factory), errors.py (RFC 9457 problem details), middleware.py (request id, timing, OpenTelemetry), health.py, deps.py (DB session, settings), __main__.py (uvicorn entrypoint, host and port from settings)

Behavior - /healthz, /readyz (database reachable and Alembic head matches), /api/v1/openapi.json, /api/v1/docs. - All errors returned as problem details with stable type URIs.

Acceptance tests - Readiness fails when the database is unreachable or migrations are behind. - Unhandled exceptions return a problem detail without stack traces; trace id present in logs.

T16. Authentication, principals, Domain roles, audit log

Files - packages/keeper/src/lorebase_keeper/auth/api_keys.py (format lbk_{prefix}_{secret}, HMAC-SHA256 with pepper from settings), principals.py, rbac.py (M1 roles: viewer, editor, modeler, reviewer, maintainer, admin at Domain scope; grant model supports the other scopes for later), audit.py (hash-chained append) - packages/keeper/src/lorebase_keeper/routes/admin.py (principals, API keys, grants, audit read) - lorebase admin bootstrap support endpoint guarded by a one-time bootstrap token from settings, or direct DB bootstrap from CLI (T25)

Behavior (design 13.2, 10.2, 10.4) - Every request resolves a principal or is rejected with 401; permission checks return 403 with the missing permission name. - Audit entries for every mutation and every denied request.

Acceptance tests - Authorization matrix test: role by endpoint group, expected allow or deny. - API key shown once; stored value is not the secret; revoked and expired keys rejected. - Audit chain verification detects a modified row.

T17. Domains and branches API

Files - packages/keeper/src/lorebase_keeper/routes/domains.py, branches.py; services/branches.py

Behavior - Create Domain (initial empty Revision on main, main protected by default per settings). - List, get, create (from branch, tag or Revision; full materialization), delete branches (not the default branch); protection flags. - Tags: create and list.

Acceptance tests - Create Domain then branch then delete branch; materialized rows for the deleted branch removed. - Protected branch rejects built-in Operations from editor when policy disallows direct commits.

T18. Schema API and schema change sets

Files - packages/keeper/src/lorebase_keeper/routes/schema.py, services/schema.py; change set commands model in lorebase_core.model.changes

Behavior - GET /schema returns the compiled schema of a branch head (api names, ids, descriptions). - POST /branches/{b}/schema/changes accepts commands (add_kind, update_kind, remove_kind, add_attribute, update_attribute, remove_attribute, add_relation, update_relation, remove_relation, add_trait, update_trait, remove_trait, implement_trait) or a full YAML document (server computes commands); dry_run returns the plan including data migrations and violations. - Requires modeler on the branch; protected branches reject schema changes outside merges.

Acceptance tests - Applying examples/supply/schema.yaml to an empty branch creates the expected elements. - Dry run of a type change reports the count and sample of failing values without committing. - Removing a Kind with Records requires cascade: true in the command; otherwise violation.

T19. Built-in Operations and execution endpoint

Files - packages/keeper/src/lorebase_keeper/routes/operations.py, services/operations.py; lorebase_core.model.operation built-in definitions

Behavior (design 5.7, 10.1) - Built-in create_{kind}, update_{kind}, delete_{kind}, link_{relation}, unlink_{relation}, and import_records (bulk, batched commits, editor or maintainer). - dry_run, idempotency_key (unique per principal; repeated key returns the original result), invocation record, provenance operation or import per changed Attribute, audit entry. - Response: status, edits, violations, revision.

Acceptance tests - Each built-in Operation happy path and violation path. - Idempotency: same key and same parameters returns the same Revision; same key with different parameters returns 409. - Import of 100k Records in batches produces the expected number of Revisions and passes verify.

Files - packages/core/src/lorebase_core/query/dsl.py (DSL model and validation against Schema) - packages/store/src/lorebase_store/query/compiler.py (DSL to SQL on materialized tables), search.py - packages/keeper/src/lorebase_keeper/routes/records.py, query.py, search.py

Behavior (design 8) - Get Record by key, list with where, order_by, keyset cursor; links by Relation (forward and inverse); POST /query with via up to configured depth; POST /traverse; POST /search lexical mode (tsvector plus trigram on titles); ?at= reads by key and list by Kind partition from the tree. - Every response includes revision; Lorebase-Revision header; ETag on Record reads. - Statement timeout and limits from settings.

Acceptance tests - Compiler golden tests: DSL input to expected SQL shape and parameters (no string interpolation of values). - Results match a brute-force evaluator over in-memory data on randomized datasets (property test, integration). - at= returns historical values after later updates. - Performance assertions in T28, not here.

T21. Revisions, log and diff API

Files - packages/keeper/src/lorebase_keeper/routes/revisions.py, services/diff.py

Behavior - GET /branches/{b}/log (first-parent by default, all=true for full graph), GET /revisions/{id}, GET /diff?from=&to=&kinds= with attribute-level expansion and per-Kind summary; paginated.

Acceptance tests - Diff of two Revisions equals the union of changesets for linear history. - Diff summary counts match materialized counts difference.

T22. Proposals, merge and Conflict resolution API

Files - packages/keeper/src/lorebase_keeper/routes/proposals.py, services/merge.py; packages/store/src/lorebase_store/repos/proposals.py

Behavior (design 6.8 to 6.10, 10.3) - Create Proposal (source branch, target branch), compute merge preview (Conflict count), reviews (approve, request changes), merge (fast-forward, clean merge, or merge attempt with Conflicts), list Conflicts, resolve single and bulk, apply policies, recompute stale attempts, close. - Branch protection: required approvals from settings; self-approval disabled by default. - Large merges (over a configured change count) run as a job in the jobs table, executed in-process by a Keeper background worker in M1 (Scribe takes this over in M2).

Acceptance tests - End-to-end per Conflict code through the API: produce, list, resolve, merge, verify. - Target moves during resolution: attempt recomputed; matching resolutions re-applied. - Merge without required approvals rejected on a protected branch.

T23. Lineage and history API

Files - packages/keeper/src/lorebase_keeper/routes/lineage.py; store queries over mat_records.lineage, provenance, mat_history

Behavior (design 5.11) - GET .../records/{key}/lineage per Attribute: origin type, origin reference, actor, time, Revision. - GET .../records/{key}/history per branch with changed Attributes per entry. - Merged changes keep their original provenance; resolutions carry merge_resolution.

Acceptance tests - Update on branch, merge into main: Lineage on main shows the original Operation invocation, not the merge. - Conflict resolved with a custom value: Lineage shows merge_resolution with Proposal and Conflict ids.

T24. Python client

Files - packages/client/src/lorebase_client/ (client.py sync and async, typed models, error mapping, pagination helpers, retries with backoff for idempotent calls)

Behavior - Covers every M1 endpoint; base URL and API key from arguments or environment (LOREBASE_URL, LOREBASE_API_KEY).

Acceptance tests - Contract test: client models validated against the live OpenAPI document of Keeper in the integration suite. - Error mapping from problem details to typed exceptions.

T25. CLI

Files - packages/cli/src/lorebase_cli/ (Typer app: domain, branch, tag, schema apply|show|plan, records import|get|list, query, search, op run, log, diff, proposal open|show|review|merge|close, conflicts list|resolve, admin bootstrap|keys|grants|verify|audit-verify)

Behavior - Human-readable tables by default; --json for machine output on every command. - records import accepts JSON Lines and CSV (CSV columns mapped by api name; no Source mapping in M1). - admin bootstrap connects to the database (DSN from settings) to create the first admin principal and key when no admin exists. - Exit codes: 0 success, 1 usage error, 2 validation or Conflict, 3 auth, 4 server error.

Acceptance tests - CLI integration scenario: bootstrap, create Domain, apply schema, import, branch, op run, diff, proposal open, merge with one Conflict resolved, verify. - --json output validated against JSON Schemas in packages/cli/tests/schemas/.

T26. Docker images and docker compose

Files - deploy/docker/Dockerfile (multi-stage, uv, non-root user, entrypoints keeper, migrate, cli) - deploy/compose/docker-compose.yaml (postgres on a pgvector Postgres 16 image, migrate, keeper, cli profile), deploy/compose/.env.example, deploy/compose/lorebase.yaml

Behavior - docker compose up -d from clean runs migrations, then Keeper; health checks gate dependencies. - No secrets in images; all configuration through .env and mounted YAML.

Acceptance tests - CI job: build image, docker compose up -d, wait for /readyz, run the CLI scenario from T25 against it, tear down. Under 2 minutes after images are built. - Image runs as non-root; image scan has no critical vulnerabilities (report).

T27. CI workflows

Files - .github/workflows/ci.yaml: on pull request and push to main: lint (ruff), types (mypy), import-linter, unit tests, integration tests with a Postgres 16 service container, coverage report; no API keys or secrets for any model provider are configured in CI - .github/workflows/images.yaml: build images on main and tags; push to GHCR on tags only; SBOM - .github/workflows/nightly.yaml: slow tests and performance baseline (T28) with results uploaded as artifacts - .github/dependabot.yml, .github/pull_request_template.md, issue templates

Behavior - CI runs the complete suite on pull requests; local development runs only the delta (working rule 3). - Tests run with the fake inference provider and outbound network disabled (working rule 7). - Caches uv and Docker layers.

Acceptance tests - A pull request with a failing unit test, a type error, or a layer violation fails the corresponding job.

T28. Demo Domain and performance baseline

Files - examples/supply/schema.yaml, examples/supply/data/*.jsonl (small set committed), tests/perf/seed.py (synthetic data to 1M Records and 3M Links, deterministic seed), tests/perf/bench.py (latency measurement against Keeper, p50, p95, p99)

Behavior - Seeds the supply Domain at configurable scale; measures key lookup, filtered list on indexed Attributes, one-hop traversal, lexical search, Operation commit, diff of 10k changes, clean merge of 10k changes.

Acceptance tests - At 1M Records on reference hardware: key lookup p95 under 5 ms, filtered list p95 under 25 ms, one-hop traversal p95 under 10 ms, Operation commit p95 under 75 ms (server-side timings from traces). Results published in docs/perf/m1.md.

T29. M1 documentation and release checklist

Files - docs/getting-started.md (compose quickstart that works), docs/cli.md (from Typer help), docs/api.md (pointer to OpenAPI and conventions), CHANGELOG.md - README quickstart updated to remove the "coming in M1" note once T26 passes

Acceptance tests - A new contributor follows docs/getting-started.md on a clean machine and reaches a merged Proposal in under 15 minutes (recorded dry run).


M1 exit criteria

  • All tasks done; CI green on main.
  • The M1 acceptance in design 18 is met, including the 1M-Record performance baseline.
  • admin verify passes on every integration test and on the performance dataset after the merge benchmark.
  • Tag v0.1.0 with images published to GHCR.