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
2 changes: 1 addition & 1 deletion .specify/feature.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"feature_directory": "specs/022-settled-readiness"
"feature_directory": "specs/023-live-nuplane-options"
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,5 @@ When asked to review or address PR review comments, think critically about each
- 011-map-shell-config: Planned map-based shell configuration under `CShells:Shells`

<!-- SPECKIT START -->
For the active work unit, read `specs/022-settled-readiness/plan.md`.
For the active work unit, read `specs/023-live-nuplane-options/plan.md`.
<!-- SPECKIT END -->
32 changes: 32 additions & 0 deletions specs/023-live-nuplane-options/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Specification Quality Checklist: Apply Live Nuplane Integration Options

**Purpose**: Validate specification completeness and quality before planning

**Created**: 2026-10-08

**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details in the user journeys or outcome requirements
- [x] Focused on host operator and integration-consumer value
- [x] Written in plain language with all mandatory specification sections complete

## Requirement Completeness

- [x] No unresolved clarification markers remain
- [x] Requirements and acceptance scenarios are testable and unambiguous
- [x] Success criteria are measurable and tied to observable behavior
- [x] Edge cases cover cancellation, in-flight policy changes, pending work, and invalid options
- [x] Scope, assumptions, compatibility, and external adoption boundaries are explicit

## Feature Readiness

- [x] Every functional requirement is covered by an acceptance scenario or edge case
- [x] The primary scenario can be tested independently through eligible completion deliveries
- [x] The specification distinguishes behavior preserved from runtime behavior being added
- [x] No Foundation, core-package, or pruning work is included in this feature

## Notes

The separately reviewed live-options requirements gate resolves the runtime option source and compatibility decisions. Its probe is evidence of the existing snapshot gap, not acceptance of an implementation. No `.specify/extensions.yml` hooks are installed.
35 changes: 35 additions & 0 deletions specs/023-live-nuplane-options/contracts/options-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Contract: Per-delivery Nuplane integration options

## Scope

This is the behavioral contract for the existing optional `CShells.Nuplane` adapter. It adds no public API or configuration key. See [`NuplaneIntegrationOptions`](../../../src/CShells.Nuplane/NuplaneIntegrationOptions.cs) and the [adapter guide](../../../src/CShells.Nuplane/README.md) for public names and host examples.

## Supported options source

Supported configuration includes `WithNuplaneFeatureDiscovery(Action<NuplaneIntegrationOptions>)`, standard options configuration/binding, and dependency-aware `services.AddOptions<NuplaneIntegrationOptions>().Configure<TDependency>(...)`. A host with a custom options provider may register `IOptionsMonitor<NuplaneIntegrationOptions>` explicitly. Replacing only `IOptions<NuplaneIntegrationOptions>` is unsupported; the adapter does not combine monitor and static values or define precedence between them.

## Delivery boundary

For a delivered callback, null arguments, an already-canceled token, and ineligible empty work are checked first. Eligibility means at least one successfully applied package or at least one committed removal. These paths do not read the options monitor or mutate epochs or access the shell registry.

For an eligible callback, read `IOptionsMonitor<NuplaneIntegrationOptions>.CurrentValue` once. Before any epoch mutation, registry access, or await:

1. copy `Enabled`, `RefreshTrigger`, `AutoReload`, and the `OnReloadResults` delegate reference to one private immutable operation value;
2. verify `RefreshTrigger` is a defined enum member and reject an undefined value with an actionable configuration error;
3. allow options-monitor and options-validation failures to propagate before coordinator side effects; no new wrapper or last-known-good fallback is required.

All policy decisions and result reporting for the admitted delivery use these copied values. Changes after capture apply to later deliveries only. The synchronous field copy is not an atomic multi-property transaction against concurrent mutation of one options object.

## Field behavior

- `Enabled = false`: do not record new observer-requested work; retain existing freshness and reload epochs. Build-participant refreshes for previously recorded work are independent of this observer setting.
- `RefreshTrigger = ChangedOrPending`: preserve existing change and pending-freshness behavior.
- `RefreshTrigger = EveryEligibleCompletion`: preserve existing refresh behavior for every eligible delivery.
- `AutoReload = false`: freshness work may proceed, but do not attempt or acknowledge pending reload work. A later eligible delivery with reload enabled may retry retained work without rescanning an already-fresh catalog unless a newer package change requires refresh.
- `OnReloadResults`: invoke the callback captured by that delivery with the existing stable read-only copy of raw registry results. Preserve existing result evaluation, partial-error, cancellation, reentry, and retry behavior.

The build participant does not read these observer options. It consumes already-recorded freshness before the catalog read and does not activate, reload, or report a shell.

## Non-goals and limits

The contract does not add an options-change listener, timer, background retry loop, new public flag, new package dependency, core-package policy, host composition, stable pin, package-readability guarantee, physical unloading, pruning, or Foundation host readiness policy. Those remain with their existing owners and acceptance gates.
26 changes: 26 additions & 0 deletions specs/023-live-nuplane-options/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Data Model: Apply Live Nuplane Integration Options

## Captured integration policy

An ephemeral value created once for an eligible, non-canceled package-reconciliation completion. It contains copies of:

| Value | Source | Lifetime and use |
|---|---|---|
| Enabled | Current `NuplaneIntegrationOptions.Enabled` | Determines whether the delivery requests observer work. Disabled delivery adds no new epochs and leaves previous epochs unchanged. |
| Refresh trigger | Current `NuplaneIntegrationOptions.RefreshTrigger` | Selects the existing refresh behavior. Undefined enum values fail before coordinator side effects. |
| Automatic reload | Current `NuplaneIntegrationOptions.AutoReload` | Determines whether eligible work requests/attempts shell promotion. When false, already-pending promotion remains pending. |
| Reload-results callback | Current `NuplaneIntegrationOptions.OnReloadResults` delegate reference | The same captured callback receives results for this delivery. A later replacement applies to a later delivery. |

The copy is made after null, cancellation, and empty-eligibility checks, and before epoch mutation, registry access, or awaits. Reading the current options once and copying each property does not promise atomicity against arbitrary concurrent mutation of the same options object during the copy.

## Existing coordinator state

The coordinator already tracks requested and committed catalog-freshness epochs and requested and committed reload epochs, including the catalog epoch associated with a reload request. Those counters, their synchronization, retry rules, and commit points remain unchanged. The captured policy is operation-local and is not retained as a second state machine.

## Relationships

- An eligible observer delivery may advance the existing catalog epoch according to its captured policy and package change set.
- A successful catalog refresh acknowledges only the freshness epoch captured by that refresh.
- Automatic reload is gated by the captured policy and current pending epochs; a successful reload acknowledges only work handled by that attempt.
- A shell-generation build participant consumes previously recorded catalog freshness without reading observer options or requesting activation/reporting.
- A settings change after one delivery captures its policy has no relationship to that in-flight value; it is observed by the next eligible delivery.
95 changes: 95 additions & 0 deletions specs/023-live-nuplane-options/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Implementation Plan: Apply Live Nuplane Integration Options

**Branch**: `023-live-nuplane-options` | **Date**: 2026-10-08 | **Spec**: [spec.md](spec.md)

**Input**: Feature specification from `/specs/023-live-nuplane-options/spec.md`

## Summary

Make the optional CShells.Nuplane coordinator consume current supported option values for each eligible package-reconciliation completion. Capture and copy one small immutable per-delivery policy before epoch mutation or asynchronous work, then carry those copied values through freshness, automatic reload, and reload-result reporting. Preserve current epochs, build participation, registration aliases, eligibility, reentry, and settlement behavior.

## Technical Context

**Language/Version**: C# 14; source packages target net8.0, net9.0, net10.0

**Primary Dependencies**: Existing `Microsoft.Extensions.Options`, `Microsoft.Extensions.Configuration`, and CShells/Nuplane abstractions; no new package

**Storage**: Existing in-memory refresh and promotion epochs; no new persisted state

**Testing**: xUnit in `tests/CShells.Tests`, net10.0; external public-package consumer on actual net8/net9/net10 after publication

**Target Platform**: Existing supported .NET runtimes

**Project Type**: Multi-package library; this work is confined to `src/CShells.Nuplane`

**Performance Goals**: No new listener, background timer, queue, or per-delivery allocation beyond a small immutable value snapshot; no separate performance target

**Constraints**: Snapshot current options once after callback validation/cancellation/eligibility checks and before epoch mutation or awaits; do not promise an atomic multi-property read against concurrent mutation of one options object; preserve current package-observer ordering and root/child ownership

**Scale/Scope**: One internal adapter policy change, deterministic tests, adapter documentation, and required publication qualification; no CShells-core, Nuplane-core, Foundation host, or stable-package changes

## Constitution Check

- **I. Abstraction-first**: No consumer-extensibility API is added. Existing public `NuplaneIntegrationOptions` remains the contract; implementation remains in the optional adapter.
- **II. Feature modularity**: No feature composition, assembly discovery, or shell policy changes are introduced.
- **III. Modern C#**: Preserve nullable, language, package-target, and repository style conventions.
- **IV. Explicit errors**: An undefined `RefreshTrigger` gets an actionable configuration error before state/registry work. Monitor and options-validation failures propagate before that work without a new wrapping-error contract.
- **V. Test coverage**: Add deterministic adapter regressions for changes across deliveries, changes during an admitted operation, pending/deferred work, invalid settings, empty/canceled callbacks, and existing configuration paths. The repository requires tests; it does not declare test-first/TDD cadence for this feature.
- **VI. Simplicity**: Use one immutable private policy value and one `IOptionsMonitor` read per eligible callback. Add no public abstraction, second configuration source, background listener, timer, general settings framework, or new package dependency.
- **VII. Lifecycle and concurrency**: Keep epoch locks/gates and async sequencing as-is. Options are copied before awaits; no user callback or async operation is introduced while holding a new lock.

Pre-design check passes. Recheck after design: no principle violation or exception is needed.

## Design Decisions

1. Resolve `IOptionsMonitor<NuplaneIntegrationOptions>` in the root-owned coordinator and update the coordinator factory in `CShellsNuplaneBuilderExtensions.cs` to supply that monitor. Keep existing `Configure(Action<NuplaneIntegrationOptions>)`, standard bind, and dependency-aware `Configure<TDependency>` paths.
2. For an eligible, non-canceled callback, read `CurrentValue` exactly once and synchronously copy `Enabled`, `RefreshTrigger`, `AutoReload`, and `OnReloadResults` into a private immutable value before mutating epochs or resolving the registry. Empty/failed-only and already-canceled callbacks return or cancel before reading options.
3. Validate that the copied refresh trigger is a defined enum member before any epoch or registry work and report an actionable configuration error for an invalid value. Let options-monitor and options-validation failures propagate before side effects; do not add last-known-good fallback or promise to wrap arbitrary provider errors.
4. Use the copied values throughout that callback, including callback invocation and result interpretation. Changes after capture apply to later deliveries and do not cancel or rewrite current work.
5. Keep existing epoch state authoritative. Disabled observer work creates no new epochs and leaves old ones intact. Automatic reload off may refresh catalog state but does not attempt or acknowledge pending reload work. A later enabled eligible delivery retries pending reload work without rescanning a fresh catalog unless a newer source change requires it.
6. Keep `BeginAsync` independent of observer options and retain existing catalog refresh-before-catalog-read ordering. Do not add an options-change listener or settings dependency to the build participant.
7. Treat direct replacement of `IOptions<NuplaneIntegrationOptions>` alone as unsupported. Standard options configuration or explicit `IOptionsMonitor<NuplaneIntegrationOptions>` registration is supported; do not add dual-source precedence or compatibility shims.

See [research.md](research.md), [data-model.md](data-model.md), and [contracts/options-policy.md](contracts/options-policy.md) for source evidence and exact policy boundaries.

## Design and Test Cadence

The source repository's active constitution requires tests but does not impose a test-first cadence. Implement deterministic regression cases with the adapter change, then run the focused suite. Gates must be controlled with `TaskCompletionSource` and bounded joins, not sleeps. Existing tests that instantiate the internal coordinator with `IOptions<T>` need a small shared test monitor or service-provider resolution through the real options system; do not retain production `IOptions<T>` as a compatibility path.

The critical snapshot proof holds catalog refresh after policy capture, changes values on the held options object and replaces the callback, then releases refresh. The admitted delivery must use its copied policy/callback; a subsequent eligible delivery must use the new values. Additional gates prove a later enabled delivery retries retained reload work without rescanning. Failed-only and pre-canceled callbacks must make zero monitor reads. Invalid enum/current-options failures must leave epoch/catalog/registry counters unchanged.

## Project Structure

```text
specs/023-live-nuplane-options/
spec.md, plan.md, research.md, data-model.md, quickstart.md, tasks.md
checklists/requirements.md
contracts/options-policy.md
src/CShells.Nuplane/
Internal/NuplaneRefreshCoordinator.cs
CShellsNuplaneBuilderExtensions.cs
README.md
tests/CShells.Tests/Integration/Nuplane/
NuplaneRefreshCoordinatorTests.cs
NuplaneCompositionTests.cs
NuplaneLiveOptionsTests.cs
NuplaneCoordinatorTestHarness.cs or a narrowly scoped options-monitor helper
```

**Structure Decision**: Keep the implementation in the existing optional adapter and use existing Nuplane integration test fixtures. Avoid new projects, public options abstractions, core changes, or duplicate test harnesses.

## Verification

Run, once on the final implementation head:

1. Focused `NuplaneRefreshCoordinatorTests`, `NuplaneCompositionTests`, `NuplaneLiveOptionsTests`, and `NuplaneReloadResultsTests`.
2. Full `tests/CShells.Tests/CShells.Tests.csproj` suite.
3. Release builds of `src/CShells.Nuplane/CShells.Nuplane.csproj` for net8.0, net9.0, and net10.0.
4. A compiled freeze-options mutation that restores the old captured-settings behavior; the live-options regression must fail under the mutation and pass after byte-identical restoration.
5. Required owner-repository PR CI and main checks, then a complete ten-package actual-public-Feedz archive audit and a fresh-cache PackageReference-only options-change consumer on actual net8/net9/net10 runtimes. Record exact version, source commit, archive hashes, loaded DLL identities, commands, and outputs. Do not count local or private package preflight as public package evidence.

Final stable release and Foundation host adoption/pins/locks are separate gates owned outside this feature.

## Complexity Tracking

No constitution exceptions or added complexity are proposed.
Loading
Loading