Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Run `task fixtures-check`, `task test`, and `task lint` before opening a pull re

Start with watch streams and authoritative resource views; add editing as an optional layer.
The [README](README.md) explains current capabilities and how to choose a native or gateway source.
Current API examples must match this branch. Native viewing is read-only LIST/WATCH on the shared
connection lifecycle; native editing, native resume, editor cleanup and larger comparisons are
separate. Save-progress evaluation remains a proposal until implemented.
Current API examples must match this branch. Native viewing is LIST/WATCH on the shared
connection lifecycle, and native editing writes conditional merge patches back through the same
proxy; native resume, editor cleanup and larger comparisons are separate. Save-progress evaluation remains a proposal until implemented.

- Keep the core gateway free of `client-go`; Kubernetes integration belongs in `gateway/kube`.
- Keep the browser client framework-free and free of runtime dependencies.
Expand Down Expand Up @@ -64,7 +64,7 @@ the rule it protects.
| `task e2e-wire` | Real Go SSE bytes consumed by the TypeScript client over HTTP. |
| `task e2e-browser` | Native `EventSource`, unbundled ESM and the native viewer example in Chromium. |
| `task cluster-facts` | Record observed Kubernetes behavior for the supported cluster version. |
| `task test-real-api` | Compose the save path and native viewing against a real API server: gateway or host proxy, host endpoint and browser store. |
| `task test-real-api` | Compose the save path and native viewing and editing against a real API server: gateway or host proxy, host endpoint and browser store. |
| `task test-cluster` | Exercise the Kubernetes backend against a real API server. |

The cluster tasks need Docker and take longer; the fixture suites are the per-pull-request baseline.
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Start with the resource state and guarantees the page needs; framing is an imple

| Source | What the page receives | Connector |
|---|---|---|
| Native through a host proxy | Original authorized resources, with the shared client lifecycle and store | `connectNativeWatch`, read-only: LIST, then WATCH, and a fresh LIST on every reconnect |
| Native through a host proxy | Original authorized resources, with the shared client lifecycle and store | `connectNativeWatch`: LIST, then WATCH, and a fresh LIST on every reconnect; edits are written back through the same proxy |
| Gateway | Projected, redacted or suppressed views and optional upstream watch sharing | `connectResourceStream`, delivered over SSE |

Native is the straightforward starting point for hosts that already proxy Kubernetes. It delivers
Expand Down Expand Up @@ -94,7 +94,7 @@ The host mounts the API server's paths under a base such as `/k8s` and owns cred
which collections a user may read. The connector lists the complete collection, delivers it as a
snapshot and becomes `live` once the WATCH from the collection's resourceVersion is accepted. Every
reconnect lists again, and the store prunes only when that snapshot completes. HTTP or in-stream 410
recovers within the bounded retry budget; 401, 403 and other 4xx are terminal. Slice 1 does not
recovers within the bounded retry budget; 401, 403 and other 4xx are terminal. The connector does not
paginate: a LIST that returns a continuation token is refused. The
[native viewer example](examples/native-viewer/README.md) runs this against `kubectl proxy`.

Expand Down Expand Up @@ -150,27 +150,31 @@ const intent = store.captureSave(uid); // detached { uid, resourceVersion, patch
```

The default policy allows `spec`, labels, annotations, `data` and `stringData`; status, immutable
metadata and redacted paths remain read-only. A host can narrow the policy for its form.
metadata and redacted paths remain read-only. Under every policy, `metadata.managedFields` and the
last-applied annotation stay read-only too. A host can narrow the policy for its form.

The intended Save flow is explicit: capture the patch, UID and resource version together; have the
host authorize and validate it; apply a conditional merge PATCH; then observe the projected result
through the stream or a guarded projected read. Preserve typing made after Save. A version rejection
can occur without any field conflict, because suppressed updates still advance Kubernetes versions.
The current recovery is a guarded read, review and another deliberate Save.
The current recovery is a guarded read, review and another deliberate Save. A native store follows
the same flow through its host proxy: the browser sends the merge PATCH with the captured UID and
version inside it, and the proxy validates it before forwarding.

**A dirty draft, an accepted write and application progress are separate states.** A successful PATCH
can precede its watch observation, and neither proves a workload has finished rolling out.

Use [the editor state model](docs/client-state-model.md) for reconciliation, conflict resolution and
arrays, and [saving edits safely](docs/saving.md) for the complete host-owned write contract. The
[conditional-save example](examples/conditional-save/README.md) executes that contract.
[conditional-save example](examples/conditional-save/README.md) executes that contract, and the
[native editor example](examples/native-editor/README.md) its native counterpart.

## How it fits today

```mermaid
flowchart LR
api["Kubernetes API"]
proxy["Your native proxy<br/>Credentials and routing"]
proxy["Your native proxy<br/>Credentials, routing and write checks"]
gateway["Go gateway<br/>Scopes, views and optional sharing"]
connector["Fetch connector<br/>State events and recovery"]
store["Resource store<br/>Live state and optional drafts"]
Expand All @@ -185,6 +189,8 @@ flowchart LR
ui -->|"Local edits"| store
ui -->|"Captured save intent"| save
save --> api
ui -->|"Native conditional PATCH"| proxy
proxy --> api
```

A page uses one source per store: the native proxy or the gateway. The gateway runs inside your
Expand Down Expand Up @@ -218,7 +224,7 @@ such as `spec`, `status` or ConfigMap `data`. Custom resources follow the same c
- [Editor state model](docs/client-state-model.md): drafts, conflicts, redactions and arrays.
- [Saving](docs/saving.md): conditional writes, recovery and user-facing outcomes.
- [Authorization](docs/auth.md) and [operations](docs/operations.md): identity, revocation and runtime limits.
- [Examples](examples/README.md): native viewer, gateway browser demo, conditional editor, recovery recipes and Vue integration.
- [Examples](examples/README.md): native viewer and editor, gateway browser demo, conditional editor, recovery recipes and Vue integration.
- [Delivery plan](docs/proposals/0006-stream-and-save-implementation-plan.md): completed work, open work, ordering and dependencies.
- [Upgrading from 0.7](docs/migrating.md) and [releasing](docs/releasing.md).

Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ tasks:
# The save path, composed against a real API server: the real gateway, a real host endpoint and the
# real browser store (run by node).
test-real-api:
desc: "Real-API composition: save path (status churn, overlapping recovery, UID race) and native viewing through a host proxy."
desc: "Real-API composition: save path (status churn, overlapping recovery, UID race) and native viewing and editing through a host proxy."
deps: [cluster-up]
dir: gateway/kube
env:
Expand Down
5 changes: 3 additions & 2 deletions docs/adopting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ delivered over SSE. Both connectors share one fetch-based frontend lifecycle.

For native access, a host that already proxies Kubernetes needs no gateway: point
`connectNativeWatch` at a collection behind that proxy, as in the [README](../README.md#watch-native-resources-through-a-host-proxy)
and the [native viewer example](../examples/native-viewer/README.md). It is read-only and re-lists on
every reconnect; native editing is separate. The steps below wire the gateway source.
and the [native viewer example](../examples/native-viewer/README.md). It re-lists on every
reconnect; to edit through the same proxy, see [native editing](saving.md#native-editing-through-a-host-proxy).
The steps below wire the gateway source.

## 1. Mount the stream endpoint

Expand Down
2 changes: 1 addition & 1 deletion docs/alternatives.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and optional draft reconciliation. Choose tools according to the part your appli

| Need | Relevant approach | Where krm-stream fits |
|---|---|---|
| Native watch through an existing browser proxy | Host proxy plus a native fetch/watch consumer | `connectNativeWatch` lists and watches through the proxy with the shared lifecycle and store, read-only; the gateway adds projected views over SSE. |
| Native watch through an existing browser proxy | Host proxy plus a native fetch/watch consumer | `connectNativeWatch` lists and watches through the proxy with the shared lifecycle and store, and edits write conditional merge patches back through it; the gateway adds projected views over SSE. |
| Kubernetes API access from a server | Kubernetes client libraries and raw watches | The Go adapter uses `client-go`; the gateway adds projections and browser snapshot framing. |
| A complete Kubernetes UI | Dashboard applications and their plugin APIs | krm-stream supplies state and transport; the host builds the UI. |
| Review and deliver configuration packages | KRM package and GitOps systems | The host can record accepted writes in its delivery workflow. |
Expand Down
8 changes: 5 additions & 3 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ the service identity's access. Full/spec Secret redaction also does not replace
These settings describe the current gateway. The native connector reads through a host proxy behind
the same session boundary; the proxy, not the browser, holds Kubernetes credentials and authorizes
each collection. Native retains original authorized resources; gateway SSE delivers
projected views. Neither source may bypass refusal or extend session lifetime. The first native
slice is read-only and must stop on terminal authentication failure, with a new connection after
access is restored; native editing is separate.
projected views. Neither source may bypass refusal or extend session lifetime. The native connector
must stop on terminal authentication failure, with a new connection after access is restored. Native
writes go through the same proxy as the signed-in person; the proxy validates each PATCH with
`ValidateNativeMergePatch` and keeps its own write authorization, and Kubernetes RBAC still applies.
See [native editing](saving.md#native-editing-through-a-host-proxy).

## Browser sessions

Expand Down
19 changes: 13 additions & 6 deletions docs/client-state-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ The connector consumes and delivers state events independently of the editor.

Do not mutate a `draft()` return value directly: reads are detached copies, and that bypasses edit
policy and notifications. Independent sources/scopes need separate stores. Switching identity or view
must not silently reuse drafts, redactions or snapshot state. The native connector,
`connectNativeWatch`, uses the store read-only; this guide's editing and save examples use gateway
views. Native editing requires a source-bound policy and host read/write contract in a later slice.
must not silently reuse drafts, redactions or snapshot state. The same store edits objects from
`connectNativeWatch`: native writes and recovery reads go through the proxy the watch reads, as
[native editing](saving.md#native-editing-through-a-host-proxy) describes. Never mix native and
projected objects, responses or editors in one store.

The native viewer uses the existing standalone `applyStreamEvent(store, event)`. A bound store
method is proposed editor cleanup in proposal 0009, not a current API or a prerequisite for native
transport. No new store is needed.
Both connectors feed the store through the existing standalone `applyStreamEvent(store, event)`. A
bound store method is proposed editor cleanup in proposal 0009, not a current API or a prerequisite
for native transport. No new store is needed.

## State per resource

Expand Down Expand Up @@ -63,6 +64,12 @@ objects it has not yet reloaded.
The default editable regions are `spec`, `metadata.labels`, `metadata.annotations`, `data`, and
`stringData`. `status`, immutable metadata, and redacted paths are read-only.

Under every policy, the store also keeps `metadata.managedFields` and the
`kubectl.kubernetes.io/last-applied-configuration` annotation read-only. These are the machinery
that gateway projections remove and native objects carry. A map that holds a protected path, such
as `metadata.annotations` beside the last-applied annotation or a Secret's `data` beside withheld
values, cannot be replaced or removed whole. It is merged and edited key by key instead.

Suppose a Deployment starts with image `v1` and replicas `3`. The person types image `v2`, then an
autoscaler changes replicas to `5`. The draft becomes image `v2`, replicas `5`: untouched fields follow
the server while local work stays. If another person changes the image to `v3`, the local image `v2`
Expand Down
Loading
Loading