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
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Run `task fixtures-check`, `task test`, and `task lint` before opening a pull re
- 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.
- Keep credentials, application identity, authorization policy, and writes in the host application.
- Keep one current name for each API. Before 1.0, remove superseded names and forwarding packages;
update callers and document the replacement instead of adding compatibility shims.
- Add public surface only for a demonstrated use case within the library’s scope. Prefer composing
existing primitives, and test the observable guarantee each addition promises.
- Treat `spec/v1.md` and `conformance/` as the shared contract between gateway and client.

## Changing behavior
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ patch through its own save endpoint, which is the one place a write can happen.
| `github.com/ConfigButler/krm-stream/gateway` | Dependency-free Go stream gateway and SSE handler. |
| `github.com/ConfigButler/krm-stream/gateway/kube` | Optional `client-go` backend and SubjectAccessReview authorizer. |
| `@configbutler/krm-stream` | Official dependency-free ESM client store and transports. |
| `krm-stream@0.1.0` | Deprecated, frozen compatibility name claim. Use the scoped package instead. |
| [`spec/v1.md`](spec/v1.md) | Normative protocol contract. |
| [`conformance/`](conformance/) | Shared fixtures exercised by the Go gateway and TypeScript client. |

Expand Down
2 changes: 1 addition & 1 deletion docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ It needs your server's service account to hold `create` on `subjectaccessreviews
`system:auth-delegator` role). It does **not** need impersonate rights: it asks a question *about* a
user, it does not act *as* one. And because the gateway re-authorizes every snapshot cycle, this is
also how a revocation reaches a stream that is already open. Timed checks bound quiet-stream revocation.
The old `SSARAuthorizer` name remains a deprecated alias; it never created SelfSubjectAccessReview.
Use `SubjectAccessReviewAuthorizer`; it creates SubjectAccessReview requests, not SelfSubjectAccessReview.

## What this library never does

Expand Down
319 changes: 22 additions & 297 deletions docs/proposals/0005-kubernetes-stream-and-save-semantics.md

Large diffs are not rendered by default.

350 changes: 118 additions & 232 deletions docs/proposals/0006-stream-and-save-implementation-plan.md

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ produces these tags and packages:
| Go Kubernetes adapter | `gateway/kube/vX.Y.Z` tag |
| Official browser client | `@configbutler/krm-stream` on npm |

`krm-stream@0.1.0` is a deprecated, frozen name claim that points to the official package. It is not
part of CI or future releases; new consumers should install `@configbutler/krm-stream`.
The historical `krm-stream@0.1.0` publication is outside the maintained release surface. Its local
forwarding package has been removed; install `@configbutler/krm-stream`. Removing local source does
not change an already published npm artifact.

Before 1.0, remove superseded API names and forwarding packages instead of maintaining compatibility
shims. Record each removal and its replacement in release notes, and update repository callers,
examples and documentation together. Changes to wire semantics still require explicit spec and
conformance review.

## Publishing setup

Expand All @@ -39,7 +45,7 @@ run can therefore leave GitHub/Go tags present without an npm package. Fix sourc
new release rather than moving public tags.

Configure npm trusted publishing for `ConfigButler/krm-stream`, workflow `release.yml`. No
long-lived `NPM_TOKEN` is required. `krm-stream` is frozen and needs no publisher configuration.
long-lived `NPM_TOKEN` is required. Configure only the maintained scoped package.

## Before merging a release PR

Expand Down
34 changes: 33 additions & 1 deletion docs/saving.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,33 @@ actual draft conflicts separately; when none exist, explain the refreshed base a
captured save. If reconciliation is refused, preserve the draft and recover before writing again.
Do not remove concurrency protection or blindly retry the old patch with a newer version.

## Answer 204 and let the watch echo it
## What the person editing sees

These presentations map to the copyable conditional editor's outcomes. The host owns wording,
authentication and any receipt or Git workflow; connection state is not a save guarantee.

| Situation / outcome | Suggested presentation | Host action |
|---|---|---|
| `version-stale`, no field conflicts | “Configuration refreshed. Your edits are intact; review and save again.” | Capture a new intent on the next deliberate Save. Do not show an empty conflict panel. |
| `draft-conflict` | Show local and current values at each conflicting field. | Offer explicit resolution through store APIs; keep the rest of the form visible. |
| Connection retrying or `recovering` | “Reconnecting. Your unsaved changes are still here.” | Disable writes until live; after a refused GET, the example requires a later accepted guarded read before another write. |
| `saved`, watch confirmation pending | “Saved to Kubernetes; waiting for live confirmation.” | Preserve later typing. Track any receipt separately from draft state. |
| Session expiry or access denial | Explain sign-in or access outcome. | Handle identity-scoped recovery; do not retry terminal auth failures indefinitely or label them field conflicts. |
| `unavailable`, deleted/recreated UID | “This configuration was removed. A replacement must be opened separately.” | Offer copy-out from a previously retained recovery copy; never apply the old draft automatically to the replacement. |

“Unsaved changes are still here” applies while the UID remains in the store. `removeResource` and
snapshot pruning discard deleted-object drafts. If recovery after deletion matters, retain a detached
copy as edits change, **before** removal; observing a missing UID is too late to read its old draft.
Keep recovery copies scoped to the original identity and UID, with a host-defined lifetime and cleanup.
They are for recovery, not a second draft to reconcile against incoming snapshots. An executed
subscription recipe, including edit-time capture and pruning, remains
[planned work](proposals/0006-stream-and-save-implementation-plan.md#deletion-recovery-copy).

For explicit keep-local resolution, the planned tested recipe is tracked in
[proposal 0006](proposals/0006-stream-and-save-implementation-plan.md#explicit-keep-local-resolution).
There is currently no dedicated keep-local helper; avoid a second application conflict registry.

## Answer 204 or a receipt and let the watch echo it

The object a Kubernetes write returns is a *raw* object: `managedFields`, the last-applied annotation,
`status`, and the Secret values your projection withholds. Writing it to the response hands the
Expand All @@ -71,6 +97,12 @@ stream as an ordinary `modified` event: projected, redacted, and three-way merge
user is still holding. Dirty state is derived from `draft` versus `server`, so there is nothing to
clear and nothing to adopt. The echo settles it.

A **receipt-only HTTP 200** is also valid: define and validate a host receipt schema containing only
intended acknowledgment fields, and keep it separate from the resource store. The copyable editor
accepts successful HTTP status but does not parse a receipt; add parsing in the host when needed.
For a Git-backed workflow, Kubernetes write acceptance, CommitRequest acceptance and an observed Git
commit are separate milestones. A receipt must not imply that all three have happened.

## If you must answer with the object

Prefer 204. If a host returns an object, capture `store.captureReconciliation(uid)` **before** the
Expand Down
9 changes: 8 additions & 1 deletion docs/vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ const { resource, state } = useLiveResource(store, uid, connection);
```

Use `resource.value?.draft` in script and `resource?.draft` in a template. A missing/deleted UID gives
`null`. For a changing selection, remount a keyed editor or create a new effect scope. Use store
`null`. For deletion recovery, capture the initial draft and subscribe to store notifications, retaining
`store.draft(uid)` while the fixed UID exists and keeping the last copy once it is absent. Capturing
only when Save is clicked loses subsequent edits. The null notification is too late to read the
removed draft. A [tested subscription recipe](proposals/0006-stream-and-save-implementation-plan.md#deletion-recovery-copy)
is pending; follow the [retention guidance](saving.md#what-the-person-editing-sees) and never apply a
recovery copy automatically to a replacement UID.

For a changing selection, remount a keyed editor or create a new effect scope. Use store
methods such as `setValue`, `removeKey` and conflict resolution methods for edits: do not bind
`v-model` directly to the draft snapshot. Store reads are detached copies, and editing them bypasses
policy checks and change notifications.
Expand Down
8 changes: 7 additions & 1 deletion examples/conditional-save/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ status-only updates. This is safe rejection, but sustained churn can hinder save
GET advances the base without requiring a snapshot. Reconcile first; never transplant an old
patch onto the latest version. No automatic write retry is performed.

Successful writes return 204. The stream echo settles the saved values while retaining later edits.
This endpoint returns 204 for successful writes. A host may instead return a receipt-only HTTP 200
under the [saving guide’s receipt contract](../../docs/saving.md#answer-204-or-a-receipt-and-let-the-watch-echo-it);
the client example accepts success but leaves receipt parsing to the host. The stream echo settles
the saved values while retaining later edits.
If the echo is delayed, dirty state remains visible; prevent repeated saves until your host's chosen
acknowledgment UX allows them. Save results never feed raw Kubernetes objects back into the store.

Expand All @@ -64,3 +67,6 @@ metadata, then the next Save performs only a guarded GET. An accepted read retur
(or actual draft conflicts); a subsequent user action captures a fresh write intent. This conservative
example may perform an extra read when a newer watch already won. It never guesses why the guard
returned false, installs a background retry loop, or retries a PATCH automatically.

Use the [user-facing outcome table](../../docs/saving.md#what-the-person-editing-sees) when wiring the
editor UI. This controller is copyable host code, not a core package export.
2 changes: 1 addition & 1 deletion gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@ For shared streams, set `Options.ReauthorizationInterval` (for example, 30 secon
`Options.ReauthorizationTimeout` (for example, 5 seconds). Each subscriber is rechecked independently,
including during quiet periods; denial or timeout stops only that subscriber. Zero interval keeps
cycle-only checks. See [authorization](../docs/auth.md) for callback contracts and capacity planning.
Use `kube.SubjectAccessReviewAuthorizer`; `SSARAuthorizer` is a deprecated compatibility alias.
Use `kube.SubjectAccessReviewAuthorizer` for Kubernetes-backed subscriber authorization.
6 changes: 0 additions & 6 deletions gateway/kube/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,3 @@ func groupResource(s gateway.Scope) string {
}
return s.Group + "/" + s.Resource
}

// SSARAuthorizer is the historical name for SubjectAccessReviewAuthorizer.
// Deprecated: use SubjectAccessReviewAuthorizer; this creates SubjectAccessReview, not SelfSubjectAccessReview.
func SSARAuthorizer(cs kubernetes.Interface, subjectFor SubjectFor) gateway.Authorizer {
return SubjectAccessReviewAuthorizer(cs, subjectFor)
}
10 changes: 0 additions & 10 deletions gateway/kube/authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,3 @@ func TestANamedScopeAsksAboutThatName(t *testing.T) {
}
}
}

func TestDeprecatedAuthorizerAlias(t *testing.T) {
cs, asked := reviewer(func(*authzv1.SubjectAccessReview) (bool, bool) { return true, false })
if err := kube.SSARAuthorizer(cs, subjectOf).Authorize(t.Context(), alice, configmapScope); err != nil {
t.Fatal(err)
}
if len(*asked) != 2 {
t.Fatal("alias must check list and watch")
}
}
12 changes: 0 additions & 12 deletions packages/krm-stream-compat/CHANGELOG.md

This file was deleted.

10 changes: 0 additions & 10 deletions packages/krm-stream-compat/README.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/krm-stream-compat/index.d.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/krm-stream-compat/index.js

This file was deleted.

41 changes: 0 additions & 41 deletions packages/krm-stream-compat/package.json

This file was deleted.

4 changes: 2 additions & 2 deletions packages/krm-stream/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ against a real `EventSource`. Neither has a runtime dependency.

## Status

The unscoped `krm-stream` package is a compatibility forwarder. This project is pre-1.0: the protocol
and the API may still change before 1.0. See the repository [README](../../README.md),
Install `@configbutler/krm-stream`. This project is pre-1.0: the protocol and API may still change
before 1.0. Renamed APIs are removed rather than retained as compatibility aliases. See the repository [README](../../README.md),
[client state model](../../docs/client-state-model.md), and [release guide](../../docs/releasing.md).

## Managed connections and conditional saves
Expand Down