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
12 changes: 10 additions & 2 deletions docs/internals/architecture/harness/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
are implementation evidence.
- Implementation status: PLC0--PLC8 are complete and terminally reviewed;
PLC9 is partially implemented through the explicit Linux and Windows AMD64
Coding Worker canaries. The remaining platform gaps are summarized by
`architecture.md` and tracked in the lifecycle plan.
Coding Worker canaries. PLC9D1/D2 add local-candidate Package GC operator
projection and dark reservation/fence mechanics; physical artifact GC remains
absent. The remaining platform gaps are summarized by `architecture.md` and
tracked in the lifecycle plan.
- Owner: `loushang.harness` Plugin architecture scope; contribution runtime
authority remains with each exact domain owner.

Expand Down Expand Up @@ -51,6 +53,12 @@ Neither may silently override a narrower implemented owner contract.
- [Resource Catalog And Source Pluginization Plan](resource-catalog-pluginization-plan.md)
owns the Resource/Skill catalog convergence and the rule that mechanisms may
be Plugin components while individual Skills remain Resources.
- [PLC9D1 Package GC Operator Projection Contract](plugin-lifecycle-plc9d1-contract.md)
bounds the current internal GC/cleanup read-model candidate; it does not
authorize physical deletion or claim PLC9D completion.
- [PLC9D2 Dark Package GC Reservation Contract](plugin-lifecycle-plc9d2-contract.md)
bounds opt-in reference fencing and durable reservation replay; no Product or
Store deletion route is active.

## Frozen Contracts

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
mechanics/rejection, C5.4 Linux Coding Product canary, and C5.5b/c Windows
LPAC containment and Coding Product canary. Both canaries are explicit and
default-dark; Current remains the default and unlisted routes remain closed.
[PLC9D1](plugin-lifecycle-plc9d1-contract.md) is a local candidate for an
internal, read-only Package GC/cleanup-debt projection. It does not reserve
or delete an artifact. [PLC9D2](plugin-lifecycle-plc9d2-contract.md) adds a
dark, opt-in durable reservation and reference-writer fence; it has no
Product/Store deletion route and does not close PLC9D.
- Scope: one delivery order for the common Plugin lifecycle, ordinary
Definition / Provider / Consumer authoring primitives, `coding.lsp`,
`coding.base`, `coding.arch`, management control, pre-LSP internal Resource/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
[Plugin Lifecycle PLC9.0 Baseline](plugin-lifecycle-plc9-baseline.md).
- PLC9B.0 refinement:
[Safe Package Boundary Contract](plugin-lifecycle-plc9b-contract.md).
- PLC9D1 refinement:
[Package GC Operator Projection Contract](plugin-lifecycle-plc9d1-contract.md)
adds an internal all-revision read model over existing retention evidence.
It grants no Store deletion or GC reservation authority.
- PLC9D2 refinement:
[Dark Package GC Reservation Contract](plugin-lifecycle-plc9d2-contract.md)
adds a durable, opt-in reference-writer fence. It is not composed by a
Product and grants no Store deletion authority.
- PLC9B1 refinement: the dark internal Owner Kernel now supplies versioned
inert records, classification, journal CAS, retry/cancel/status, and disabled
refusal. It has no production composition or artifact capability; all
Expand Down Expand Up @@ -385,6 +393,7 @@ implemented.
| Instance runtime | `src/loushang/harness/plugin_management/instance_runtime.py::PluginInstanceRuntimeLedger` | Durable Instance activation, lease-family, drain, revocation, and retirement state | Retain; do not replace with Worker/process state |
| Security retirement acceptance | `src/loushang/harness/plugin_management/security_acceptance.py::PluginInstanceSecurityRetirementJournal` | Durable acceptance evidence for security retirement | Retain; keep distinct from graceful retirement and generic management auth |
| Package retention and cleanup | `src/loushang/harness/plugin_management/package_lifecycle.py::PluginPackageLifecycleLedger` | Durable pins, cleanup leases/attempts/repair decisions, recovery barrier, retention snapshots, and GC candidates | Retain as lifecycle evidence; PLC9D must add deletion execution/result without weakening candidate recheck |
| Dark GC reservation and reference fence | `src/loushang/harness/plugin_management/package_gc_reservation.py::PluginPackageGcReservationJournal` and `src/loushang/harness/plugin_management/gc_fence.py::PluginPackageGcReferenceGatePort` | PLC9D2 journals exact reservation/cancellation, replays active fences, and guards opt-in desired/Instance/Package reference writers in one lock order | Retain dark; D3 needs Product-wide writer binding and downgrade exclusion before Store-owned rooted deletion; a reservation alone is not authorization |
| Coding Product composition | `src/loushang/coding/_plugin_lifecycle.py::CodingPluginLifecycle` | Product adapter composes the generic ledgers under one workspace identity and coordination lock | Retain as an outer Product adapter until common application ports replace Product-specific call sites; it must not become a second generic owner |
| Management application command adapter | `src/loushang/harness/plugin_management/application.py::PluginManagementCommandApplication` | A1-1 preserves correlation around the durable operation identity and delegates every mutation to `PluginManagementService` | Retain as the transport-neutral command boundary; transports cannot import the service or desired-state ledger directly |
| Management query projector | `src/loushang/harness/plugin_management/application.py::PluginManagementReadModelProjector` | A1-1 joins independently revisioned desired, operation, migration, Source, Instance, Package, and retirement snapshots without persisting another clock | Retain as the common read boundary; optional owners remain explicitly unsupported/unknown and forward/reverse skew remains observable |
Expand Down Expand Up @@ -835,8 +844,9 @@ publication outside those exact canaries.

| Current seam | Exact source owner or symbol | Current fact | PLC9 disposition and gate |
| --- | --- | --- | --- |
| Cleanup attempts and repair | `src/loushang/harness/plugin_management/package_lifecycle.py::PluginPackageLifecycleLedger` | Derives `pending`, `retryable_failure`, `terminal_failure`, `retry_permitted`, `succeeded`, and `safe_abandoned` from durable attempts/decisions | Retain; PLC9D adds operator projection and exact deletion execution without releasing debt implicitly |
| GC candidate | `src/loushang/harness/plugin_management/package_lifecycle.py::PluginPackageGcCandidateV1` | Binds desired, Instance, package-journal, and recovery-barrier revisions | Retain and recheck immediately before exact revision deletion; desired absence alone is insufficient |
| Cleanup attempts and repair | `src/loushang/harness/plugin_management/package_lifecycle.py::PluginPackageLifecycleLedger` | Derives `pending`, `retryable_failure`, `terminal_failure`, `retry_permitted`, `succeeded`, and `safe_abandoned` from durable attempts/decisions | Retain; PLC9D1 projects this evidence, while later deletion execution must not release debt implicitly |
| Package GC operator projection | `src/loushang/harness/plugin_management/package_gc.py::PluginPackageGcReadModel` | PLC9D1 projects every known revision, exact candidate or blocker codes, and durable cleanup/repair state without a mutation port | Retain as internal read-only evidence; future executable GC must add an exclusive reservation, Store-owned rooted deletion, and a durable result/debt receipt |
| GC candidate | `src/loushang/harness/plugin_management/package_lifecycle.py::PluginPackageGcCandidateV1` | Binds desired, Instance, package-journal, and recovery-barrier revisions | Retain; later executable GC must reserve against new references and recheck under the owner fence before exact revision deletion; desired absence alone is insufficient |
| Coding private roots | `src/loushang/coding/_plugin_lifecycle.py::CodingPluginLifecycleStateLayout` | Separates private lifecycle state and package data bases and prepares private directory permissions | Retain path containment; path ownership is not deletion authorization |
| Continuity deletion authorization | `src/loushang/harness/plugin_management/continuity_mutation.py::PluginContinuityDeletionAuthority` | Serializes one exact deletion, durably authorizes it, and settles terminal receipt/cancel evidence; it does not perform the source mutation | Retain as Product authorization/settlement precedent; never elevate it into a generic destructive executor |
| Continuity destructive commit | `src/loushang/harness/continuity/mutation.py::AuthorizedContinuityDeletionLease._commit_complete_and_release` over the source-owned `PreparedContinuityDeletion.commit` port, prepared by `src/loushang/harness/continuity/plugin_provider.py::PluginContinuityProvider._prepare_delete` | Calls the source/data-domain candidate commit first, validates its receipt, then asks the Product authority to settle | Retain the plan -> authorization -> source commit -> receipt settlement order for any future domain deletion contract |
Expand Down Expand Up @@ -879,7 +889,8 @@ PLC9A1 contract:
domain generation publication, and recovery/rollback composition; C5.0
documents and guards these absences but implements none of them;
- `remote_service` topology contract and client;
- executable artifact-GC owner/receipt;
- executable artifact-GC owner and Store result/debt receipt (PLC9D1/D2 add
only the operator projection and dark reservation/fence mechanics);
- generic Plugin-private data deletion command/receipt; and
- correlated backup-retention projection.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# PLC9D1 Package GC Operator Projection Contract

## Status And Scope

- Tracking: PLC9 issue `#509`; this is a narrow implementation candidate, not
PLC9D or PLC9 completion evidence.
- Owner: `PluginPackageLifecycleLedger` remains the sole retention-evidence
owner. `PluginPackageGcReadModel` is a read-only internal projection.
- Effect: no artifact deletion, GC lease/reservation, Store mutation, private
data deletion, backup-expiry claim, or new management command.

PLC9D1 makes existing durable retention and cleanup evidence inspectable for
**every known Package revision**, including a revision present only in history.
The existing management Installation view is keyed by current Installations;
it cannot by itself list an orphaned revision that might be GC eligible.

## Operator Query

`PluginPackageGcReadModel.snapshot()` captures one
`PluginPackageLifecycleSnapshotV1` and returns a versioned projection sorted by
exact Package revision. Each row carries the exact revision, zero or more
blocker codes, an optional GC candidate, and that revision's cleanup task
states. Cleanup summaries expose attempt count, last result code, retry time,
last repair action, and whether the lease is still open; they do not invent an
operator repair decision.

The blocker codes are `startup_recovery`, `desired_installation`,
`nonretired_instance`, `runtime_family`, `retention_pin`, `cleanup_lease`, and
`terminal_cleanup_failure`. The candidate is present exactly when the existing
retention owner reports no blocker. Its identity binds the desired, Instance,
Package journal, and recovery-barrier revisions. It proves retention eligibility,
not that a published Store tree currently exists. A terminal cleanup failure
retains a lease and stays blocked until the existing durable repair/attempt
sequence reaches success or an explicit `safe_abandon` decision.

The operator projection grants no deletion authority. A later executable GC
slice must first add a durable exclusive reservation that prevents new
references during deletion, bind the exact published Store tree and native
identity, recheck the candidate under the appropriate Product/owner fence,
perform rooted deletion through the Store owner, and durably settle success or
retryable debt. Calling `recheck_gc_candidate()` alone cannot close the race
between a read and physical deletion.

## Caller Inventory And Verification

| Seam | Before | PLC9D1 |
| --- | --- | --- |
| Package lifecycle ledger | retention snapshots, candidate generation/recheck, cleanup attempts and repair evidence | unchanged authority and journal format |
| Internal GC operator | no all-revision projection | `plugin_management.package_gc.PluginPackageGcReadModel` consumes only `snapshot()`; no command or Store dependency |
| Coding/CLI/RPC/UI | no artifact GC route | unchanged; no new Product or transport caller |

The focused durable-ledger regression covers recovery gating, an exact
candidate, stale candidate rejection after a new pin, visible terminal cleanup
debt, and explicit safe abandonment. Static checks keep the read model free of
deletion or Store imports. No physical deletion or crash-restart GC claim is
made by this slice.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# PLC9D2 Dark Package GC Reservation Contract

## Status And Boundary

- Tracking: PLC9 `#509`. This is a local implementation candidate, not an
accepted executable-GC or PLC9D completion claim.
- Owner: `PluginPackageGcReservationJournal` durably owns only reservation and
cancellation evidence. The Package lifecycle ledger still owns retention
candidates; Desired State and Instance Runtime still own their references.
- Activation: dark. No Coding/Product, CLI, RPC, UI, author SDK, Store, or
physical deletion route is added.

An executable GC owner cannot rely on a read-time candidate recheck: another
writer could select or pin the same revision before deletion. D2 introduces a
shared, explicitly injected guard over the three reference-writing ledgers.
It is a prerequisite for deletion, not deletion authorization.

## Reservation Protocol

An opt-in owner graph binds **one journal object** to the desired-state,
Instance-runtime, and Package-lifecycle ledgers. `reserve()` refuses an
unbound graph, rechecks the exact D1 candidate while holding the journal's
exclusive cross-process lock, then appends a versioned reservation. Repeating
the same operation rechecks the candidate and converges. Changed operation or
idempotency identities and another active reservation for the same revision
fail closed. A cancellation
requires the exact reservation ID, a new operation identity, and a reason; D2
permits cancellation because it never starts physical deletion.

Bound reference writers acquire the reservation guard **before** their owner
locks. The management service also takes it before its operation journal lock,
so its nested desired-state commit and recovery follow the same order. A
reentrant in-process guard avoids reacquiring the file lock in that nested
path. A reserved revision cannot be newly selected by desired-state commit or
update, activated or leased by the Instance runtime, or pinned by Package
retention. Unrelated revisions and reference release remain available.

The journal replays active reservations after restart, repairs only an
incomplete tail, and rejects a complete corrupt record. Bound writers fail
closed when the reservation journal cannot be reconstructed. The operator can
inspect active reservations separately from the D1 retention projection; an
ordinary D1 candidate remains a retention candidate, not a deletion receipt.

## Explicit Limits And Next Gate

An older or separately constructed writer that omits the injected guard can
still write the same desired-state journal. The regression freezes this
counterexample and proves that a bound Instance runtime refuses activation of
its newly selected revision. Consequently **D2 does not authorize deletion**.
Before D3, one Product composition must bind every supported writer and prove
a minimum-version/downgrade fence excludes legacy writers; D3 must recheck
desired, Instance, Package, and reservation evidence under that authority.

D3 must also bind the exact published Store tree/native identity, execute
rooted deletion through the Store owner, and persist a result or retryable
debt. Neither reservation cancellation nor Plugin remove may invoke private
data deletion or assert backup expiry.

| Seam | Old caller | D2 caller and authority |
| --- | --- | --- |
| Desired selection | management service or direct desired ledger | optional gate before operation/desired locks; same desired owner |
| Instance activation/family | runtime ledger | optional gate before operation/runtime locks; same Instance owner |
| Package pin | lifecycle ledger | optional gate before Package lock; same retention owner |
| Reservation | absent | internal `package_gc_reservation.py` journal only; no Product or Store caller |

Focused durable-ledger tests cover exact replay, cancellation, stale candidate,
new pin/selection refusal, management lock order, restart and partial-tail
repair, corrupt-log fail-closed behavior, unbound-graph refusal, and the legacy
writer counterexample. No native deletion or end-to-end GC test is claimed.
10 changes: 10 additions & 0 deletions src/loushang/harness/plugin_management/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@
PluginManagementOperationEventV1,
PluginManagementOperationResultV1,
)
from loushang.harness.plugin_management.package_gc import (
PLUGIN_PACKAGE_GC_OPERATOR_PROJECTION_VERSION,
PluginPackageGcOperatorProjectionV1,
PluginPackageGcOperatorRowV1,
PluginPackageGcReadModel,
)
from loushang.harness.plugin_management.package_lifecycle import (
PLUGIN_PACKAGE_GC_CANDIDATE_VERSION,
PluginCleanupTaskSnapshotV1,
Expand Down Expand Up @@ -237,6 +243,7 @@
"PLUGIN_MIGRATION_FENCE_VERSION",
"PLUGIN_PACKAGE_REVISION_REF_VERSION",
"PLUGIN_PACKAGE_GC_CANDIDATE_VERSION",
"PLUGIN_PACKAGE_GC_OPERATOR_PROJECTION_VERSION",
"PLUGIN_PACKAGE_LIFECYCLE_EVENT_VERSION",
"PLUGIN_PACKAGE_PIN_RELEASE_VERSION",
"PLUGIN_PACKAGE_PIN_VERSION",
Expand Down Expand Up @@ -329,6 +336,9 @@
"PluginMigrationFenceV1",
"PluginPackageRevisionRefV1",
"PluginPackageGcCandidateV1",
"PluginPackageGcOperatorProjectionV1",
"PluginPackageGcOperatorRowV1",
"PluginPackageGcReadModel",
"PluginPackageLifecycleError",
"PluginPackageLifecycleEventV1",
"PluginPackageLifecycleLedger",
Expand Down
23 changes: 23 additions & 0 deletions src/loushang/harness/plugin_management/gc_fence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Neutral reference-writer fence for a future executable Package GC owner."""

from __future__ import annotations

from contextlib import AbstractContextManager, nullcontext
from typing import Protocol

from loushang.harness.plugin_management.records import PluginPackageRevisionRefV1


class PluginPackageGcReferenceGatePort(Protocol):
def guard(
self,
) -> AbstractContextManager[frozenset[PluginPackageRevisionRefV1]]: ...


def gc_reference_guard(
gate: PluginPackageGcReferenceGatePort | None,
) -> AbstractContextManager[frozenset[PluginPackageRevisionRefV1]]:
return nullcontext(frozenset()) if gate is None else gate.guard()


__all__ = ["PluginPackageGcReferenceGatePort", "gc_reference_guard"]
Loading
Loading