Rename the project spec and update every reference to it

The charter file carried a tool-specific name while being the repository's own
document: mission, non-negotiable constraints, the pinned stack, repo
conventions and phase status, cited as authority by thirty files across the
agent, api, deploy, docs, search and terraform trees.

PROJECT-SPEC.md says what it is. All 42 references are updated in the same
commit, including the relative link in docs/status.md, so nothing points at a
filename that no longer exists.
This commit is contained in:
2026-08-28 15:57:12 -07:00
parent 25d5d9ce2e
commit f756a9d4f6
31 changed files with 41 additions and 41 deletions
View File
+1 -1
View File
@@ -211,7 +211,7 @@ escalation, not a footnote: think about whether your environment wants
the log-shipping agent running with that level of access before turning
on the `etw` feature and an `[source] kind = "etw"` config. Event Log
alone (no elevated privileges needed) covers the common case and is what
Phase 1's exit criteria in `/CLAUDE.md` actually requires to be running.
Phase 1's exit criteria in `/PROJECT-SPEC.md` actually requires to be running.
Providers are configured by **GUID**, not friendly name — ETW's own API
requires it. Look one up with `logman query providers "<Friendly Name>"`.
+2 -2
View File
@@ -77,7 +77,7 @@ pub enum SourceConfig {
// Dead-code-on-default-build, same reasoning as EventLog/Etw below:
// these fields are only read by the `file-tail`-gated arm in
// spawn_source (main.rs), which doesn't exist in the default build
// (`default = ["journald"]`). Pre-existing gap from Phase 0 — CLAUDE.md
// (`default = ["journald"]`). Pre-existing gap from Phase 0 — PROJECT-SPEC.md
// mandates plain `cargo clippy --all-targets -- -D warnings` (no
// --all-features), which this broke silently since only
// --all-features clippy was ever actually run.
@@ -202,7 +202,7 @@ impl Default for MetricsConfig {
/// way. Kept as a small hand-rolled parser rather than pulling in a
/// duration-parsing crate for this one field -- this is the
/// statically-linked edge agent every "no glibc runtime deps" constraint
/// in CLAUDE.md is about keeping lean, and the grammar needed here is a
/// in PROJECT-SPEC.md is about keeping lean, and the grammar needed here is a
/// handful of lines.
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
+1 -1
View File
@@ -9,7 +9,7 @@ and `/docs/query-language-reference.md` for the user-facing syntax.
## Why plain REST, not gRPC + REST gateway
CLAUDE.md pins the control plane to "Go, gRPC + REST gateway." This
PROJECT-SPEC.md pins the control plane to "Go, gRPC + REST gateway." This
service is plain `net/http` instead — a deliberate simplification, not a
change to the pinned stack. Wiring up a `.proto` service,
`google.api.http` annotations, and `protoc-gen-grpc-gateway` codegen for
+1 -1
View File
@@ -2,7 +2,7 @@
// login: a real login page and session-based auth covering both /api
// and /alerting, plus a simple admin-managed user list, for deployments
// reachable over the internet that can no longer rely on Phase 0-3's
// "no auth yet" default (see /docs/architecture.md and CLAUDE.md's
// "no auth yet" default (see /docs/architecture.md and PROJECT-SPEC.md's
// Phase 4 section for the enterprise/ SSO alternative this is not --
// this package has no tenant/RBAC-service concept, just "is this a
// valid logged-in user").
+2 -2
View File
@@ -1,7 +1,7 @@
# deploy
Kubernetes deployment for Cairn OBS, added in Phase 4 (`/deploy` was
deliberately stubbed through Phase 3 -- see `/CLAUDE.md`'s Phase 3
deliberately stubbed through Phase 3 -- see `/PROJECT-SPEC.md`'s Phase 3
non-goals). Two pieces:
- `operator/` -- a small Go controller-runtime Operator managing one CRD
@@ -18,7 +18,7 @@ map of per-tenant ClickHouse connection pools via `internal/chrunner`;
`search` holds a map of per-tenant Tantivy indices via
`src/registry.rs`) -- not at the Kubernetes layer. This directory is
**not** "one Deployment per tenant" or a general multi-cluster system;
that's an explicit Phase 4 non-goal (see `/CLAUDE.md`). What it *does*
that's an explicit Phase 4 non-goal (see `/PROJECT-SPEC.md`). What it *does*
add:
- A `Tenant` CRD + controller (`operator/internal/controller`) that
+1 -1
View File
@@ -99,7 +99,7 @@ unconditional by design, no disable switch.
both tenants reached `status.phase: Active` with real credentials.
This proves Phase 4's "two tenants... with their own users, roles,
dashboards" exit criteria (`/CLAUDE.md`) end to end at the deployment-
dashboards" exit criteria (`/PROJECT-SPEC.md`) end to end at the deployment-
topology layer: `-provision-tenant` (`enterprise/internal/
tenantprovision`) is what actually creates each tenant's ClickHouse
database/user/grant and marks it active in `rbacstore`; running inside
+1 -1
View File
@@ -94,7 +94,7 @@ search:
# A second replica would form a second, independent consumer instance
# against the same partitions with no coordination -- correctness,
# not just resource waste, is the reason this isn't a `replicas` knob
# yet. Matches CLAUDE.md's Phase 4 non-goal: "no general multi-cluster
# yet. Matches PROJECT-SPEC.md's Phase 4 non-goal: "no general multi-cluster
# orchestration."
replicas: 1
resources: {}
+1 -1
View File
@@ -18,7 +18,7 @@ import (
// internal/controller/tenant_controller.go's doc comment for how
// TenantReconciler derives Phase/Conditions from those fields rather
// than fabricating its own "provisioned" claim. This is the "lightweight
// unification" named in CLAUDE.md/docs/phase-4-runbook.md's "two
// unification" named in PROJECT-SPEC.md/docs/phase-4-runbook.md's "two
// independent provisioning mechanisms" gap: -provision-tenant stays the
// real actor; this operator's reconcile loop never touches Postgres or
// ClickHouse and gained no new credentials.
+5 -5
View File
@@ -65,7 +65,7 @@ one instead of deferring it, and keeps Kafka credentials off the edge agent.
family isn't a fit for this (no real row-level locking/transactional
read-modify-write).
This split is not to be changed without discussion — see CLAUDE.md.
This split is not to be changed without discussion — see PROJECT-SPEC.md.
## Component responsibilities
@@ -213,7 +213,7 @@ both the Helm and docker-compose layers, the two provisioning
mechanisms unified, both storage engines' write paths per-tenant-routed,
and the tenant-picker frontend page now built and browser-verified
(`web/src/routes/select-tenant`, `api/httpserver.WithCredentialedCORS`
— see `/CLAUDE.md`'s Phase 4 section and `/web/README.md`'s "Tenant
— see `/PROJECT-SPEC.md`'s Phase 4 section and `/web/README.md`'s "Tenant
picker" section), the remaining gaps in this phase are entirely the
already-disclosed live-verification caveats: the ClickHouse/Postgres-
backed pieces have never run against a real database in this
@@ -244,18 +244,18 @@ plain HTTP instead (`api/authz.HTTPAuthorizer`, `web`'s
`GET /auth/features`) — the same "network boundary, not import boundary"
shape `/alerting``api` already used before `enterprise/` existed.
## Non-negotiables carried from CLAUDE.md
## Non-negotiables carried from PROJECT-SPEC.md
- Rust agent: statically linked musl, `x86_64-unknown-linux-musl` and
`aarch64-unknown-linux-musl`, no glibc runtime deps.
- Windows support via native ETW/Event Log API, not WSL — designed
(Phase 1) but still unverified on real Windows hardware.
- Every UI action maps to a documented REST/gRPC call — no UI-only logic.
- Pinned stack (see CLAUDE.md table) — no substitutions without discussion.
- Pinned stack (see PROJECT-SPEC.md table) — no substitutions without discussion.
## Explicitly out of scope (current, Phase 4)
Per `/CLAUDE.md`'s Phase 4 non-goals and `/docs/security/threat-model.md`:
Per `/PROJECT-SPEC.md`'s Phase 4 non-goals and `/docs/security/threat-model.md`:
deny-override permission grants, a data retention/deletion policy for
deprovisioned tenants, general multi-cluster orchestration in `/deploy`,
and any defense against a privileged ClickHouse/Postgres administrator —
+5 -5
View File
@@ -221,7 +221,7 @@ Key facts, verified against primary sources:
already speak. Real tradeoff: Redpanda was originally chosen partly
for its lightweight single-binary footprint (`docker-compose.yml`
runs it with `--smp=1 --memory=1G --overprovisioned`, tuned for a
resource-constrained local/homelab deployment per `CLAUDE.md`'s
resource-constrained local/homelab deployment per `PROJECT-SPEC.md`'s
stated deployment targets); Kafka's JVM-based broker has a materially
larger minimum memory/startup footprint. This is a real regression
for the project's stated "docker-compose for local/homelab" use case,
@@ -263,7 +263,7 @@ project's own logo package.
**Before this audit**: no root `LICENSE` file existed anywhere in the
repo — not at the root, not in `enterprise/`. The only license
declarations were prose statements in `CLAUDE.md`/`docs/architecture.md`
declarations were prose statements in `PROJECT-SPEC.md`/`docs/architecture.md`
and correct `license = "AGPL-3.0-only"` fields in the two Rust
workspaces' `Cargo.toml`s. `web/package.json` had no `license` field at
all (npm's tooling reported the package itself as `UNLICENSED` as a
@@ -324,10 +324,10 @@ assumed.
- Every prose reference to `enterprise/` as "commercial license" or
"commercial-licensed" across the repo was updated. Present-tense
claims (code comments, `README.md` files describing current state,
`CLAUDE.md`'s non-negotiable constraints) were corrected outright.
`PROJECT-SPEC.md`'s non-negotiable constraints) were corrected outright.
Historical, phase-specific documents (`docs/phase-4-isolation-design.md`,
`docs/phase-4-rbac-design.md`, `docs/phase-4-runbook.md`, and the
relevant parts of `CLAUDE.md`'s and `docs/architecture.md`'s Phase 4
relevant parts of `PROJECT-SPEC.md`'s and `docs/architecture.md`'s Phase 4
sections) were given forward-pointing corrections — "commercial
license at the time this was written; AGPLv3 as of Phase 6" — rather
than rewritten as if the commercial-license period never happened,
@@ -400,7 +400,7 @@ Redpanda decision below, every item has one.
**Every task-2 (c)/unresolved-(b) item now has an explicit resolution**
— fixed, isolated, or, for Redpanda, flagged and decided. Phase 6's exit
criteria in `CLAUDE.md` are updated accordingly.
criteria in `PROJECT-SPEC.md` are updated accordingly.
## Legal disclaimer (repeated, deliberately)
+1 -1
View File
@@ -72,7 +72,7 @@ curl -X POST http://localhost:8080/search -H 'Content-Type: application/json' \
```
**The `record_id` in both responses should match.** That's the actual
Phase 1 exit criterion (`/CLAUDE.md`) for the Linux half: the same
Phase 1 exit criterion (`/PROJECT-SPEC.md`) for the Linux half: the same
record, reachable both ways. If `/search` returns nothing yet, give it a
few more seconds — Tantivy commits on a timer (`COMMIT_INTERVAL_MS`,
default 2s), so there's a small window where a record is in ClickHouse
+1 -1
View File
@@ -97,7 +97,7 @@ not a projection):
| `SELECT count() FROM logs WHERE service='web'` (raw SQL) | Pure ClickHouse | 17.5ms |
All four "well under a second" — the Phase 2 exit criteria in
`/CLAUDE.md`. The combined text+aggregation case (the one everyone should
`/PROJECT-SPEC.md`. The combined text+aggregation case (the one everyone should
be nervous about, since it's a two-backend query) came in at 46ms, not
meaningfully slower than the pure-ClickHouse case — the Tantivy prefilter
step is fast, and 5,000 UUIDs (see below) is a small `IN` clause by
+2 -2
View File
@@ -1,6 +1,6 @@
# Dashboard design
> **Status:** Approved, in progress. Task 2 of Phase 3 — see `/CLAUDE.md`'s
> **Status:** Approved, in progress. Task 2 of Phase 3 — see `/PROJECT-SPEC.md`'s
> "What done looks like for Phase 3" section for the exit criteria this is
> built against. Task 3 (dashboard CRUD API + web UI) implements this
> doc; if implementation reveals this design is wrong somewhere, fix this
@@ -24,7 +24,7 @@ saved query itself.
## Why not ClickHouse: PostgreSQL for control-plane config
This phase adds PostgreSQL as a new pinned-stack component — flagged and
confirmed with the project owner before implementation, per CLAUDE.md's
confirmed with the project owner before implementation, per PROJECT-SPEC.md's
"ask before... making an architectural decision not already specified in
`/docs/architecture.md`." Scope is strictly control-plane config
(dashboards, panels, and — per `/docs/phase-3-alerting-design.md`
+1 -1
View File
@@ -37,7 +37,7 @@ ships entirely in `enterprise/`, not AGPL core. Core (`/api`,
`/alerting`, `/web`) stays genuinely single-tenant: no multi-tenant
mechanism present at all, not merely a missing management UI on top of
otherwise-functional isolation. This was an explicit choice put to the
project owner rather than assumed, because CLAUDE.md's licensing
project owner rather than assumed, because PROJECT-SPEC.md's licensing
boundary text names multi-tenancy as enterprise-gated, and a
"mechanism in core, feature in enterprise" split would have let a
sufficiently motivated self-hosting AGPL user wire up real isolation
+1 -1
View File
@@ -61,7 +61,7 @@ Design choices worth calling out explicitly:
| Hardware floor | Runs on CPU (slow) or a single consumer GPU via quantized (GGUF) models | Needs a real GPU; not practically CPU-viable |
| Deployment complexity | Single binary/container, `ollama pull <model>`, built-in REST API | Python server, CUDA/driver management, more moving parts |
| Throughput under concurrency | Adequate for one-user-at-a-time interactive use; not built for high concurrent QPS | Purpose-built for high-throughput serving (continuous batching, PagedAttention) |
| Fit for this project | Matches `docker-compose for local/homelab` (CLAUDE.md's stated deployment target) — most self-hosters won't have a dedicated inference GPU | Fits a provisioned-GPU SaaS inference tier — not this phase's target (cloud is the opt-in secondary path, not primary) |
| Fit for this project | Matches `docker-compose for local/homelab` (PROJECT-SPEC.md's stated deployment target) — most self-hosters won't have a dedicated inference GPU | Fits a provisioned-GPU SaaS inference tier — not this phase's target (cloud is the opt-in secondary path, not primary) |
Cairn OBS's actual AI workload shape is one interactive query bar per user
at a time, not a high-QPS inference-serving problem — vLLM's real
+1 -1
View File
@@ -495,7 +495,7 @@ handler to call them from).
## Known residual risks (explicitly out of scope, not silently assumed away)
Per `/CLAUDE.md`'s Phase 4 non-goals, restated here in threat-model
Per `/PROJECT-SPEC.md`'s Phase 4 non-goals, restated here in threat-model
terms:
- **A privileged ClickHouse/Postgres administrator is not defended
+1 -1
View File
@@ -2,7 +2,7 @@
Phase-by-phase record of what was built, what "done" meant for each phase,
and how it was verified. Conventions and constraints live in
[`/CLAUDE.md`](../CLAUDE.md); the architecture spec is
[`/PROJECT-SPEC.md`](../PROJECT-SPEC.md); the architecture spec is
[`/docs/architecture.md`](architecture.md).
Each phase has a runbook in this directory recording the actual
+1 -1
View File
@@ -206,7 +206,7 @@ below) and Tantivy (`/search/README.md`'s "Per-tenant indices" section
routing (does traffic actually reach `enterprise-api` instead of
`api`), now a single-flag choice in both `deploy/helm/cairnobs` and
`docker-compose.yml` (`enterprise.enabled` / `COMPOSE_PROFILES`), see
CLAUDE.md.
PROJECT-SPEC.md.
## Ingest tenant identity
+1 -1
View File
@@ -20,7 +20,7 @@
// Both Helm (deploy/helm/cairnobs/templates/api.yaml vs
// enterprise-api.yaml) and docker-compose.yml (COMPOSE_PROFILES) now
// make this the deployment-topology choice, not just a binary sitting
// unused alongside api's -- see this repo's CLAUDE.md. `search`'s write
// unused alongside api's -- see this repo's PROJECT-SPEC.md. `search`'s write
// side (ingest, and by extension the Redpanda consumer search itself
// runs) is still not tenant-aware -- see enterprise/internal/searchclient
// and search/src/registry.rs's doc comments, and
+1 -1
View File
@@ -1,6 +1,6 @@
// Package tenantcrd syncs enterprise-api -provision-tenant's real
// provisioning result into the Tenant CRD (deploy/operator/api/
// v1alpha1) -- the "lightweight unification" named in CLAUDE.md's "two
// v1alpha1) -- the "lightweight unification" named in PROJECT-SPEC.md's "two
// independent provisioning mechanisms" gap: -provision-tenant stays the
// sole real actor (rbacstore + tenantprovision.ProvisionClickHouse, see
// runProvisionTenant's doc comment in cmd/enterprise-api/main.go); this
+1 -1
View File
@@ -43,7 +43,7 @@ database, regardless of any `tenant_id` tag a message carries -- routing
a tagged record into its own tenant's dedicated database is
`enterprise/internal/chwriter` and `enterprise/cmd/enterprise-ingest`'s
job (a separate module by architectural convention, not a licensing
split -- both are AGPLv3, see `/CLAUDE.md`'s licensing boundary), not
split -- both are AGPLv3, see `/PROJECT-SPEC.md`'s licensing boundary), not
this package's. `consumer` and `clickhousewriter` live outside
`internal/` (moved there once `enterprise/internal/chwriter` needed to
import them directly -- Go's compiler-enforced `internal/` visibility
+1 -1
View File
@@ -9,7 +9,7 @@
// tenant the call's bearer credential belongs to and attaches it as a
// "tenant_id" Kafka message header on every record produced -- the first
// step of Phase 4's ingest tenant-awareness (see
// /docs/phase-4-runbook.md and CLAUDE.md's "ingest itself has no tenant
// /docs/phase-4-runbook.md and PROJECT-SPEC.md's "ingest itself has no tenant
// concept" gap). Deliberately scoped no further than that for now:
// nothing downstream (this package's own consumer, or `search`'s
// separate Redpanda consumer) reads that header yet to route a record's
@@ -1,7 +1,7 @@
-- Additive-only per-resource grant: lets a specific user exceed their
-- tenant-baseline role on one dashboard (e.g. an Editor granted Admin on
-- a dashboard they don't own). No deny-overrides -- named non-goal in
-- /docs/phase-4-rbac-design.md and CLAUDE.md's Phase 4 exit criteria.
-- /docs/phase-4-rbac-design.md and PROJECT-SPEC.md's Phase 4 exit criteria.
CREATE TABLE IF NOT EXISTS dashboard_permissions
(
id UUID PRIMARY KEY,
+1 -1
View File
@@ -100,7 +100,7 @@ type LogRecord struct {
Severity Severity `protobuf:"varint,4,opt,name=severity,proto3,enum=sentry.logs.v1.Severity" json:"severity,omitempty"`
// Original, unparsed log line. Always populated, even when structured
// fields below are also present, per the schema-on-read fallback
// requirement in CLAUDE.md.
// requirement in PROJECT-SPEC.md.
Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"`
// Structured fields extracted by the agent's parser (e.g. RFC 5424
// syslog header fields), plus source-provided fields (e.g. Windows
+1 -1
View File
@@ -43,7 +43,7 @@ message LogRecord {
// Original, unparsed log line. Always populated, even when structured
// fields below are also present, per the schema-on-read fallback
// requirement in CLAUDE.md.
// requirement in PROJECT-SPEC.md.
string message = 5;
// Structured fields extracted by the agent's parser (e.g. RFC 5424
+1 -1
View File
@@ -8,7 +8,7 @@ aggregation queries.
Tantivy is a Rust library with no maintained Go bindings — using it from
`ingest` (Go) would mean cgo-bridging to a compiled Rust cdylib, exactly
the fragile FFI complexity CLAUDE.md's "prefer boring, well-understood
the fragile FFI complexity PROJECT-SPEC.md's "prefer boring, well-understood
dependencies... operators need to trust it" principle steers away from.
It would also couple ClickHouse-write latency to Tantivy-write latency in
the same request path. See `/docs/architecture.md` for the fuller
+1 -1
View File
@@ -76,7 +76,7 @@ no CVE, no known exploit, nothing to patch.
It reaches us only transitively (tantivy 0.22.1 -> measure_time 0.8.3 ->
instant 0.1.13), so it cannot be dropped without moving off the pinned
Tantivy version, and CLAUDE.md pins the stack deliberately.
Tantivy version, and PROJECT-SPEC.md pins the stack deliberately.
The advisory's substance does not apply to this build either: `instant`
exists to paper over std::time::Instant being unavailable on wasm, and
+1 -1
View File
@@ -1,6 +1,6 @@
# terraform-provider-cairnobs
Cairn OBS's Terraform provider -- `CLAUDE.md`'s "Repo conventions" section
Cairn OBS's Terraform provider -- `PROJECT-SPEC.md`'s "Repo conventions" section
names this a first-class deliverable alongside `cairnobsctl`
("CLI and Terraform provider are first-class, not afterthoughts"), but
no phase before this one had actually built any of it. Four resources
+1 -1
View File
@@ -2,7 +2,7 @@
// built on HashiCorp's terraform-plugin-framework (not the legacy
// SDKv2 -- the framework is the actively-developed, currently-
// recommended library for a provider started from scratch, matching
// CLAUDE.md's "prefer boring, well-understood dependencies" read
// PROJECT-SPEC.md's "prefer boring, well-understood dependencies" read
// forward rather than backward).
package provider
+1 -1
View File
@@ -1,5 +1,5 @@
// Command terraform-provider-cairnobs is Cairn OBS's Terraform provider --
// CLAUDE.md names it a first-class deliverable alongside cairnobsctl
// PROJECT-SPEC.md names it a first-class deliverable alongside cairnobsctl
// ("CLI and Terraform provider are first-class, not afterthoughts"),
// but this is the first phase to actually build any of it.
//