Skip to content

Commit 49ead6d

Browse files
committed
docs: update v3.6.1 to match Option A — three sibling dirs, per-kernel repos, CKProject CRD
Rewrote versioning.md for Option A (runc constraint discovery, no /ck-tool/ root, per-kernel bare repos, three sibling dirs under kubelet namespace dir). Added operator.md sections: per-version deployments, three PVs per kernel, CKProject CRD with kubectl get ckp, kopf+NATS dual control plane, .status patching, versioned reconciliation lifecycle (12 steps). Updated changelog.md v3.6.1 entry with all findings. Updated introduction.md release train with PROVEN status.
1 parent a62e251 commit 49ead6d

4 files changed

Lines changed: 604 additions & 164 deletions

File tree

docs/v3.6/changelog.md

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,52 +45,70 @@ v3.5-alpha6 was the last deployed incremental release. v3.6 adds:
4545

4646
**Date:** 2026-04-06 | **Implements:** CK.Operator v1.3.0
4747

48-
Version materialisation moves from `serving.json` on disk to the CK.Project custom resource. The CK volume becomes purely immutable -- no write-through exceptions.
48+
Version materialisation moves from `serving.json` on disk to the CK.Project custom resource. The CK volume becomes purely immutable -- no write-through exceptions. Storage uses per-kernel bare repositories and three sibling directories per kernel version (Option A).
4949

5050
### What Changes
5151

5252
- **serving.json retired** -- no longer exists on disk. The three problems it created (write-through hack, inert file, decorative git refs) are dissolved.
53-
- **CK.Project `spec.versions`** -- named versions with git commit ref and URL route prefix. Version state is in etcd, not the filesystem.
54-
- **`spec.repo`** -- upstream git URL. The operator clones to scratch space and runs `git archive` locally (FUSE mounts cannot host bare repos).
55-
- **`deploy.materialise` step** -- new reconciliation step between `deploy.namespace` and `deploy.storage`. Streams `git archive` output to filer at `/ck/v/{tag}/{kernel}/`.
56-
- **`.git-ref` stamp** -- each materialised directory contains the exact commit hash. Implements `prov:wasGeneratedBy` from Git2PROV.
57-
- **Per-version PVs** -- `ck-{project}-{kernel}-{tag}`, each pointing to `/ck/v/{tag}/{kernel}`. When a tag's ref changes, content updates without PV recreation.
53+
- **Option A: Three sibling dirs** -- in-container mount layout uses `/ck/{kernel}/ck/`, `/ck/{kernel}/tool/`, `/ck/{kernel}/data/` as three sibling PVs under a kubelet-created namespace directory. No nested volume mounts. Driven by the runc constraint: `mkdirat` fails with EROFS in ReadOnly parent overlays before CSI volume content is visible.
54+
- **Per-kernel bare repos** -- each kernel has its own isolated bare git repository at `/ck/{kernel}/` on the SeaweedFS filer. No monorepo. No `spec.repo`. CK and TOOL loops extract from the same bare repo to sibling `ck/` and `tool/` directories.
55+
- **CKProject CRD** -- `ck.tech.games/v1` kind `CKProject` with `spec.versions` containing per-kernel `ck_ref` and `tool_ref`. `kubectl get ckp -A` shows Phase, Versions, and Checks.
56+
- **kopf + NATS dual control plane** -- both kopf CRD watch and NATS message listener trigger the same `reconcile()` function. `kubectl apply` and `nats pub` produce identical results.
57+
- **CKProject .status patching** -- operator patches `.status` after each reconcile with phase, per-version materialisation state, and aggregate proof.
58+
- **`deploy.materialise` step** -- new reconciliation step between `deploy.namespace` and `deploy.storage`. Extracts CK loop to `/ck/{kernel}/{version}/ck/` and TOOL loop to `/ck/{kernel}/{version}/tool/` via `git archive` from per-kernel bare repos.
59+
- **Three PVs per kernel per version** -- `ck-{project}-{kernel}-{version}-ck`, `ck-{project}-{kernel}-{version}-tool`, `ck-{project}-{kernel}-{version}-data`. CK and TOOL ReadOnlyMany, DATA ReadWriteMany.
60+
- **Per-version deployments** -- each declared version gets its own Deployment and service. Multiple versions run simultaneously at different route prefixes.
5861
- **Per-version HTTPRoutes** -- longest-prefix-first routing. Each version gets its own deployment and route rule.
59-
- **Garbage collection** -- filer paths under `/ck/v/` not referenced by any CK.Project version are deleted after reconciliation.
60-
- **Shared kernel optimisation** -- identical tree hashes across versions share one filer copy.
62+
- **`.git-ref` stamp** -- each materialised loop directory (`ck/` and `tool/`) contains the exact commit hash. Implements `prov:wasGeneratedBy` from Git2PROV.
63+
- **Quick setup mode** -- no git required for bootstrapping. Omit `ck_ref`/`tool_ref` and upload files directly to the filer. Transition to git-managed later without changing PVs or mounts.
64+
- **Garbage collection** -- version directories under `/ck/{kernel}/` not referenced by any CK.Project are deleted. Git internals (`HEAD`, `objects/`, `refs/`) are never GC'd.
65+
- **Shared kernel optimisation** -- identical refs across versions share one filer copy, PVs point to existing path.
6166
- **Backward compatible** -- no `spec.versions` means flat layout, existing projects unchanged.
62-
63-
### Git Model (D9)
64-
65-
- One repo per project. CK + TOOL in same repo, DATA never in git.
66-
- Tag prefix convention: `ck/{kernel}/vX.Y.Z`, `tool/{kernel}/vX.Y.Z` for independent loop versioning.
67-
- Per-loop overrides: `ck_ref` and `tool_ref` in version declarations for split CK/TOOL tags.
68-
- Ontological grounding: DOAP for repo metadata, Git2PROV (PROV-O) for commit provenance.
67+
- **Proven on live cluster** -- `hello-v1-0-0-proc` Running with three sibling PVs on AKS + SeaweedFS 3.93.
68+
69+
### Reconciliation Lifecycle (Versioned -- 12 Steps)
70+
71+
```
72+
1. deploy.namespace
73+
2. deploy.materialise -- NEW
74+
3. deploy.storage.ck
75+
4. deploy.storage.tool -- NEW (separate TOOL PVs)
76+
5. deploy.storage.data
77+
6. deploy.processors
78+
7. deploy.web
79+
8. deploy.routing
80+
9. deploy.conceptkernels
81+
10. deploy.auth
82+
11. deploy.graph
83+
12. deploy.endpoint
84+
```
6985

7086
### Deficiencies Resolved
7187

7288
| Deficiency | Resolution |
7389
|-----------|-----------|
74-
| D1: No version materialisation | `git archive` from bare repo, commit-pinned |
75-
| D3: No serving.json write-through | serving.json retired, version state in CR |
76-
| D6: No git integration on filer | Bare repo on scratch, `.git-ref` traceability |
90+
| D1: No version materialisation | `git archive` from per-kernel bare repos, commit-pinned via `ck_ref`/`tool_ref` |
91+
| D2: Per-kernel volume isolation | Resolved by three-PV model (ck, tool, data per kernel per version) |
92+
| D3: No serving.json write-through | serving.json retired, version state in CK.Project CR |
93+
| D6: No git integration on filer | Per-kernel bare repos on filer, `.git-ref` traceability |
7794

7895
### New CK.Project CRD Fields
7996

8097
| Field | Type | Required | Description |
8198
|-------|------|----------|-------------|
82-
| `spec.repo` | string | MUST (if versions declared) | Upstream git URL |
8399
| `spec.versions` | array | SHOULD | Version declarations |
84-
| `spec.versions[].name` | string | MUST | Tag name (used in PV names, filer paths, routes) |
85-
| `spec.versions[].ref` | string | MUST | Git commit hash to materialise |
86-
| `spec.versions[].route` | string | MUST | URL path prefix |
87-
| `spec.versions[].ck_ref` | string | MAY | CK loop tag override |
88-
| `spec.versions[].tool_ref` | string | MAY | TOOL loop tag override |
100+
| `spec.versions[].name` | string | MUST | Version tag (used in PV names, filer paths, routes) |
101+
| `spec.versions[].route` | string | MUST | URL path prefix on project hostname |
102+
| `spec.versions[].data` | string | SHOULD | "isolated" or "shared" -- DATA directory scoping |
103+
| `spec.versions[].kernels` | array | MUST | Per-kernel declarations |
104+
| `spec.versions[].kernels[].name` | string | MUST | Concept kernel name |
105+
| `spec.versions[].kernels[].ck_ref` | string | MAY | Git commit hash for CK loop extraction |
106+
| `spec.versions[].kernels[].tool_ref` | string | MAY | Git commit hash for TOOL loop extraction |
89107

90108
### New Ontology Classes
91109

92-
- `VersionDeclaration` -- named version pinned to a git commit, mapped to a URL route
93-
- `GitRepoConfig` -- upstream git repository location
110+
- `VersionDeclaration` -- named version with per-kernel refs, mapped to a URL route
111+
- `KernelVersionRef` -- per-kernel git refs for a specific version deployment
94112

95113
---
96114

docs/v3.6/introduction.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,15 @@ v3.5.16 Agent Teams PLANNED
175175
---------------------------------------------------------------------
176176
v3.6 Full release -- sum of all above
177177
178-
v3.6.1 serving-multiversion-unpack (CK.Operator v1.3.0) SPEC
179-
Version materialisation via CK.Project CR
180-
serving.json retired, git archive -> filer pipeline
181-
Per-version PVs, HTTPRoutes, garbage collection
178+
v3.6.1 serving-multiversion-unpack (CK.Operator v1.3.0) PROVEN
179+
Option A: three sibling dirs (runc constraint discovery)
180+
Per-kernel bare repos (no monorepo, no /ck-tool/ root)
181+
CKProject CRD with per-kernel ck_ref/tool_ref
182+
kopf + NATS dual control plane
183+
Per-version deployments, 3 PVs per kernel per version
184+
Quick setup mode (no git required)
185+
serving.json retired, version state in CK.Project CR
186+
Proven: hello-v1-0-0-proc Running with three sibling PVs
182187
```
183188

184189
### Why This Model

docs/v3.6/operator.md

Lines changed: 152 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ For the full step-by-step breakdown, see [Reconciliation Lifecycle](./reconcilia
8181
## Version Materialisation (v3.6.1)
8282

8383
::: info v3.6.1 -- serving-multiversion-unpack
84-
This section documents the version materialisation model introduced in v3.6.1, implemented in CK.Operator v1.3.0. See [Versioning](./versioning) for the full git model.
84+
This section documents the version materialisation model introduced in v3.6.1, implemented in CK.Operator v1.3.0. See [Versioning](./versioning) for the full storage layout and runc constraint analysis.
8585
:::
8686

8787
### serving.json Is Retired
@@ -96,110 +96,202 @@ v3.6.1 dissolves all three problems by moving version state from the filesystem
9696

9797
### Version State in CK.Project CR
9898

99-
Version declarations live in the CK.Project custom resource:
99+
Version declarations live in the CK.Project custom resource. Each version specifies per-kernel git refs for both CK and TOOL loops:
100100

101101
```yaml
102+
apiVersion: ck.tech.games/v1
103+
kind: CKProject
104+
metadata:
105+
name: hello
102106
spec:
103-
repo: https://github.com/ConceptKernel/kernels.git
107+
hostname: hello.tech.games
104108
versions:
105-
- name: live
106-
ref: abc123f
109+
- name: v1.3.2
107110
route: /
108-
- name: staging
109-
ref: def4567
110-
route: /staging
111-
- name: review-pr-42
112-
ref: 789feda
113-
route: /review/pr-42
111+
data: isolated
112+
kernels:
113+
- name: Hello.Greeter
114+
ck_ref: abc123f
115+
tool_ref: aaa111
116+
- name: CK.Lib.Py
117+
ck_ref: eee555
118+
tool_ref: fff666
119+
- name: v1.3.19
120+
route: /next
121+
data: isolated
122+
kernels:
123+
- name: Hello.Greeter
124+
ck_ref: def4567
125+
tool_ref: bbb222
126+
- name: CK.Lib.Py
127+
ck_ref: eee555 # same as v1.3.2
128+
tool_ref: fff666 # same as v1.3.2
129+
```
130+
131+
The operator reads `spec.versions`, materialises each version from per-kernel bare repositories on the SeaweedFS filer, and creates per-version deployments, PVs, and HTTPRoute rules. A version promotion is a CK.Project resource update -- `kubectl patch`, NATS command, or operator API. Standard Kubernetes-native workflow with etcd history.
132+
133+
### Per-Version Deployments
134+
135+
Each declared version gets its own Deployment. Pods in one version's deployment mount that version's PVs only. Multiple versions of the same project run simultaneously, each serving its designated route:
136+
137+
```
138+
hello-v1.3.2-proc → mounts v1.3.2 PVs, serves /
139+
hello-v1.3.19-proc → mounts v1.3.19 PVs, serves /next
140+
```
141+
142+
This model supports safe canary testing: deploy a new version at `/next`, verify it, then promote by swapping routes in the CK.Project CR.
143+
144+
### Three PVs Per Kernel Per Version
145+
146+
Each kernel in each version gets three independent PVs:
147+
148+
```
149+
ck-{project}-{kernel}-{version}-ck → /ck/{kernel}/{version}/ck/ ReadOnlyMany
150+
ck-{project}-{kernel}-{version}-tool → /ck/{kernel}/{version}/tool/ ReadOnlyMany
151+
ck-{project}-{kernel}-{version}-data → /ck-data/{hostname}/{kernel}/{version}/ ReadWriteMany
152+
```
153+
154+
Inside the pod, these mount as three sibling directories under the kernel name:
155+
156+
```
157+
/ck/{kernel}/ck/ ← CK PV (ReadOnly)
158+
/ck/{kernel}/tool/ ← TOOL PV (ReadOnly)
159+
/ck/{kernel}/data/ ← DATA PV (ReadWrite)
160+
```
161+
162+
The kernel name directory is not a volume -- it is a plain directory created by the kubelet. See [Versioning -- Three Sibling Dirs](./versioning#in-container-mount-layout-three-sibling-dirs-option-a) for the runc constraint that drives this design.
163+
164+
### CKProject CRD
165+
166+
v3.6.1 introduces the `CKProject` Custom Resource Definition at `ck.tech.games/v1`:
167+
168+
```bash
169+
kubectl get ckp -A
170+
```
171+
172+
```
173+
NAMESPACE NAME PHASE VERSIONS CHECKS AGE
174+
ck-hello hello Running 1 15 1d
175+
ck-delvinator delvinator Running 1 15 2d
176+
```
177+
178+
The CKProject CRD provides:
179+
180+
- **`spec.versions`** -- array of named versions with per-kernel `ck_ref`/`tool_ref`
181+
- **`spec.hostname`** -- project hostname for HTTPRoute generation
182+
- **`.status.phase`** -- Running, Degraded, or Failed (patched by operator after reconcile)
183+
- **`.status.versions`** -- per-version status with materialisation state
184+
- **`.status.proof`** -- aggregate proof from reconciliation checks
185+
186+
#### CKProject .status Patching
187+
188+
After each reconciliation, the operator patches the CKProject `.status` with the reconciliation result:
189+
190+
```yaml
191+
status:
192+
phase: Running
193+
versions:
194+
- name: v1.0.0
195+
materialised: true
196+
deploymentReady: true
197+
routeAccepted: true
198+
proof:
199+
totalChecks: 15
200+
totalPassed: 15
201+
lastReconciled: "2026-04-06T14:00:00Z"
114202
```
115203

116-
The operator reads `spec.versions`, materialises each version from the git repository, and creates per-version PVs and HTTPRoute rules. A version promotion is a CK.Project resource update -- `kubectl patch`, NATS command, or operator API. Standard Kubernetes-native workflow with etcd history.
204+
This means `kubectl get ckp` always reflects the actual cluster state. If materialisation fails or a deployment is unhealthy, the status shows it.
205+
206+
### Dual Control Plane: kopf + NATS
207+
208+
CK.Operator uses both kopf (Kubernetes watch) and NATS messaging as entry points. Both trigger the same `reconcile()` function:
209+
210+
```
211+
kopf watch CKProject CR change ──→ reconcile()
212+
NATS input.CK.Operator message ──→ reconcile()
213+
```
214+
215+
This provides two equivalent paths to the same outcome:
216+
217+
| Path | Use Case |
218+
|------|----------|
219+
| `kubectl apply -f ckproject.yaml` | GitOps, CI/CD pipelines, manual ops |
220+
| `nats pub input.CK.Operator '{"action":"project.deploy",...}'` | Web shell, inter-kernel communication, browser |
221+
222+
Both paths produce identical results. The reconcile function is idempotent -- running it twice with the same input produces no changes.
117223

118224
### Materialisation Pipeline
119225

120226
The reconciliation lifecycle gains a new step, `deploy.materialise`, inserted between `deploy.namespace` and `deploy.storage`:
121227

122228
```
123-
Reconciliation lifecycle (v3.6.1):
229+
Reconciliation lifecycle (v3.6.1 -- versioned):
124230
125231
1. deploy.namespace
126-
2. deploy.materialise -- NEW: git archive -> filer
232+
2. deploy.materialise -- NEW: git archive -> filer (per-version, per-kernel)
127233
3. deploy.storage.ck
128-
4. deploy.storage.data
129-
5. deploy.processors
130-
6. deploy.web
131-
7. deploy.routing
132-
8. deploy.conceptkernels
133-
9. deploy.auth
134-
10. deploy.graph
135-
11. deploy.endpoint
234+
4. deploy.storage.tool -- NEW: separate TOOL PVs
235+
5. deploy.storage.data
236+
6. deploy.processors
237+
7. deploy.web
238+
8. deploy.routing
239+
9. deploy.conceptkernels
240+
10. deploy.auth
241+
11. deploy.graph
242+
12. deploy.endpoint
136243
```
137244

245+
This is 12 steps (11 + the new materialise step). The flat (non-versioned) lifecycle remains 10 steps for backward compatibility.
246+
138247
For each version in `spec.versions`, the materialise step:
139248

140-
1. Checks if `/ck/v/{tag}/{kernel}/.git-ref` exists on filer
141-
2. If it exists and contains the same ref -- skip (already current)
249+
1. For each kernel, checks if `/ck/{kernel}/{version}/ck/.git-ref` and `/ck/{kernel}/{version}/tool/.git-ref` exist on filer
250+
2. If they exist and contain the matching refs -- skip (already current)
142251
3. If missing or different ref:
143-
- Runs `git archive {ref}:{kernel}/` from the bare repo
144-
- Uploads the archive to filer at `/ck/v/{tag}/{kernel}/`
145-
- Writes the commit hash to `/ck/v/{tag}/{kernel}/.git-ref`
146-
4. Ensures PV `ck-{project}-{kernel}-{tag}` exists pointing to `/ck/v/{tag}/{kernel}`
147-
5. Ensures PVC is bound
252+
- CK loop: `git -C /ck/{kernel} archive {ck_ref} | upload to /ck/{kernel}/{version}/ck/`
253+
- TOOL loop: `git -C /ck/{kernel} archive {tool_ref} | upload to /ck/{kernel}/{version}/tool/`
254+
- Writes commit hashes to `.git-ref` in each loop directory
255+
4. Ensures three PVs per kernel per version exist
256+
5. Ensures PVCs are bound
148257

149-
### Per-Version PVs and HTTPRoutes
150-
151-
Each declared version gets its own PersistentVolume and routing rule:
152-
153-
```yaml
154-
# Per-version CK volume
155-
apiVersion: v1
156-
kind: PersistentVolume
157-
metadata:
158-
name: ck-delvinator-core-live
159-
spec:
160-
accessModes: [ReadOnlyMany]
161-
capacity:
162-
storage: 50Gi
163-
csi:
164-
driver: seaweedfs-csi-driver
165-
volumeHandle: ck-delvinator-core-live
166-
volumeAttributes:
167-
path: "/ck/v/live/Delvinator.Core"
168-
```
258+
### Per-Version HTTPRoutes
169259

170-
HTTPRoute rules are ordered longest-prefix first, so `/staging` matches before `/`:
260+
HTTPRoute rules are ordered longest-prefix first:
171261

172262
```yaml
173263
spec:
174-
hostnames: [delvinator.tech.games]
264+
hostnames: [hello.tech.games]
175265
rules:
176266
- matches:
177-
- path: { type: PathPrefix, value: /staging }
267+
- path: { type: PathPrefix, value: /next }
178268
backendRefs:
179-
- name: delvinator-staging
269+
- name: hello-v1.3.19
270+
port: 80
180271
- matches:
181272
- path: { type: PathPrefix, value: / }
182273
backendRefs:
183-
- name: delvinator-live
274+
- name: hello-v1.3.2
275+
port: 80
184276
```
185277
186-
When a tag's ref changes, the PV is not recreated -- the filer path is stable (keyed by tag name). Content changes when the operator re-materialises. Pods pick up new content on rolling restart.
278+
When a version's ref changes, the PV is not recreated -- the filer path is stable (keyed by version name). Content changes when the operator re-materialises. Pods pick up new content on rolling restart.
187279
188280
### Version Lifecycle via CR
189281
190282
| CR Change | Operator Action |
191283
|-----------|-----------------|
192-
| Add version | Materialise, create deployment + PV/PVC + route rule |
193-
| Change version ref | Re-materialise, roll pods |
194-
| Remove version | Delete deployment, PV/PVC, route rule, GC filer path |
284+
| Add version | Materialise CK + TOOL, create deployment + 3 PVs/PVCs per kernel + route rule |
285+
| Change version ck_ref or tool_ref | Re-materialise affected loop, roll pods |
286+
| Remove version | Delete deployment, PVs/PVCs, route rule, GC filer paths |
195287
196288
### Garbage Collection
197289
198-
After reconciliation, the operator scans `/ck/v/` on the filer and deletes directories not referenced by any active version in any CK.Project resource. GC logs every deletion and never removes directories referenced by other projects.
290+
After reconciliation, the operator scans version directories under each kernel on the filer and deletes those not referenced by any active version in any CK.Project resource. GC deletes both `ck/` and `tool/` subdirectories together. GC MUST NOT delete git internals (`HEAD`, `objects/`, `refs/`). GC logs every deletion and never removes directories referenced by other projects.
199291

200292
### Shared Kernel Optimisation
201293

202-
When two versions reference the same git tree hash for a kernel (e.g., the CK loop has not changed between live and staging), the operator reuses one filer copy. The PV for the second version points to the first version's filer path -- no duplicate storage.
294+
When two versions reference the same commit for a kernel's loop, the operator does not extract twice. It checks `.git-ref` in the existing version directory. If the hash matches, the new version's PV points to the existing path -- no duplicate storage.
203295

204296
### Backward Compatibility
205297

0 commit comments

Comments
 (0)