diff --git a/docs/architecture/rfcs/README.md b/docs/architecture/rfcs/README.md index 9cb78e57c..6e680a2c8 100644 --- a/docs/architecture/rfcs/README.md +++ b/docs/architecture/rfcs/README.md @@ -136,6 +136,15 @@ changes. - **Current boundary:** Milestones, blocking guards, and legal transitions are specified as a read-only projection; no canonical lifecycle projection has shipped. +- [Goal Instance Identity and Orphan Recovery v0](goal-instance-identity-and-orphan-recovery-v0.md) + ([中文版](goal-instance-identity-and-orphan-recovery-v0.zh-CN.md)) + - **RFC status:** Draft. + - **Delivery on `main`:** Identity/recovery proposal; the guided orphan fence + and M0 registry codec shipped separately in #4808 and #4917. + - **Current boundary:** Defines R5 lifetime fencing for R2/R3 consumers, + incompatible activation beyond codec-only v1, typed commit/recovery ownership, + exact legacy cleanup and product readback. Instance minting, activation and + resolution remain unshipped; R6 and D1–D3 keep their own qualification gates. ## Planning, Research, And Adaptive Intelligence diff --git a/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.md b/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.md new file mode 100644 index 000000000..8ef205050 --- /dev/null +++ b/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.md @@ -0,0 +1,798 @@ +# Goal Instance Identity and Orphan Recovery (v0) + +- **RFC status:** Draft +- **Delivery maturity:** Identity/recovery proposal; codec prerequisite shipped in #4917 +- **Authors / owners:** LoopX contributors +- **Created:** 2026-09-23 +- **Last normative revision:** 2026-09-23 +- **Implementation baseline:** `23edcb19c70394480e3a9ebe8a960f5a320c5342` +- **Related contracts:** [Issue #4801](https://github.com/loopx-project/loopx/issues/4801), [orphan fence slice #4808](https://github.com/loopx-project/loopx/pull/4808) +- **Language mirror:** [Chinese semantic mirror](goal-instance-identity-and-orphan-recovery-v0.zh-CN.md) + +## Document map and maintenance contract + +Sections 1-10 are the durable design and acceptance contract. Section 11 is +the normative delivery plan. Section 12 contains unresolved decisions; +recommendations there are not approval. Appendices are non-normative evidence +and decision history. + +The English and Chinese documents are semantic mirrors. Any normative change +must update both in the same pull request. + +--- + +## 1. Decision summary + +This RFC makes five linked decisions: + +1. `goal_id` remains the human-readable alias. A project-registry-owned, + immutable `goal_instance_id` identifies one Goal lifetime. +2. Every durable host, session, channel, automation, Turn, and quota binding + carries the exact pair. The source project registry, not a global + projection or a binding store, authorizes execution. +3. Instance identity needs an incompatible activation format plus commit-time + fencing. The shipped M0 codec is not an identity-enforcement protocol; + rejecting one old decoder alone does not exclude every old effect path. +4. Orphan recovery is one preview-first, journaled lifecycle operation with + explicit per-candidate `adopt`, `migrate`, `archive`, or `delete` + dispositions. It never guesses or merges. +5. Activation is explicit and registry-wide. Within an activated registry, + unstamped bindings are read-only. Non-activated projects retain their legacy + behavior and have no ABA guarantee; a fleet-wide default change is separate. + +The existing state-file locations, host-specific runtime directory layouts, +provider revision domains, leases, and global registry role do not become new +Goal authorities. This RFC does not approve automatic cleanup of every +external provider, automatic candidate selection, or direct mutation of +orphan state by ordinary bootstrap. + +## 2. Problem and motivation + +Today one `goal_id` serves as both a display name and a durable identity. A +Goal can disappear from the project registry while active-state files, +sessions, host bindings, Goal Channels, and heartbeat automations survive. +Creating a new Goal with the same `goal_id` can make those old records appear +current. This is an ABA failure: + +1. Goal A named `release` creates durable attachments. +2. Goal A is removed, but some attachments remain. +3. Goal B is created with the same name. +4. An attachment that only stores `release` cannot distinguish A from B. + +The fence shipped by #4808 correctly blocks guided bootstrap when a registry +entry is absent but a project state candidate remains. It does not distinguish +Goal lifetimes, resolve the orphan, or invalidate attachments after +delete-and-recreate. + +No individual host or session store can solve this locally. If a host invents +identity, it becomes a second Goal authority. If deletion synchronously knows +every provider, it becomes a cross-owner distributed cleanup transaction. +LoopX needs one source-owned lifetime identity, with admission and commit fenced +against retirement. An identity match is necessary, not an authorization grant. + +### Invariants + +1. The source project registry is the only authority that creates or resolves + a Goal instance. +2. A new Goal after terminal removal receives a fresh random instance ID, + including when its `goal_id`, path, objective, or configuration is reused. +3. Updating configuration, reconnecting a provider, renewing a lease, opening + a Turn, or rewriting a binding never rotates the instance ID. +4. A stale, missing, malformed, or legacy binding cannot authorize execution + for an instance-aware Goal. +5. A stale global projection may route a lookup but cannot authorize a write. +6. Before source safety is complete, a resolution journal blocks ordinary + activation/mutation of that Goal, including after registry publication. Exact + recovery remains available; completed-source projection retry is separate. +7. Resolution leaves exactly one selected writable state authority or leaves + the Goal absent with no writable candidate. +8. Destructive resolution requires a verified targeted backup, exact plan + digest, explicit Goal confirmation, and post-write readback. +9. Crash retry reuses prepared identity and owner idempotency keys; every plan, + including archive/delete-only plans, journals before its first mutation. +10. A released old writer cannot silently open or rewrite an instance-aware + project registry. + +## 3. Scope and non-goals + +### In scope + +- Project-registry Goal lifetime identity and exact binding comparison. +- A registry-wide compatibility boundary for old readers and writers. +- Instance propagation through first-party durable binding owners. +- Final checks at activation, resume, delivery, state mutation, and quota + authorization. +- Preview, backup, apply, resume, and rollback for project-local orphan state. +- Diagnostics for orphan state, legacy identity, stale bindings, stale global + projections, incomplete journals, and manual cleanup. +- A staged migration that separates observation from enforcement. + +### Non-goals + +- Changing the human-facing `goal_id` or placing instance IDs in state paths. +- Migrating low-level Codex, Pi, OpenCode, or other host runtime directories. +- Merging multiple active-state candidates. +- Treating timestamps, paths, content equality, global projections, or host + records as identity evidence. +- Making physical deletion from an external provider the safety boundary. +- Introducing a public generic binding-provider framework. +- Changing Claim, Lease, authority revision, Turn, or provider idempotency + semantics beyond binding them to a Goal instance. +- Allowing an old binary to downgrade or edit a strict registry. + +### Roadmap placement and contract cooperation + +The [overall roadmap](loopx-overall-roadmap-v0.md) measures accepted outcomes, +human attention and recovery. This RFC contributes a bounded R5 lifetime fence +that R2/R3 can consume; it does not complete R1–R7 or promote a provider. + +| Existing owner / contract | Contribution and boundary | +| --- | --- | +| [TS convergence](typescript-control-plane-migration-v0.md), R5 | Put match, admission, retirement and recovery transitions in the typed kernel; keep Python codec, filesystem and host adapters. Do not add a second Python policy engine or per-field RPCs. | +| [Shared authority](shared-goal-authority-state-provider-v0.md), D1–D3 / R6 | Bind the existing head, operation receipts and authorization projection to lifetime. Provider incarnation, revision, lease epoch and Goal instance remain different domains; no default cutover or new store. | +| [Shared alignment](shared-goal-alignment-and-governed-amendment-v0.md), R4 | Identity is not intent, permission or work revision. Preserve `intent_basis` source-facts semantics; amendments remain with their existing owner. | +| [Semantic handoff](capable-manager-semantic-handoff-v0.md), [collaboration](agent-im-openviking-collaboration-v0.md), R2/R3 | Bind request, adoption, result and original-conversation return to the instance. Late A results cannot settle B; transport success and registration do not prove acceptance. | +| [Single-owner daemon](single-owner-local-daemon-v0.md) | Reuse profile quiescence and actual executor drain; a PID, package version or stopped heartbeat is not proof that old writers cannot return. Daemon identity grants no Goal authority. | + +[#4808](https://github.com/loopx-project/loopx/pull/4808) owns the shipped guided +fence; [#4912](https://github.com/loopx-project/loopx/pull/4912) proposes diagnosis; +[#4915](https://github.com/loopx-project/loopx/pull/4915) proposes host-neutral +paths. Reuse those owners without treating open PRs as shipped. Canonical Todos +own execution status; this document owns acceptance and dependency boundaries. + +## 4. Current-system contract + +At the named main baseline, [#4917](https://github.com/loopx-project/loopx/pull/4917) +has shipped `control_plane/projects/registry_codec.py`, format-preserving +transactions and a production I/O census. It reads legacy objects and strict +`loopx_project_registry_envelope_v1` arrays and accepts writer protocol +`goal_instance_v1`. It does **not** mint, compare or enforce Goal instances. +Therefore a codec-capable but identity-unaware binary is part of the old-writer +population; the original object-root-only argument is insufficient. + +The project registry owns configuration and source identity; the global registry +is a host-local route projection. Canonical coordination already has typed TS +transaction owners, independent provider incarnations and receipts. +`coordination/authority_source.ts` and `authority_source_capture.py` witness +registry facts, explicitly without a transaction spanning registry and provider. +Their digest recheck must not be advertised as retirement serialization. + +`orphaned_goal_state.py` discovers current/legacy candidates and fences guided +bootstrap. `state_backup.py` owns backup/verification. Bootstrap updates, Goal +deletion, sessions, channels and quota remain distinct callers. Their current +alias/revision bindings do not prove lifetime continuity. No instance activation +or orphan mutation command described below has shipped at this baseline. + +## 5. Proposed architecture + +### 5.1 Ownership and authority + +Placement: existing Goal/project lifecycle in the built-in control plane; no +new public capability id, provider id or extension distribution. File, SQLite +and PostgreSQL keep their existing provider contracts and qualification gates. + +The source authority alone allocates and publishes `goal_instance_id`. A prepared +resolution may reserve one ID durably; it has no execution authority until the +lifecycle commit. The global registry only copies it. Pure match and lifecycle +transitions belong in the nearest typed TS Goal boundary, consuming source facts +through one coarse transaction. Python adapts CLI, codec, filesystem, backup and +host effects, and executes typed plans without duplicating transition rules. + +The project registry remains the local source of identity. For a future R6 +service profile, its versioned authorization projection and instance are admitted +by the authenticated authority service. Remote workers do not open a laptop's +path, allocate from a local clone or use a global projection as authority. Until +that path is qualified, instance enforcement is explicitly local-profile only. + +No binding, path, timestamp or content hash may infer identity. Existing grant, +claim, lease, quota, stop and intent checks still apply after an exact match. + +### 5.2 Goal identity model + +`goal_instance_id` is opaque, immutable, public-safe, and not a credential. Its +canonical form is `ginst_` followed by 32 lowercase hexadecimal characters, +providing 128 random bits. + +The typed contract distinguishes `GoalRef { goal_id, goal_instance_id }` from +`LegacyGoalRef { goal_id }`. `BindingMatch` is an exhaustive result with +`current`, `legacy_read_only`, `missing_goal_instance_id`, +`goal_instance_mismatch`, `goal_not_registered`, `resolution_in_progress`, and +`invalid` variants. These are kernel decisions; generated/decoded Python +representations are wire adapters, not a second matching implementation. + +An instance-aware Goal record contains: + +```json +{ + "id": "release-2026", + "goal_instance_id": "ginst_6ff38d6d143d4b72a6ff894b95f067c1", + "status": "active", + "repo": "/project", + "state_file": ".codex/goals/release-2026/ACTIVE_GOAL_STATE.md" +} +``` + +The absolute path above is illustrative, not public evidence. Stored paths +continue to follow existing privacy and portability rules. + +Every durable attachment stores: + +```json +{ + "goal_id": "release-2026", + "goal_instance_id": "ginst_6ff38d6d143d4b72a6ff894b95f067c1" +} +``` + +The matching matrix is exhaustive: + +| Registry state | Binding state | Read-only inspection | Execution or mutation | +| --- | --- | ---: | ---: | +| Exact instance | Exact same instance | allow | allow under commit fence and existing grants | +| Exact instance | Missing instance | allow with finding | reject | +| Exact instance | Different instance | allow with finding | reject | +| Legacy Goal | Legacy binding | allow with finding | reject in activated registry | +| Legacy Goal | Instance binding | allow with finding | reject | +| Goal absent | Any binding | allow as historical | reject | +| Resolution source safety incomplete | Any binding | allow with finding | reject ordinary execution; exact recovery only | +| Invalid record | Any | fail closed | reject | + +Observation and non-activated projects retain legacy execution without an ABA +guarantee. An activated project has no `legacy_compatible` execution branch. +The exact-match row also requires active lifecycle state and existing grants. + +### 5.3 Identity independence + +The following revision domains remain independent: + +| Event or identity | Rotates `goal_instance_id`? | Reason | +| --- | ---: | --- | +| Fresh creation after terminal retirement | yes | Starts a new Goal lifetime | +| Orphan `adopt` or `migrate` that creates a Goal | yes | Establishes a new authority | +| Forced bootstrap or configuration update | no | Changes configuration within one lifetime | +| Provider reconnect or provider revision | no | Changes an attachment or backend generation | +| Authority revision or migration receipt | no | Versions authority state, not Goal existence | +| Claim or Lease epoch | no | Coordinates work inside one lifetime | +| New Turn or session | no | Creates execution lineage within one lifetime | +| Binding revision | no | Versions one owner's attachment record | +| Global registry synchronization | no | Copies a source-owned identity | +| Rollback to orphaned bytes | never restores one | Restored bytes have no execution authority | + +`--force` cannot replace an instance ID. Imports into a new project authority +mint a new identity rather than copying an identity owned by another registry. +Restoring an authority into a replacement deployment follows the existing +incarnation/restore contract; copying a registry backup must not resurrect stale +writers or create a second writable authority. + +### 5.4 Activation format and mixed-writer gate + +Identity activates per project registry, not per Goal. Reuse the M0 codec and +I/O census; do not build another registry loader. M0's v1 format/protocol remains +codec-only. Proposed activation uses an incompatible header schema +`loopx_project_registry_envelope_v2` and `minimum_writer_protocol: goal_instance_v2` +with the existing two-element `[header, payload]` shape and payload digest. +The final identifiers require the M2 compatibility matrix before release. + +Changing only the minimum writer string is insufficient: M0 readers can decode +an unknown writer protocol, and a state mutation may never rewrite the registry. +The new schema must reject identity-unaware **read-to-effect** paths as well as +registry writes. Old global-object routes and already-loaded processes need +separate exclusion; no header can retrospectively constrain cached code. + +```console +loopx activate-goal-instance-identity --project . --format json +loopx activate-goal-instance-identity --project . --execute \ + --plan-revision sha256:31ab... --confirm-registry-path .loopx/registry.json +``` + +Activation must: + +1. Stop new admissions and durably quiesce all Goals in the registry through + existing lifecycle/host owners; drain or explicitly reconcile admitted effects. + Inventory direct CLI, attached/managed hosts, global routes, automation and + provider writers. A disconnected or unaccounted writer blocks activation. +2. Verify a targeted backup, host protocol support and the exact preview digest. + A capability advertisement is evidence to inspect, not write authority. +3. Prepare a durable activation journal and one ID per current Goal, then publish + the incompatible envelope atomically under the registry transaction. The + same IDs survive retry; no ID is minted in ordinary M0/M1 execution. +4. Bind owner state through explicit reviewed migration/reconnection. Missing + stamps never inherit authority by alias. Reopen admission only after required + commit fences and readbacks are installed; pending projection delivery alone + must not authorize or duplicate effects. + +Before publication, abort leaves the legacy object. After publication, only +forward repair is legal; never unwrap for an old writer. Fresh projects may +opt into the complete profile. Installation or codec availability does not +activate it. Fleet-wide legacy shutdown is a separate release decision. + +Test all compatibility classes: object-only packages, codec-only M0/M1 packages, +and enforcement packages; test direct source and global routes, warm processes, +state writes, resume, quota and delivery. Require unchanged business state and +receipts on rejection, not only unchanged registry bytes. Local drain plus +unsupported source decoding must be proved for the supported package range; +where an old route bypasses both, activation remains unavailable until an +existing owner can mechanically exclude it. Same-principal deliberate filesystem +tampering is outside this mixed-version safety model. + +### 5.5 Binding owners and commit fencing + +| Existing owner | Instance-bound state and decisive boundary | +| --- | --- | +| Registry / attached host / Chat | Session/thread and activation binding; route selection, resume and executor admission | +| Turn / scheduler / quota | Work selection, lineage, settlement, debit/void and replay identity | +| Todo / coordination / lease | Head/provider binding, work mutation, claim/renew/reclaim and receipts | +| Handoff / inbox / outbox | Request, receiver adoption, accepted artifact, result delivery and historical readback | +| Goal Channel / Lark / heartbeat | Connection, delayed inbound/outbound event and wake admission | +| Pi / OpenCode / other first-party hosts | Host action, execution binding and quota/state writeback | + +Maintain one private exhaustive owner inventory with stable locator, revision, +content digest, observed typed reference and cleanup support. Sorted inventory +and owner revisions enter plans. Reuse the M0 I/O census for registry access; +binding/effect coverage is a distinct inventory, not proof from the writer count. + +Resolve route → validate source/authorized projection → compare exact reference +and lifecycle → check existing permissions → admit the operation → commit under +the owning fence. A last-moment read alone leaves a check/use race: +A passes the check, deletion/recreation publishes B, then A writes the reused +path or spends quota. The contract requires an explicit linearization boundary: + +- Locally, lifecycle retirement and each short protected commit share the same + instance admission guard with documented lock order. Do not hold a registry + lock across a model call, network request or user validation. +- In canonical transactions, propagate the original source witness and instance + to the TS owner; check the current instance at the commit boundary under the + lifecycle guard. Existing CAS and lease fences remain required. A witness + hash or Goal ID alone is not this guard. +- Retirement closes admission first, drains admitted commits and reconciles + pending external effects in their existing journals, then publishes retirement. + Uncertain external effects prevent declaring retirement/reuse complete. An + external provider without fencing is not retroactively cancellable. +- A shared service must serialize lifetime and mutation admission server-side, + with tenant/actor checks and restore incarnation. A local file lock does not + qualify R6; no cross-ledger atomicity is implied. + +Scope operation/replay keys, canonical state selection and late results to the +instance even when physical paths retain the alias. Old receipts remain readable +as A's history, but cannot grant B a lease, satisfy B's acceptance or debit B. +Do not silently relabel historical receipts. Selected provider state must either +be newly initialized for B or explicitly imported through its reviewed lifecycle. +A stale writer that pauses after validation and resumes after recreation must +produce no B-side write, delivery, debit or ownership change. + +### 5.6 Orphan resolution command + +The command is a dry run unless `--execute` is present. Every discovered +candidate requires one explicit disposition: + +```console +loopx resolve-orphaned-goal-state \ + --project . \ + --goal-id release-2026 \ + --disposition .codex/goals/release-2026/ACTIVE_GOAL_STATE.md=adopt \ + --disposition .claude/goals/release-2026/ACTIVE_GOAL_STATE.md=archive \ + --objective "Ship the release" \ + --domain engineering \ + --role owner \ + --format json +``` + +- `adopt` retains selected state content at its canonical location under a fresh + Goal instance; it does not revive former claims, grants or execution bindings. +- `migrate` moves one selected candidate through the canonical state-path + helper, under the same human Goal ID, then registers a fresh instance. +- `archive` moves a candidate outside every active Goal root. +- `delete` removes a candidate only after targeted backup verification. + +At most one candidate may be `adopt` or `migrate`. All other candidates must be +`archive` or `delete`. An archive/delete-only plan leaves the Goal absent. +There is no implicit winner, content merge, arbitrary destination path, or +renamed target Goal. The first resolution profile is project-local legacy file +state. If a candidate routes to promoted canonical/provider state, fail closed +with its existing authority migration/import route; moving Markdown cannot adopt +a File/SQLite/PostgreSQL head. Any later provider support must preserve source +selection, incarnation, historical receipts and D1–D3 gates. + +Preview validates before computing its digest: + +- the project and registry resolve inside their declared authority boundary; +- each candidate is a readable regular file inside an allowed Goal root; +- no candidate or parent is a symlink; +- resolved paths do not escape, alias, or duplicate one another; +- the requested Goal is absent from the project registry; +- no other resolution exists for the Goal; +- every candidate tree digest and binding-owner revision is stable; +- the migration destination is selected by the canonical path helper; +- the Goal creation specification is complete when required. + +Execution must bind the preview and human intent: + +```console +loopx resolve-orphaned-goal-state \ + --project . \ + --goal-id release-2026 \ + --disposition .codex/goals/release-2026/ACTIVE_GOAL_STATE.md=adopt \ + --disposition .claude/goals/release-2026/ACTIVE_GOAL_STATE.md=archive \ + --objective "Ship the release" \ + --domain engineering \ + --role owner \ + --execute \ + --plan-revision sha256:82e3... \ + --confirm-goal-id release-2026 +``` + +### 5.7 Resolution transaction and journal + +Use a typed lifecycle with `prepared`, `applying`, `source_committed`, +`complete`, `rollback_prepared`, and `rolled_back` states. The journal lives under +`.loopx/lifecycle/orphaned-goal-state//.json`; it is +recovery metadata, not another registry. TS owns transitions and allowed effects; +existing Python/host adapters execute them and return observations. + +1. Acquire lifecycle/registry/affected-owner guards in documented order. Exact + completed replay returns its historical receipt; it does not reactivate a Goal. +2. Before preparation, rebuild and compare the full registry, candidate-tree, + inventory, creation-specification and plan digests; mismatch has no effects. +3. Verify targeted backup, then durably prepare **every** plan before any move or + deletion, including archive/delete-only plans. Reserve a new ID only if creating + a Goal; retain candidate preimages, planned postimages and owner operation keys. +4. Apply each disposition under its guard. Persist intent before the effect and + reconcile actual pre/post state after crash-before-ack. Neither state matching + requires replaying a deletion; neither matching state permits guessing. +5. Publish the new Goal only once selected content is canonical and competitors + are inactive. Resume a prepared plan from its recorded phase; do not demand + the original preview's pre-mutation digests after successful partial work. +6. Obtain required local invalidation receipts, verify the source and safety + fences, and commit the lifecycle. Complete registry-wide activation first when + other legacy Goals exist. Other Goals are outside the resolution guard's scope. +7. Deliver the global projection and operator result through existing retry + owners. Projection failure is typed `pending_sync`, not a second authority or + permission to repeat the source commit. + +Before source safety is established, the journal blocks ordinary activation and +mutation for this Goal, even if its record is published. Only an exact +resolution/resume operation may perform its declared recovery effects. After +source commit plus required invalidation, projection lag may remain visible +without holding unrelated Goals or source-authorized local work indefinitely. +Every event-driven wake is advisory; source checks still decide admission. + +The receipt records plan/resolution/terminal-journal digests; candidate pre/post +digests; exact source Goal reference; backup manifest verification; each owner +observation, revision, operation and readback; local removal receipts; and typed +projection/manual-cleanup findings. Exclude raw private content. Recovery must +remain available while new lifecycle admissions are disabled. + +### 5.8 Logical revocation and legacy cleanup + +Exact instance mismatch plus the commit fence is the safety boundary; physical +external deletion is hygiene. Typed cleanup selectors distinguish: + +- `instance`: exact retired `(goal_id, goal_instance_id)`; +- `legacy_observation`: exact owner locator + observed revision + content digest, + admitted only while the lifecycle guard proves the Goal absent/inactive. + +A legacy orphan has no retired instance to invent. If its owner supports exact +conditional removal, revalidate the observation under the owner guard and remove +only that record. If the revision changed, stop/replan. If the provider cannot +prove exact removal, retain the now-inert record with `manual_cleanup_required`. +Never use Goal-ID-only bulk cleanup or stamp a legacy binding to make it deletable. + +Required local logical invalidation receipts gate source completion; optional +physical cleanup and external manual cleanup have separate retry state. A new +binding created by a different lifetime cannot match the legacy selector. Test +legacy and stamped orphans independently, including a revision race. + +### 5.9 Rollback + +Rollback is also preview-first and digest-bound: + +```console +loopx rollback-orphaned-goal-resolution \ + --project . \ + --goal-id release-2026 \ + --resolution-id ores_... \ + --format json + +loopx rollback-orphaned-goal-resolution \ + --project . \ + --goal-id release-2026 \ + --resolution-id ores_... \ + --execute \ + --plan-revision sha256:... \ + --confirm-goal-id release-2026 +``` + +Rollback may restore verified bytes only to an orphaned, fenced state. It +never restores a retired Goal instance ID or binding authority. It is rejected +after any post-resolution Goal write, Todo mutation, quota spend, session or +channel creation, automation installation, or binding revision. Rollback itself +journals before its first mutation and uses the same retirement/commit guard, +so checking eligibility cannot race a new business write. A successful +rollback removes the newly created Goal through the lifecycle owner, restores +candidate bytes, verifies their digests, and makes diagnosis unhealthy with +`orphaned_goal_state`. + +If rollback is no longer legal, recovery is a forward operation: finish the +journal, retire the new Goal through normal lifecycle, and create another +resolution plan. + +### 5.10 Bounded refactor and delivery owners + +| Existing boundary | Change and deletion requirement | +| --- | --- | +| TS Goal lifecycle + `coordination/authority_source.ts` | One typed reference/match/admission/recovery rule set; extend whole transactions, remove duplicate host decisions. The witness is evidence, not a cross-store lock. | +| `control_plane/projects/registry_codec.py` | Extend shipped codec/protocol checks and existing transaction; retain v1 compatibility without granting v2 enforcement. | +| `bootstrap.py` / Goal deletion | Use the lifecycle transaction; preserve identity on config updates and fence retirement/recreation. | +| `orphaned_goal_state.py` / `state_backup.py` | Keep discovery and backup effects; a thin resolution adapter drives the typed plan instead of a new Python state machine. | +| Existing binding / delivery owners | Persist exact refs and commit fences; retain owner-local journals and idempotency instead of a central provider cleanup framework. | +| Diagnosis / CLI / Chat / frontend / Lark | Render the same lifecycle result and recovery action, with audience filtering; no second detector or independent repair state. | + +Characterize ordinary bootstrap/update/deletion and default-off paths before +refactoring. Ship a whole local retirement/recreation path with actual consumers; +do not add unused Goal abstractions, per-field RPCs or a new scheduler. Larger +cross-host and provider-import work stays with R6/D1–D3, not a prerequisite for +the local legacy-file recovery outcome. + +## 6. Alternatives and design choices + +### Generation counter + +A counter under `goal_id` needs a permanent tombstone after deletion or a +second global allocator. Random source-registry identity avoids both and does +not require ordered IDs. + +### Instance ID in directory paths + +Instance-segment paths separate files but do not identify sessions, channels, +automations, or quota calls. They also force every human-facing path consumer +through a broad migration. Record-level identity closes the reported ABA +failure without that unrelated layout change. + +### Delete every binding during Goal deletion + +Cross-provider deletion is not atomic and would make Goal lifecycle know every +storage implementation. Exact matching makes stale records inert. Owner-local +cleanup then improves hygiene without becoming authority. + +### Add a capability field to the current registry object + +Released old loaders accept unknown object fields and schema values. They +could silently rewrite an instance-aware registry. This fails the mixed-writer +invariant. + +### A new registry path with a redirect object + +Released old binaries could ignore the redirect, continue using the old path, +and create split authority. Keeping one path with an incompatible root shape +causes a deterministic decode failure instead. + +### Automatic candidate selection or merge + +Path preference, modification time, and content equality do not prove +authority. Multiple candidates may represent divergent histories. The +operator must classify every candidate. + +### One module for discovery and mutation + +Combining the small read-side fence with backup, journaling, cleanup, rollback, +and registry publication would mix stable detection with a large transaction. +The sibling resolution owner preserves a deep plan/apply interface without +making the fence module a lifecycle service. + +## 7. Safety, privacy, and compatibility + +Identity and digests are public-safe metadata, not credentials. Backups, +candidate contents, session payloads, local absolute paths, provider handles, +and raw state remain private runtime data and must not enter public receipts, +logs, fixtures, or documentation. + +The format gate and effect-owner fences jointly exclude supported mixed writers. +Activation requires source/global/warm-process negative tests for object-only, +codec-only and enforcement binaries. Test helpers use isolated synthetic state; +never probe a live Goal to demonstrate rejection. + +Non-activated legacy projects retain existing behavior. Activated projects allow +legacy inspection, backup and migration previews but reject unstamped execution. +An observation finding cannot advertise ABA protection. Protocol names must +represent the full enforced contract, not merely a decoder's availability. + +An activated registry cannot be downgraded. Unsupported source schema/protocol +must fail before a business effect, including effects that do not edit registry +bytes. Recovery uses a current compatible release and never unwraps the payload. + +Within the activated profile, source unavailability, digest mismatch, malformed +envelope, missing instance, incomplete source safety or owner-inventory ambiguity +fails closed. +Global projection unavailability may delay visibility but cannot grant +execution. + +## 8. Migration and rollback + +Migration has two scopes: + +1. **Fresh opted-in project.** Create the activation envelope and mint the first Goal in + its committed registry transaction. +2. **Existing project.** Preview registry-wide activation, quiesce every Goal, + back up the registry and local bindings, stamp every current Goal with a new + instance, atomically write the strict envelope, and require explicit + reconnection of all attachments. + +No existing attachment is automatically blessed. There is no evidence that an +unstamped record belongs to the current lifetime rather than a prior same-name +Goal. + +The activation cutover point is the verified incompatible-envelope write; +admission remains closed until required owner fences/readbacks complete: + +- Before it, abort leaves the legacy object authoritative. +- After it, old binaries and unstamped bindings are unsupported and fail + closed. +- If later steps fail, current code resumes the activation journal. +- Returning to legacy execution is not rollback. + +Orphan archive/delete may run without identity activation because it creates no +Goal. Orphan adopt/migrate requires an already activated registry, a fresh activated +registry, or a journaled registry-wide activation admitted by the same plan. + +Before resolution preparation, abort changes nothing. After preparation, retry +the same plan. The separate rollback command is legal only before any +post-resolution business write and restores orphaned, non-executable bytes. + +## 9. Validation and acceptance + +| Claim | Test or evidence | Required result | Boundary / exclusions | +| --- | --- | --- | --- | +| Same-name recreation is fenced | Delete A, preserve every attachment, create B with same alias, exercise all owners | Every old path rejects before effect or quota | Does not require physical provider deletion | +| Updates preserve lifetime | Force-update config, reconnect provider, renew lease, start Turn | Same instance ID in registry and new records | Does not preserve binding revisions | +| Unstamped execution is read-only inside activated scope | Exercise status, backup, migration preview, resume, delivery, write, spend | Reads succeed with finding; ordinary business effects reject; explicit migration remains available | Non-activated legacy behavior is unchanged | +| Old writer is mechanically denied | Object-only, codec-only and enforcement packages; source/global/warm-process paths | Rejection before business effects, state/receipts unchanged | Decoder rejection alone is insufficient | +| Strict codec is exhaustive | Static writer inventory plus Python/TS conformance | No direct project-registry write bypass | Test helpers may use isolated fixtures | +| Source authority beats global projection | Make global entry stale and invoke every executable route | Source mismatch rejects | Global routing availability is separate | +| Preview is pure | Preview 1, 2, and 4 candidates | No file, registry, binding, or journal write | Read-only filesystem metadata access allowed | +| Plan binds full state | Change candidate, owner revision, registry, or Goal spec after preview | Apply rejects before backup or mutation | None | +| Path validation fails closed | Symlink, escape, duplicate path, non-regular or unreadable candidate | Preview/apply rejects | Canonical helper owns legal destination | +| Crash retry is idempotent | Every plan type; crash before/after each effect and journal acknowledgement | One prepared ID when applicable, exact reconciliation, no duplicate effect | Archive/delete-only is journaled too | +| Published-but-unsafe stays fenced | Crash after publication before required invalidation/source readback | Activation, mutation and quota reject; exact recovery works | Completed source with only projection lag is separately pending-sync | +| Cleanup failure is safe | Stamped and legacy bindings; changed legacy revision; local invalidation failure | Exact cleanup only; inert external records; required local invalidation retries | No invented retired identity | +| Receipt is complete | Compare receipt to owner table and all readbacks | Every owner/candidate/registry/global/backup/fence row present | Raw private contents excluded | +| Backup precedes destruction | Inject failure before and after backup verification | No destruction before proof; verified restore works | Archive-only still receives targeted backup | +| Rollback is fenced | Roll back before and after a post-resolution write | Early rollback restores orphan fence; late rollback rejects | Never restores old authority | +| Diagnostics remain unhealthy | Create orphan, legacy, mismatch, stale global, and incomplete journal states | Typed findings and exact next action | Diagnosis performs no repair | +| Commit race is fenced | Pause A after match, retire/recreate B, resume A; race rollback with a write | No B-side write, debit, delivery or ownership change | Real local entrypoint; real isolated provider for affected canonical paths | +| History stays historical | Same alias and operation key across lifetimes; delayed accepted result | A receipt/result cannot authorize or settle B | Historical A readback stays available | +| Default-off parity | Same input with activation off/on against immutable pre-change baseline | Off preserves schema, guidance, state and effects; on rejects stale refs | No live project migration | +| Provider boundary is honest | Native authority candidate presented to file-only resolution | No file-only adoption; existing import route identified | No D1–D3 or R6 qualification claimed | +| Product recovery is complete | CLI and packaged frontend; qualified Lark route | Preview → confirmation → crash/resume → source and original-entry readback | Untested transports explicitly remain unqualified | + +The implementation must also run existing bootstrap, registry, deletion, +session, host, Goal Channel, heartbeat, quota, backup, docs-governance, and +public-safety suites. Skipped owner rows are not acceptance. + +## 10. Operational contract + +The stable findings are: + +- `orphaned_goal_state` +- `orphan_resolution_in_progress` +- `legacy_goal_identity` +- `goal_instance_id_missing` +- `goal_instance_mismatch` +- `goal_not_registered` +- `stale_global_goal_instance` +- `unsupported_registry_writer_protocol` +- `binding_owner_inventory_changed` +- `manual_cleanup_required` + +Every rejection identifies the source registry, Goal alias, expected and +observed instance when public-safe, owner, retryability, and one lifecycle next +action. It does not expose raw state, credentials, local session content, or +provider secrets. + +Status and diagnosis are read-only. They can inspect a legacy registry, +historical binding, strict envelope, and incomplete journal. They cannot stamp +identity, repair a global projection, choose a candidate, or complete cleanup. + +CLI, packaged frontend and Lark consume the same preview/confirm/resume result. +Show selected/competing candidates, execution block, backup proof, pending sync +and the exact next action. A stale session should explain the retired lifetime +and offer an explicitly authorized new binding, never silently resume it. +Return completion to the initiating conversation. Reuse existing settings and +action editors; a CLI-first slice must name unqualified companion surfaces and +cannot claim the complete product journey. This RFC itself changes no UI. + +The lifecycle retains one verified backup and one terminal receipt per +resolution according to the existing backup retention policy. An incomplete +journal is never garbage-collected automatically. Local cleanup retry is +idempotent. External manual cleanup is advisory because logical revocation +already prevents execution. + +## 11. Normative delivery plan + +| Milestone | Outcome and prerequisite | Decisive exit / rollback | +| --- | --- | --- | +| M0: shipped codec prerequisite | Reuse #4917 codec/transaction/I/O census; no lifetime enforcement | v1 read/write support is shipped, not proof of old-writer exclusion. Do not rebuild or remove the now-used codec. | +| M1: characterize owners and typed rules | Inventory binding/effect producers and consumers; characterize baseline; implement only rules used by the selected lifecycle | Default-off parity, typed negative matrix, source/global/warm-process compatibility census. No minting or automatic activation. | +| M2: opt-in local lifetime transaction | Incompatible activation + retirement/recreation guard + selected local owner enforcement as one usable slice | Real CLI local ABA/concurrency/crash/replay path; retain M2/M3 activation hold until every supported effect owner is fenced. Before cutover abort; afterward forward repair. | +| M3: supported-owner qualification | Complete host, quota, Todo/lease, handoff, channel and automation paths for the activated profile | Every inventory row qualified, including delayed results and unsupported binaries; no legacy fallback inside activated projects. Non-activated behavior unchanged. | +| M4: orphan recovery | Reuse fence/diagnosis/path/backup owners; journaled file-state resolution with exact legacy cleanup | Preview, destructive/retry/rollback negatives and original-entry readback. Archive/delete can ship earlier without identity creation; native-provider adoption stays blocked. | +| M5: migration and product acceptance | M2–M4 plus multi-Goal quiescence/reconnection; packaged frontend and qualified Lark | 2–3 workers, dependency artifact, interrupted A, recreated B and late A return; unrelated Goal progresses; B accepted independently. No duplicate protected effect. | + +M0 is a prerequisite checkpoint, not the next unstarted task. The next slice +owns M1 compatibility/consumer characterization and the M2 local lifecycle seam; +do not ship a field-only milestone as the ABA outcome. M2 and M3 describe +implementation order, not permission to activate a partially fenced system. +R2/R3 consume the completed local slice; R6 service adoption and D1–D3 provider +promotion retain their own acceptance. No new paid cohort or soak is authorized. + +## 12. Open decisions and holds + +1. **Supported package/profile matrix:** release and host owners pin all supported + object-only, codec-only and enforcement packages and exclusion evidence before + M2 activation. v2 names are proposed; protocol support requires semantics. +2. **Exact commit guard:** Goal lifecycle and TS transaction owners must choose + and prove the local retirement/commit lock and external-effect drain contract + before M2. A digest recheck alone cannot discharge this hold. +3. **Canonical destination/provider import:** reuse the current path owner; follow + #4915 without assuming merge. Provider-state adoption needs its own reviewed + import contract; the first file-only slice rejects it. +4. **Service profile:** R6 chooses authenticated identity projection and server + serialization. No remote local-path requirement or cloned writable registry. +5. **Retention/default rollout:** existing backup/receipt owners decide retention; + incomplete journals are never automatically collected. Fleet-wide enforcement + or default activation requires a separate disclosed migration/release decision. + +--- + +## Appendix A: Execution ledger (non-normative) + +### 2026-09-23 — Design synthesis + +- **Baseline:** `23edcb19c70394480e3a9ebe8a960f5a320c5342` +- **Delivered:** Revised RFC proposal; M0 codec prerequisite shipped separately. +- **Evidence:** Current registry, bootstrap, deletion, binding, orphan-fence, + backup, global-projection, and documentation contracts were inspected. +- **Known gaps:** No instance enforcement, activation or resolution fixture has shipped. +- **Effect on normative design:** Align with roadmap/TS/shared authority; separate codec + compatibility from enforcement, specify commit fencing and legacy cleanup. + +## Appendix B: Decision log + +| Date | Decision | Owner / approval | Alternatives | Normative sections changed | +| --- | --- | --- | --- | --- | +| 2026-09-23 | Propose source-owned random Goal instance identity plus strict registry envelope | Proposal; maintainer approval pending | Counter, path identity, additive capability marker | Initial RFC | +| 2026-09-23 | Refine compatibility, typed lifecycle ownership, commit fencing and legacy cleanup against current main | Design proposal; implementation holds in Section 12 | Decoder-only exclusion, duplicate Python state machine, guessed legacy identity | Sections 3–5 and 7–12 | + +## Appendix C: Evidence registry + +| Evidence id | Claim | Baseline / environment | Artifact or command | Result | Privacy / validity boundary | +| --- | --- | --- | --- | --- | --- | +| E1 | M0 reads/writes v1 without lifetime enforcement | Named baseline / #4917 | Registry codec and bootstrap inspection | observed | Codec compatibility is not identity admission | +| E2 | The shipped fence blocks orphaned guided bootstrap but does not resolve it | Named baseline | Issue #4801 and PR #4808 | observed | Public issue and code scope | +| E3 | Global entries are source projections | Named baseline | `loopx/global_registry.py` inspection | observed | Static current-code fact | +| E4 | Activation excludes every supported old effect path | future M1–M3 fixture | Object-only/codec-only/enforcement matrix | unverified | Source/global/warm-process coverage required before activation | +| E5 | Every durable binding owner validates exact identity | future M1-M3 fixtures | Owner inventory conformance suite | unverified | Required before enforcement | +| E6 | Resolution is crash-safe and recoverable | future M4 fixtures | Fault-injection and restore matrix | unverified | Required before destructive use | + +## Appendix D: Rejected or superseded alternatives + +- A warning-only writer capability marker cannot constrain code that does not + understand the marker. +- A redirect object leaves the old path writable and permits split authority. +- A global counter makes the projection an allocator or requires permanent + tombstones. +- Automatic binding upgrade guesses history from an alias and is forbidden. +- Provider-wide cleanup as a commit precondition couples availability to every + integration and still cannot prove external deletion. +- Runtime-directory migration does not solve non-file bindings and is outside + issue #4801. + +## Appendix E: Incident and review lessons + +- A healthy current state does not prove continuity from an earlier same-name + Goal. +- An advisory compatibility check is not a writer fence when old code ignores + it. +- Crash safety requires journaling every plan before the first mutation, reserving + identity when applicable, and reconciling effects whose acknowledgement was lost. +- Cleanup and authorization are separate: stale records may remain for audit + while exact identity matching keeps them inert. diff --git a/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.zh-CN.md b/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.zh-CN.md new file mode 100644 index 000000000..e0e99fe91 --- /dev/null +++ b/docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.zh-CN.md @@ -0,0 +1,725 @@ +# Goal 实例身份与孤儿状态恢复(v0) + +- **RFC 状态:** Draft +- **交付成熟度:** Identity/recovery 提案;codec 前置已由 #4917 交付 +- **作者/Owner:** LoopX contributors +- **创建日期:** 2026-09-23 +- **最后规范修订:** 2026-09-23 +- **实现基线:** `23edcb19c70394480e3a9ebe8a960f5a320c5342` +- **相关契约:** [Issue #4801](https://github.com/loopx-project/loopx/issues/4801)、[orphan fence 切片 #4808](https://github.com/loopx-project/loopx/pull/4808) +- **语言镜像:** [英文语义镜像](goal-instance-identity-and-orphan-recovery-v0.md) + +## 文档地图与维护契约 + +第 1-10 节是长期设计与验收契约,第 11 节是规范性交付计划,第 12 +节记录未决事项;其中的建议不等同于批准。附录只记录非规范性证据和决策历史。 + +中英文文档互为语义镜像。任何规范性变更都必须在同一个 PR 中同步修改两份文档。 + +--- + +## 1. 决策摘要 + +本 RFC 共同作出五项决策: + +1. `goal_id` 保留为面向人的别名。由项目 registry 拥有且不可变的 + `goal_instance_id` 标识一次 Goal 生命周期。 +2. 每个持久 host、session、channel、automation、Turn 和 quota binding + 都携带精确二元组。只有源项目 registry 可以授权执行;全局投影和 binding + store 都不能授权。 +3. 实例身份需要不兼容的 activation format 加提交时 fence。已交付的 M0 codec + 不代表 identity enforcement;拒绝一种旧 decoder 不能排除所有旧 effect 路径。 +4. 孤儿恢复是一个 preview-first、有 journal 的生命周期操作。操作者必须对每个 + candidate 明确选择 `adopt`、`migrate`、`archive` 或 `delete`,系统不得猜测 + 或合并。 +5. Activation 必须显式、以整个项目 registry 为范围。已激活项目内 unstamped + binding 只读;未激活项目保留 legacy 行为且不提供 ABA 保证。全局默认切换另行决策。 + +现有 state-file 位置、host 专属 runtime 目录布局、provider revision、 +Lease 和全局 registry 的角色都不会成为新的 Goal authority。本 RFC 不批准 +自动清理所有外部 provider、自动选择 candidate,也不允许普通 bootstrap +直接修改孤儿状态。 + +## 2. 问题与动机 + +当前 `goal_id` 同时承担展示名称和持久身份。Goal 可能已从项目 registry +消失,但 active-state 文件、session、host binding、Goal Channel 和 heartbeat +automation 仍然存在。之后创建同名 Goal 时,这些旧记录可能被误认为当前记录。 +这是典型的 ABA 问题: + +1. 名为 `release` 的 Goal A 创建了持久 attachment。 +2. Goal A 被删除,但部分 attachment 仍存在。 +3. 系统创建了同名 Goal B。 +4. 只保存 `release` 的 attachment 无法区分 A 和 B。 + +#4808 已交付的 fence 会在 registry 中不存在 Goal、但项目中仍有状态 candidate +时阻止 guided bootstrap。它仍无法区分 Goal 生命周期、解决 orphan,也无法在 +删除后同名重建时让旧 attachment 失效。 + +任何单独 host 或 session store 都无法局部解决该问题。若 host 自行生成身份, +它就成为第二个 Goal authority;若删除流程同步理解所有 provider,它就变成跨 +owner 的分布式清理事务。LoopX 需要源拥有的生命周期身份,并让 admission/commit 与 retirement 串行化。 +身份匹配是必要条件,不是授权凭证。 + +### 不变量 + +1. 只有源项目 registry 可以创建或解析 Goal instance。 +2. Goal 终局删除后重新创建时必须获得新的随机 instance ID,即使复用了 + `goal_id`、路径、objective 或配置。 +3. 配置更新、provider 重连、Lease 续期、新 Turn 或 binding 改写都不能轮换 + instance ID。 +4. stale、缺失、畸形或 legacy binding 不能为 instance-aware Goal 授权执行。 +5. stale 全局投影可以辅助路由,但不能授权写入。 +6. Source safety 完成前,resolution journal 阻止该 Goal 的普通激活/修改, + 即使 registry 已发布;精确 recovery 保持可用,source 完成后的投影 retry 单独处理。 +7. resolution 最终只能留下一个被选择的可写状态 authority,或保持 Goal + 缺失且没有可写 candidate。 +8. 破坏性 resolution 必须具备已验证的定向备份、精确 plan digest、显式 Goal + 确认和写后回读。 +9. crash retry 复用 prepared identity 和 owner idempotency key;包括只做 + archive/delete 的所有 plan,都必须在首次修改前写 journal。 +10. 已发布的旧 writer 不能静默打开或改写 instance-aware 项目 registry。 + +## 3. 范围与非目标 + +### 范围内 + +- 项目 registry 所有的 Goal 生命周期身份和精确 binding 比较。 +- 面向旧 reader/writer 的 registry 级兼容边界。 +- 在所有第一方持久 binding owner 中传播 instance。 +- 在激活、恢复、投递、状态修改和 quota 授权处做最终检查。 +- 项目本地 orphan state 的 preview、backup、apply、resume 和 rollback。 +- orphan state、legacy identity、stale binding、stale 全局投影、未完成 journal + 和人工清理的诊断。 +- 将 observation 与 enforcement 分离的分阶段迁移。 + +### 非目标 + +- 改变面向人的 `goal_id`,或把 instance ID 放进 state path。 +- 迁移 Codex、Pi、OpenCode 或其他 host 的底层 runtime 目录。 +- 合并多个 active-state candidate。 +- 将 timestamp、路径、内容相等、全局投影或 host 记录当作身份依据。 +- 把外部 provider 的物理删除作为安全边界。 +- 引入公开、通用的 binding-provider 框架。 +- 改变 Claim、Lease、authority revision、Turn 或 provider idempotency + 语义;它们只新增 Goal instance 绑定。 +- 允许旧二进制降级或编辑严格 registry。 + +### Roadmap 定位与契约协同 + +[整体 roadmap](loopx-overall-roadmap-v0.zh-CN.md) 以验收结果、人的注意力成本和 +恢复能力衡量产品。本 RFC 提供 R5 的有界 lifetime fence,供 R2/R3 消费, +不据此完成 R1–R7 或晋升 provider。 + +| 既有 owner/契约 | 本 RFC 的贡献与边界 | +| --- | --- | +| [TS 收敛](typescript-control-plane-migration-v0.zh-CN.md)、R5 | Match、admission、retirement、recovery transition 归 typed kernel;Python 保留 codec、文件和 host adapter。不得新增第二套 Python policy engine 或逐字段 RPC。 | +| [Shared authority](shared-goal-authority-state-provider-v0.zh-CN.md)、D1–D3/R6 | 将已有 head、operation receipt、authorization projection 绑定 lifetime。Provider incarnation、revision、lease epoch 与 Goal instance 相互独立;不新增 store、不切换默认。 | +| [Shared alignment](shared-goal-alignment-and-governed-amendment-v0.zh-CN.md)、R4 | Identity 不等于 intent、permission 或 work revision。保留 `intent_basis` 的 source-facts 语义;amendment 仍归原 owner。 | +| [语义 handoff](capable-manager-semantic-handoff-v0.zh-CN.md)、[协作](agent-im-openviking-collaboration-v0.md)、R2/R3 | Request、adoption、result 和原会话 return 绑定 instance。A 的迟到结果不能结算 B;transport 成功和 registration 不证明验收。 | +| [单 owner daemon](single-owner-local-daemon-v0.md) | 复用 profile quiescence 和真实 executor drain;PID、package version 或停止 heartbeat 不证明旧 writer 不会回来。Daemon identity 不授予 Goal authority。 | + +[#4808](https://github.com/loopx-project/loopx/pull/4808) 已交付 guided fence; +[#4912](https://github.com/loopx-project/loopx/pull/4912) 提议 diagnosis; +[#4915](https://github.com/loopx-project/loopx/pull/4915) 提议 host-neutral paths。 +复用这些 owner,但不能将 open PR 视作已交付。Canonical Todo 拥有执行状态, +本文件拥有验收与依赖边界。 + +## 4. 当前系统契约 + +在上述 main 基线上,[#4917](https://github.com/loopx-project/loopx/pull/4917) +已交付 `control_plane/projects/registry_codec.py`、保留格式的 transaction 和 +生产 I/O census。它读取 legacy object 及 `loopx_project_registry_envelope_v1` +array,并接受 writer protocol `goal_instance_v1`;它**不**生成、比较或强制 +Goal instance。因此,懂 codec 但不懂 identity 的版本也是旧 writer,原先 +“旧 loader 只接受 object 根”的论证不充分。 + +Project registry 拥有配置和源 identity;global registry 是 host-local route +projection。Canonical coordination 已有 typed TS transaction owner、独立的 +provider incarnation 和 receipt。`coordination/authority_source.ts` 与 +`authority_source_capture.py` 见证 registry facts,且明确不构成 registry/provider +之间的 transaction;digest recheck 不能冒充 retirement serialization。 + +`orphaned_goal_state.py` 发现 current/legacy candidate 并阻止 guided bootstrap; +`state_backup.py` 拥有 backup/verification。Bootstrap update、Goal deletion、 +session、channel 和 quota 仍是不同 caller,alias/revision binding 不证明 +lifetime continuity。本文所述 instance activation 和 orphan mutation command +在该基线尚未交付。 + +## 5. 提议架构 + +### 5.1 Ownership 与 authority + +放置决策:既有 built-in control-plane Goal/project lifecycle,不新增 public +capability id、provider id 或 extension distribution。File、SQLite、PostgreSQL +保留各自 provider contract 和 qualification gate。 + +只有 source authority 可以分配、发布 `goal_instance_id`。Prepared resolution +可持久预留一个 ID,但直到 lifecycle commit 才具备执行效力。Global registry +只复制。纯 match、lifecycle transition 归最近的 typed TS Goal boundary, +通过一次粗粒度 transaction 消费 source facts。Python 适配 CLI、codec、文件、 +backup、host effect,执行 typed plan,不复制状态规则。 + +本地 registry 继续拥有源 identity。未来 R6 service profile 由经过认证的 +authority service 准入版本化 authorization projection 和 instance。远端 worker +不能打开笔记本路径、从本地副本分配身份,或把 global projection 当 authority。 +该路径完成资格化前,instance enforcement 明确只覆盖 local profile。 + +任何 binding、path、timestamp、content hash 都不得推断身份。匹配后仍必须执行 +既有 grant、claim、lease、quota、stop 和 intent 检查。 + +### 5.2 Goal identity 模型 + +`goal_instance_id` 是 opaque、immutable、public-safe 的非凭证字段。规范格式 +为 `ginst_` 加 32 个小写十六进制字符,包含 128 个随机 bit。 + +Typed contract 区分 `GoalRef { goal_id, goal_instance_id }` 与 +`LegacyGoalRef { goal_id }`。`BindingMatch` 是穷尽结果,包含 `current`、 +`legacy_read_only`、`missing_goal_instance_id`、`goal_instance_mismatch`、 +`goal_not_registered`、`resolution_in_progress`、`invalid`。这些是 kernel +决策;生成/解码得到的 Python representation 只作 wire adapter,不另实现 match。 + +Instance-aware Goal 记录包含: + +```json +{ + "id": "release-2026", + "goal_instance_id": "ginst_6ff38d6d143d4b72a6ff894b95f067c1", + "status": "active", + "repo": "/project", + "state_file": ".codex/goals/release-2026/ACTIVE_GOAL_STATE.md" +} +``` + +上面的绝对路径只作说明,并非公开证据。持久路径继续遵守现有隐私和可移植性规则。 + +每个持久 attachment 保存: + +```json +{ + "goal_id": "release-2026", + "goal_instance_id": "ginst_6ff38d6d143d4b72a6ff894b95f067c1" +} +``` + +匹配矩阵必须穷尽: + +| Registry 状态 | Binding 状态 | 只读检查 | 执行或修改 | +| --- | --- | ---: | ---: | +| 精确 instance | 完全相同的 instance | 允许 | 在 commit fence 和既有 grants 下允许 | +| 精确 instance | 缺失 instance | 带 finding 允许 | 拒绝 | +| 精确 instance | 不同 instance | 带 finding 允许 | 拒绝 | +| Legacy Goal | Legacy binding | 带 finding 允许 | 已激活 registry 中拒绝 | +| Legacy Goal | Instance binding | 带 finding 允许 | 拒绝 | +| Goal 缺失 | 任意 binding | 作为历史记录允许 | 拒绝 | +| Resolution source safety 未完成 | 任意 binding | 带 finding 允许 | 拒绝普通执行;仅允许精确 recovery | +| 记录非法 | 任意 | fail closed | 拒绝 | + +Observation 和未激活项目保留 legacy execution,但不能声称 ABA 保证。已激活项目 +没有 `legacy_compatible` execution 分支。精确匹配也必须同时满足 active +lifecycle state 和既有 grants。 + +### 5.3 身份独立性 + +以下 revision domain 相互独立: + +| 事件或身份 | 是否轮换 `goal_instance_id` | 原因 | +| --- | ---: | --- | +| 终局退休后重新创建 | 是 | 开始新的 Goal 生命周期 | +| 创建 Goal 的 orphan `adopt` 或 `migrate` | 是 | 建立新的 authority | +| 强制 bootstrap 或配置更新 | 否 | 修改同一生命周期内的配置 | +| Provider 重连或 provider revision | 否 | 修改 attachment 或 backend generation | +| Authority revision 或 migration receipt | 否 | 版本化 authority state,而非 Goal 存在性 | +| Claim 或 Lease epoch | 否 | 协调同一生命周期内的工作 | +| 新 Turn 或 session | 否 | 在同一生命周期中创建执行 lineage | +| Binding revision | 否 | 版本化单个 owner 的 attachment | +| 全局 registry 同步 | 否 | 复制源拥有的 identity | +| 回滚到 orphan bytes | 永不恢复 | 恢复的 bytes 没有执行 authority | + +`--force` 不能替换 instance ID。导入到新的项目 authority 时必须生成新 identity, +不得复制由另一个 registry 拥有的 identity。 + +恢复 authority 到替代 deployment 必须遵守既有 incarnation/restore contract; +复制 registry backup 不能复活 stale writer 或创建第二个可写 authority。 + +### 5.4 Activation format 与 mixed-writer 门禁 + +Identity 以整个 project registry 激活,不按单 Goal 激活。复用 M0 codec 和 +I/O census,不另建 loader。M0 v1 format/protocol 只代表 codec。提议 activation +使用不兼容的 `loopx_project_registry_envelope_v2` header schema 和 +`minimum_writer_protocol: goal_instance_v2`,保留 `[header, payload]` 与 +payload digest。最终标识须在发布前通过 M2 compatibility matrix。 + +只修改 writer string 不够:M0 reader 可以解码未知 writer protocol,而状态写入 +可能根本不改 registry。新 schema 必须拒绝不懂 identity 的 **read-to-effect** +路径和 registry write。旧 global-object route、已加载进程还需单独排除;header +不能追溯约束缓存中的旧代码。 + +```console +loopx activate-goal-instance-identity --project . --format json +loopx activate-goal-instance-identity --project . --execute \ + --plan-revision sha256:31ab... --confirm-registry-path .loopx/registry.json +``` + +Activation 必须: + +1. 经既有 lifecycle/host owner 停止新 admission,持久 quiesce registry 中全部 + Goal;drain 或明确 reconcile 已准入 effect。盘点 direct CLI、attached/managed + host、global route、automation 和 provider writer。失联或无法说明的 writer + 阻塞 activation。 +2. 验证定向 backup、host protocol support 和精确 preview digest。Capability + advertisement 是待验证证据,不是写权限。 +3. Prepare durable activation journal 和每个 Goal 的一个 ID,在 registry + transaction 下原子发布不兼容 envelope。Retry 复用 ID;普通 M0/M1 不生成 ID。 +4. 通过显式、审阅过的迁移/重连绑定 owner state。缺 stamp 不得按 alias 继承 + authority。必要 commit fence 和 readback 完成后才重新准入;投影待交付不能 + 授权或重复 effect。 + +发布前可 abort 回 legacy object;发布后只能向前修复,禁止解包给旧 writer。 +新项目可 opt into 完整 profile;安装和 codec 可用不等于 activation。全局关闭 +legacy execution 是独立 release 决策。 + +验证 object-only、codec-only M0/M1、enforcement 三类版本,包括 direct source、 +global route、warm process、state write、resume、quota、delivery。拒绝后必须 +business state 和 receipt 不变,不能只检查 registry bytes。本地 drain 加拒绝 +unsupported source decode 必须覆盖受支持版本;若旧路径同时绕过两者,保持 +activation 不可用,直到既有 owner 能机械排除。相同 OS principal 故意篡改文件 +不属于 mixed-version 安全模型。 + +### 5.5 Binding owner 与提交 fence + +| 既有 owner | Instance-bound state 与关键边界 | +| --- | --- | +| Registry/attached host/Chat | Session/thread、activation binding;route selection、resume、executor admission | +| Turn/scheduler/quota | Work selection、lineage、settlement、debit/void、replay identity | +| Todo/coordination/lease | Head/provider binding、work mutation、claim/renew/reclaim、receipt | +| Handoff/inbox/outbox | Request、receiver adoption、accepted artifact、result delivery、historical readback | +| Goal Channel/Lark/heartbeat | Connection、迟到 inbound/outbound event、wake admission | +| Pi/OpenCode/其他第一方 host | Host action、execution binding、quota/state writeback | + +维护一个 private 穷尽 owner inventory:稳定 locator、revision、content digest、 +observed typed reference、cleanup support。排序后的 inventory 和 owner revision +进入 plan。Registry access 复用 M0 I/O census;binding/effect coverage 必须另外 +盘点,writer 数量不构成证明。 + +Resolve route → 校验 source/授权 projection → 比较精确 reference 与 lifecycle → +既有 permission → operation admission → 在 owner fence 下提交。最后一刻读取 +仍有 check/use race:A 通过检查后暂停,删除/重建发布 B,随后 A 写入复用路径或 +消费 quota。因此必须明确 linearization boundary: + +- 本地 retirement 与每笔短 protected commit 共用 instance admission guard, + 固定 lock order;不跨 model call、network request 或 user validation 持有 + registry lock。 +- Canonical transaction 将原 source witness 和 instance 传给 TS owner,在 + lifecycle guard 下于 commit 边界检查当前 instance。既有 CAS、lease fence + 仍必要;witness hash 或 Goal ID 不等于该 guard。 +- Retirement 先关闭 admission,drain 已准入 commit,并经既有 journal reconcile + pending external effect,再发布 retirement。不确定的外部 effect 阻止宣告 + retirement/reuse 完成;无 fencing 的外部 provider 不能被追溯取消。 +- Shared service 必须在服务端串行化 lifetime/mutation admission,保留 tenant、 + actor、restore incarnation 校验。本地文件锁不满足 R6,也不声称跨 ledger 原子性。 + +即使物理路径仍用 alias,operation/replay key、canonical state selection、late +result 都必须按 instance 隔离。旧 receipt 仍可作为 A 的历史读取,但不能授予 B +lease、满足 B acceptance 或给 B 扣费;不得静默重标历史 receipt。B 的 provider +state 必须新建,或经既有审阅过的 lifecycle 显式导入。A 在校验后暂停、同名重建后 +恢复的反例,必须不产生任何 B-side write、delivery、debit 或 ownership change。 + +### 5.6 Orphan resolution 命令 + +除非带 `--execute`,命令只做 dry run。每个发现的 candidate 都必须有明确 +disposition: + +```console +loopx resolve-orphaned-goal-state \ + --project . \ + --goal-id release-2026 \ + --disposition .codex/goals/release-2026/ACTIVE_GOAL_STATE.md=adopt \ + --disposition .claude/goals/release-2026/ACTIVE_GOAL_STATE.md=archive \ + --objective "Ship the release" \ + --domain engineering \ + --role owner \ + --format json +``` + +- `adopt` 在 canonical 位置保留选中 state content,以新 Goal instance 注册; + 不恢复旧 claim、grant 或 execution binding。 +- `migrate` 通过 canonical state-path helper 移动一个选中 candidate,在相同 + human Goal ID 下注册新 instance。 +- `archive` 把 candidate 移出所有 active Goal root。 +- `delete` 只有在定向 backup 验证后才删除 candidate。 + +最多一个 candidate 可以是 `adopt` 或 `migrate`;其余 candidate 必须是 +`archive` 或 `delete`。只有 archive/delete 的计划让 Goal 保持缺失。系统不提供 +隐式 winner、内容合并、任意 destination path 或重命名 target Goal。 + +首个 resolution profile 仅覆盖项目本地 legacy file state。若 candidate 路由到 +已晋升 canonical/provider state,必须 fail closed 并指向既有 authority +migration/import route;移动 Markdown 不能接管 File/SQLite/PostgreSQL head。 +后续 provider 支持必须保持 source selection、incarnation、历史 receipt 和 D1–D3。 + +Preview 在计算 digest 前验证: + +- project 和 registry 位于声明的 authority boundary 内; +- 每个 candidate 是 allowed Goal root 内可读的 regular file; +- candidate 和 parent 都不是 symlink; +- resolved path 不 escape、alias 或互相重复; +- 请求的 Goal 不存在于项目 registry; +- 同一 Goal 不存在其他 resolution; +- 所有 candidate tree digest 和 binding-owner revision 稳定; +- migration destination 由 canonical path helper 选择; +- 需要创建 Goal 时,其 specification 完整。 + +执行必须绑定 preview 和人工意图: + +```console +loopx resolve-orphaned-goal-state \ + --project . \ + --goal-id release-2026 \ + --disposition .codex/goals/release-2026/ACTIVE_GOAL_STATE.md=adopt \ + --disposition .claude/goals/release-2026/ACTIVE_GOAL_STATE.md=archive \ + --objective "Ship the release" \ + --domain engineering \ + --role owner \ + --execute \ + --plan-revision sha256:82e3... \ + --confirm-goal-id release-2026 +``` + +### 5.7 Resolution transaction 与 journal + +Typed lifecycle 包含 `prepared`、`applying`、`source_committed`、`complete`、 +`rollback_prepared`、`rolled_back`。Journal 位于 +`.loopx/lifecycle/orphaned-goal-state//.json`,是恢复 +metadata,不是另一份 registry。TS 拥有 transition/allowed effect;既有 +Python/host adapter 执行并返回 observation。 + +1. 按固定顺序获取 lifecycle/registry/affected-owner guard。已完成 exact replay + 返回历史 receipt,不重新激活 Goal。 +2. Prepare 前重建并比较完整 registry、candidate-tree、inventory、creation-spec、 + plan digest;不匹配则无 effect。 +3. 验证定向 backup,所有 plan 在首次 move/delete 前持久 prepare,**包括只做 + archive/delete** 的 plan。仅创建 Goal 时预留 ID;记录 candidate preimage、 + planned postimage 和 owner operation key。 +4. 在 guard 下应用 disposition;effect 前写 intent,crash-before-ack 后核对真实 + pre/post state。已匹配 post-state 不重复删除;两者均不匹配时不得猜测。 +5. 选中内容已 canonical、竞争 candidate 已失活后,只发布一次新 Goal。Prepared + plan 从已记录 phase 恢复,不要求部分成功后仍符合原 preview 的 pre-mutation digest。 +6. 收集必要 local invalidation receipt、验证 source 和 safety fence,再完成 lifecycle。 + 有其他 legacy Goal 时先完成 registry-wide activation;其他 Goal 不属于本次 + resolution guard 范围。 +7. 通过既有 retry owner 投递 global projection 和 operator result。投影失败是 + typed `pending_sync`,不是另一份 authority,也不能重复 source commit。 + +Source safety 成立前,journal 阻止该 Goal 的普通 activation/mutation,即使已发布 +Goal record;只有精确 resolution/resume 可以执行其声明的恢复 effect。Source commit +及必要 invalidation 完成后,投影 lag 仍需可见,但不能无限阻塞无关 Goal 或已有 +source 授权的本地工作。Event-driven wake 只作提示,source check 决定 admission。 + +Receipt 保存 plan/resolution/terminal-journal digest、candidate pre/post digest、 +精确 source Goal reference、backup manifest verification、各 owner observation、 +revision、operation、readback、local removal receipt,以及 typed projection/manual +cleanup finding;排除 private raw content。关闭新 lifecycle admission 时仍保留恢复。 + +### 5.8 逻辑 revocation 与 legacy cleanup + +精确 instance mismatch 加 commit fence 构成安全边界;外部物理删除是 hygiene。 +Typed cleanup selector 区分: + +- `instance`:精确 retired `(goal_id, goal_instance_id)`; +- `legacy_observation`:精确 owner locator + observed revision + content digest, + 仅在 lifecycle guard 证明 Goal absent/inactive 时准入。 + +Legacy orphan 没有可凭空生成的 retired instance。Owner 支持条件删除时,在其 guard +下重新校验 observation,只删除该 record;revision 改变则 stop/replan。Provider +无法证明精确删除时,保留 inert record 并返回 `manual_cleanup_required`。不得按 +Goal ID 批量删除,也不能先给 legacy binding stamp 身份以便删除。 + +必要 local logical invalidation receipt 阻塞 source completion;可选物理清理和 +external manual cleanup 使用独立 retry state。另一个 lifetime 创建的新 binding +不能匹配旧 legacy selector。分别验证 legacy/stamped orphan 和 revision race。 + +### 5.9 Rollback + +Rollback 同样采用 preview-first,并绑定 digest: + +```console +loopx rollback-orphaned-goal-resolution \ + --project . \ + --goal-id release-2026 \ + --resolution-id ores_... \ + --format json + +loopx rollback-orphaned-goal-resolution \ + --project . \ + --goal-id release-2026 \ + --resolution-id ores_... \ + --execute \ + --plan-revision sha256:... \ + --confirm-goal-id release-2026 +``` + +Rollback 只能把已验证 bytes 恢复到 orphaned、fenced 状态,不能恢复 retired +Goal instance ID 或 binding authority。在任何 resolution 后 Goal write、Todo +mutation、quota spend、session/channel 创建、automation 安装或 binding revision +发生后,rollback 必须拒绝。Rollback 自身也须在首次修改前写 journal,并使用同一 retirement/commit guard, +避免资格检查与新业务写入竞争。成功 rollback 通过 lifecycle owner 删除新 Goal, +恢复 candidate bytes,验证 digest,并让 diagnose 重新以 +`orphaned_goal_state` 报告 unhealthy。 + +若 rollback 已不合法,恢复只能向前执行:完成 journal,通过普通 lifecycle +退休新 Goal,再创建新的 resolution plan。 + +### 5.10 有界重构与交付 owner + +| 既有边界 | 改动与删除要求 | +| --- | --- | +| TS Goal lifecycle + `coordination/authority_source.ts` | 一套 typed reference/match/admission/recovery 规则;扩展完整 transaction,删除 host 重复判断。Witness 是证据,不是跨 store 锁。 | +| `control_plane/projects/registry_codec.py` | 扩展已交付 codec/protocol check 和 transaction;保留 v1 compatibility,不赋予 v2 enforcement。 | +| `bootstrap.py`/Goal deletion | 消费 lifecycle transaction;配置更新保留 identity,退休/重建受 fence 保护。 | +| `orphaned_goal_state.py`/`state_backup.py` | 保留 discovery/backup effect;薄 resolution adapter 驱动 typed plan,不新增 Python 状态机。 | +| 既有 binding/delivery owner | 持久 exact ref 和 commit fence;复用 owner-local journal/idempotency,不新增中央 provider cleanup framework。 | +| Diagnosis/CLI/Chat/frontend/Lark | 带 audience filtering 呈现同一 lifecycle result/recovery action,不新增 detector 或独立 repair state。 | + +重构前刻画普通 bootstrap/update/deletion 和 default-off 路径。以真实 consumer +交付完整本地 retirement/recreation 路径;不增加未使用 Goal abstraction、逐字段 +RPC 或 scheduler。更大的 cross-host/provider import 保留给 R6/D1–D3,不阻塞 +本地 legacy-file recovery 的有用交付。 + +## 6. 替代方案与设计选择 + +### Generation counter + +`goal_id` 下的 counter 需要在删除后永久保留 tombstone,或依赖第二个全局 +allocator。源 registry 生成随机 identity 不需要这两项,也不要求 ID 有序。 + +### 在目录路径中加入 instance ID + +Instance-segment path 可以隔离文件,却不能标识 session、channel、automation +或 quota call;同时会迫使所有面向人的路径 consumer 参与大范围迁移。记录级 +identity 无需引入该无关布局变更即可修复已报告 ABA 问题。 + +### 删除 Goal 时清理所有 binding + +跨 provider 删除不具备原子性,还会迫使 Goal lifecycle 理解所有 storage +实现。精确匹配先让 stale record 失活;owner 本地清理只改善卫生,不承担 +authority。 + +### 给当前 registry object 增加 capability 字段 + +已发布的旧 loader 接受未知 object 字段和 schema value,因此仍可能静默改写 +instance-aware registry,不满足 mixed-writer 不变量。 + +### 新 registry 路径加 redirect object + +已发布的旧二进制可能忽略 redirect,继续写旧路径并形成 split authority。同一路径 +上的不兼容根形状会产生确定性 decode failure。 + +### 自动选择或合并 candidate + +路径优先级、修改时间和内容相等都不能证明 authority。多个 candidate 可能代表 +分叉历史,必须由操作者逐个分类。 + +### 一个模块同时负责 discovery 和 mutation + +把小型只读 fence 与 backup、journaling、cleanup、rollback 和 registry +publication 合并,会让稳定 detector 混入大型事务。独立 resolution owner +保留深的 plan/apply 接口,又不会把 fence 模块变成 lifecycle service。 + +## 7. 安全、隐私与兼容性 + +Identity 和 digest 是 public-safe metadata,不是凭证。Backup、candidate 内容、 +session payload、本地绝对路径、provider handle 和原始状态属于 private runtime +data,不得进入公开 receipt、log、fixture 或文档。 + +Format gate 与 effect-owner fence 共同排除受支持的 mixed writer。Activation +必须通过 object-only、codec-only、enforcement 三类版本的 source/global/warm-process +negative test。只使用隔离 synthetic state,不在活动 Goal 上验证拒绝。 + +未激活 legacy 项目保留原行为。已激活项目允许 legacy inspect、backup、migration +preview,但拒绝 unstamped execution。Observation finding 不能声称 ABA 保障。 +Protocol 名称必须代表完整 enforcement,而非 decoder 可用性。 + +已激活 registry 不可降级。Unsupported source schema/protocol 必须在业务 effect +前失败,包括不修改 registry bytes 的 effect。恢复只能使用兼容版本,不解包 payload。 + +在已激活 profile 内,源不可用、digest mismatch、envelope 畸形、instance 缺失、 +source safety 未完成或 owner inventory 歧义都必须 fail closed。全局投影不可用可能延迟可见性, +但不能授权执行。 + +## 8. 迁移与回滚 + +迁移有两个范围: + +1. **显式 opt-in 的新项目。** 创建 activation envelope,并在首次 Goal 的 committed registry + transaction 中生成 identity。 +2. **已有项目。** Preview registry-wide activation,quiesce 每个 Goal,备份 + registry 和本地 binding,为所有当前 Goal 生成新 instance,原子写入严格 + envelope,并要求所有 attachment 显式重连。 + +不得自动认可任何现有 attachment。系统没有证据证明一条 unstamped record +属于当前生命周期,而不是更早的同名 Goal。 + +已验证不兼容 envelope write 是 activation cutover point;必要 owner fence 和 +readback 完成前保持 admission 关闭: + +- 写入前,abort 让 legacy object 保持 authority。 +- 写入后,旧二进制和 unstamped binding 均不受支持并 fail closed。 +- 后续步骤失败时,当前代码恢复 activation journal。 +- 恢复 legacy execution 不属于 rollback。 + +Orphan archive/delete 不创建 Goal,因此无需 identity activation。Orphan +adopt/migrate 必须使用已激活 registry、新建已激活 registry,或由同一 plan +准入有 journal 的 registry-wide activation。 + +Resolution prepare 前 abort 不改变任何状态。Prepare 后必须重试同一个 plan。 +独立 rollback 命令仅在没有任何 resolution 后业务写入时合法,并只能恢复 +orphaned、non-executable bytes。 + +## 9. 验证与验收 + +| 声明 | 测试或证据 | 必须结果 | 边界/排除项 | +| --- | --- | --- | --- | +| 同名重建被 fence | 删除 A,保留全部 attachment,以相同 alias 创建 B,遍历所有 owner | 所有旧路径在 effect 或 quota 前拒绝 | 不要求 provider 物理删除 | +| 更新保留生命周期 | 强制更新配置、provider 重连、Lease 续期、新 Turn | Registry 和新记录使用相同 instance ID | 不要求 binding revision 不变 | +| 已激活范围内 unstamped execution 只读 | 执行 status、backup、migration preview、resume、delivery、write、spend | 读取带 finding 成功;拒绝普通业务 effect;显式迁移仍可用 | 未激活 legacy 行为不变 | +| 旧 writer 被机械拒绝 | Object-only、codec-only、enforcement 版本;source/global/warm-process | 业务 effect 前拒绝,state/receipt 不变 | Decoder rejection 本身不充分 | +| Strict codec 穷尽 | 静态 writer inventory 加 Python/TS conformance | 不存在项目 registry 直接写绕过 | 测试 helper 可使用隔离 fixture | +| 源 authority 胜过全局投影 | 制造 stale 全局条目,调用所有 executable route | 源 mismatch 拒绝 | 全局 routing availability 独立 | +| Preview 纯只读 | Preview 1、2、4 个 candidate | 不写 file、registry、binding 或 journal | 允许读取 filesystem metadata | +| Plan 绑定完整状态 | Preview 后修改 candidate、owner revision、registry 或 Goal spec | Apply 在 backup/mutation 前拒绝 | 无 | +| 路径校验 fail closed | Symlink、escape、重复路径、非 regular 或不可读 candidate | Preview/apply 拒绝 | 合法 destination 由 canonical helper 拥有 | +| Crash retry 幂等 | 所有 plan;每个 effect 和 journal ack 前后 crash | 需要时只有一个 prepared ID;精确 reconcile,无重复 effect | 只做 archive/delete 也有 journal | +| 已发布但不安全仍被 fence | 发布后、必要 invalidation/source readback 前 crash | Activation/mutation/quota 拒绝;精确 recovery 可用 | Source 完成后仅投影 lag 单列 pending-sync | +| Cleanup 失败仍安全 | Stamped/legacy binding;legacy revision 改变;local invalidation 失败 | 只精确 cleanup;external record inert;必要 local invalidation 重试 | 不生成虚假 retired identity | +| Receipt 完整 | 对照 owner 表和全部 readback | 每个 owner/candidate/registry/global/backup/fence row 都存在 | 排除原始 private 内容 | +| 破坏前已有 backup | 在 backup 验证前后注入失败 | Proof 前不破坏;已验证 restore 可用 | Archive-only 仍做定向 backup | +| Rollback 被 fence | 分别在 resolution 后写入前后 rollback | 早期恢复 orphan fence;晚期拒绝 | 永不恢复旧 authority | +| 诊断持续 unhealthy | 构造 orphan、legacy、mismatch、stale global 和 incomplete journal | Typed finding 和精确 next action | Diagnose 不做 repair | +| Commit race 被 fence | A match 后暂停,退休/重建 B,再恢复 A;rollback 与 write 竞争 | 无 B-side write/debit/delivery/ownership change | 真实本地入口;涉及 canonical path 时用真实隔离 provider | +| 历史保持历史 | 不同 lifetime 复用 alias/operation key;迟到 accepted result | A receipt/result 不能授权或结算 B | A 历史 readback 仍可用 | +| Default-off parity | 同输入 off/on 对比 immutable pre-change baseline | Off 保持 schema/guidance/state/effect;on 拒绝 stale ref | 不迁移活动项目 | +| Provider 边界真实 | Native authority candidate 交给 file-only resolution | 不按文件接管;指明既有 import route | 不声称 D1–D3/R6 资格 | +| 产品恢复完整 | CLI、packaged frontend、已资格化 Lark | Preview→确认→crash/resume→source 与原入口 readback | 未测 transport 明确不具备资格 | + +实现还必须运行现有 bootstrap、registry、deletion、session、host、Goal Channel、 +heartbeat、quota、backup、docs-governance 和 public-safety suite。跳过任何 owner +row 都不算验收。 + +## 10. 运维契约 + +稳定 finding 包括: + +- `orphaned_goal_state` +- `orphan_resolution_in_progress` +- `legacy_goal_identity` +- `goal_instance_id_missing` +- `goal_instance_mismatch` +- `goal_not_registered` +- `stale_global_goal_instance` +- `unsupported_registry_writer_protocol` +- `binding_owner_inventory_changed` +- `manual_cleanup_required` + +每个 rejection 都应在 public-safe 前提下标识 source registry、Goal alias、 +expected/observed instance、owner、retryability 和一个 lifecycle next action, +不得暴露 raw state、credential、本地 session 内容或 provider secret。 + +Status 和 diagnose 只读。它们可以检查 legacy registry、historical binding、 +strict envelope 和 incomplete journal,不能 stamp identity、修复全局投影、选择 +candidate 或完成 cleanup。 + +CLI、packaged frontend、Lark 消费同一 preview/confirm/resume result。展示选中及 +竞争 candidate、execution block、backup proof、pending sync 和精确 next action。 +Stale session 应解释旧 lifetime,并提供显式授权的新 binding,不得静默 resume。 +完成结果返回原始会话。复用已有 settings/action editor;CLI-first 切片须标出 +未资格化 companion surface,不能声称完整产品闭环。本 RFC 自身不改 UI。 + +Lifecycle 按现有 backup retention policy 为每次 resolution 保留一个 verified +backup 和一个 terminal receipt。Incomplete journal 不得自动 GC。本地 cleanup +retry 必须幂等。External manual cleanup 只作提示,因为 logical revocation +已经阻止执行。 + +## 11. 规范性交付计划 + +| 里程碑 | Outcome 与前置 | 决定性退出/回滚 | +| --- | --- | --- | +| M0:已交付 codec 前置 | 复用 #4917 codec/transaction/I/O census;无 lifetime enforcement | v1 read/write 已交付,不证明 old-writer exclusion;不重建或删除已在使用的 codec。 | +| M1:owner 刻画与 typed rule | 盘点 binding/effect producer/consumer、刻画基线;只实现选定 lifecycle 使用的规则 | Default-off parity、typed negative matrix、source/global/warm-process compatibility census;不 mint、不自动 activate。 | +| M2:opt-in 本地 lifetime transaction | 不兼容 activation、retirement/recreation guard、选定本地 owner enforcement 作为完整可用切片 | 真实 CLI local ABA/concurrency/crash/replay;全部受支持 effect owner 有 fence 前保留 M2/M3 activation hold。Cutover 前 abort,之后 forward repair。 | +| M3:受支持 owner 资格化 | 完成已激活 profile 的 host、quota、Todo/lease、handoff、channel、automation 路径 | 全 inventory row,包括 late result/unsupported binary;已激活项目无 legacy fallback,未激活行为不变。 | +| M4:orphan recovery | 复用 fence/diagnosis/path/backup owner;journaled file-state resolution 与精确 legacy cleanup | Preview、destructive/retry/rollback negative、原入口 readback;archive/delete 可提前交付但不创建身份,native-provider adoption 仍阻塞。 | +| M5:迁移与产品验收 | M2–M4、多 Goal quiescence/reconnection、packaged frontend、已资格化 Lark | 2–3 worker、一条产物依赖、中断 A、重建 B、迟到 A return;无关 Goal 继续,B 独立验收;无重复 protected effect。 | + +M0 是前置 checkpoint,不是下一个尚未开始的任务。下一切片承担 M1 compatibility/ +consumer 刻画及 M2 本地 lifecycle seam,不能把加字段当作 ABA outcome。M2/M3 +描述实现顺序,不授权激活部分有 fence 的系统。R2/R3 消费完成的本地切片;R6 +service adoption、D1–D3 provider promotion 保留各自验收。不授权付费 cohort/soak。 + +## 12. 未决事项与 hold + +1. **受支持 package/profile matrix:** release/host owner 在 M2 activation 前固定 + object-only、codec-only、enforcement 版本及排除证据。v2 名称是提案,support + 必须包含语义。 +2. **精确 commit guard:** Goal lifecycle/TS transaction owner 在 M2 前选定并证明 + 本地 retirement/commit lock 与 external-effect drain contract;digest recheck + 不能解除 hold。 +3. **Canonical destination/provider import:** 复用当前 path owner,跟随 #4915 + 但不预设已合并。Provider-state adoption 要有独立审阅的 import contract;首个 + file-only 切片拒绝它。 +4. **Service profile:** R6 决定 authenticated identity projection 与服务端串行化; + 不要求远端访问本地路径,不复制 writable registry。 +5. **Retention/default rollout:** 既有 backup/receipt owner 决定 retention; + incomplete journal 不自动 GC。全局 enforcement/default activation 需独立、 + 已披露的 migration/release 决策。 + +--- + +## 附录 A:执行记录(非规范性) + +### 2026-09-23 — 设计综合 + +- **基线:** `23edcb19c70394480e3a9ebe8a960f5a320c5342` +- **已交付:** 修订 RFC 提案;M0 codec 前置已单独交付。 +- **证据:** 已检查当前 registry、bootstrap、deletion、binding、orphan fence、 + backup、全局投影和文档契约。 +- **已知缺口:** 尚无 instance enforcement、activation 或 resolution fixture。 +- **对规范设计的影响:** 对齐 roadmap/TS/shared authority;分开 codec compatibility + 与 enforcement,明确 commit fence、legacy cleanup。 + +## 附录 B:决策日志 + +| 日期 | 决策 | Owner/批准 | 替代方案 | 变更的规范章节 | +| --- | --- | --- | --- | --- | +| 2026-09-23 | 提议 source-owned 随机 Goal instance identity 与 strict registry envelope | 提案;等待 maintainer 批准 | Counter、path identity、附加 capability marker | 初始 RFC | +| 2026-09-23 | 基于当前 main 修订 compatibility、typed lifecycle ownership、commit fence、legacy cleanup | 设计提案;实现 hold 见第 12 节 | Decoder-only exclusion、重复 Python 状态机、猜测 legacy identity | 第 3–5、7–12 节 | + +## 附录 C:证据登记 + +| 证据 ID | 声明 | 基线/环境 | Artifact 或命令 | 结果 | 隐私/有效性边界 | +| --- | --- | --- | --- | --- | --- | +| E1 | M0 读写 v1,但不 enforce lifetime | 指定基线/#4917 | Registry codec 与 bootstrap 检查 | observed | Codec compatibility 不等于 identity admission | +| E2 | 已交付 fence 阻止 orphan guided bootstrap,但不执行 resolution | 指定基线 | Issue #4801 和 PR #4808 | observed | 公开 issue 和代码范围 | +| E3 | 全局条目是 source projection | 指定基线 | 检查 `loopx/global_registry.py` | observed | 静态当前代码事实 | +| E4 | Activation 排除所有受支持旧 effect 路径 | 未来 M1–M3 fixture | Object-only/codec-only/enforcement matrix | unverified | Activation 前需覆盖 source/global/warm-process | +| E5 | 每个持久 binding owner 验证精确 identity | 未来 M1-M3 fixture | Owner inventory conformance suite | unverified | Enforcement 前必须完成 | +| E6 | Resolution 可在 crash 后安全恢复 | 未来 M4 fixture | Fault-injection 和 restore matrix | unverified | 破坏性使用前必须完成 | + +## 附录 D:已拒绝或被替代的方案 + +- 只有 warning 的 writer capability marker 无法限制不理解 marker 的旧代码。 +- Redirect object 让旧路径继续可写,允许 split authority。 +- 全局 counter 会让 projection 成为 allocator,或要求永久 tombstone。 +- 自动 binding upgrade 只能按 alias 猜测历史,因此禁止。 +- 把全 provider cleanup 当作 commit precondition 会让 availability 耦合所有 + integration,且仍不能证明 external deletion。 +- Runtime-directory migration 无法解决非文件 binding,并且超出 issue #4801。 + +## 附录 E:事故与评审经验 + +- 当前状态 healthy 不能证明它与更早的同名 Goal 连续。 +- 若旧代码会忽略 compatibility check,advisory check 就不是 writer fence。 +- Crash safety 要求所有 plan 在首次 mutation 前写 journal,需要时预留 identity, + 并 reconcile 丢失 acknowledgement 的 effect。 +- Cleanup 与 authorization 是两个问题:stale record 可以保留用于 audit,同时 + 由精确 identity matching 保持 inert。 diff --git a/docs/architecture/rfcs/loopx-overall-roadmap-v0.md b/docs/architecture/rfcs/loopx-overall-roadmap-v0.md index 8b4abb2ed..401ec3706 100644 --- a/docs/architecture/rfcs/loopx-overall-roadmap-v0.md +++ b/docs/architecture/rfcs/loopx-overall-roadmap-v0.md @@ -509,6 +509,13 @@ L3 checkpoint: standalone acquisition/takeover, atomic claim admission and maint - **Exit:** affected real CLI/backend, immutable baseline versus candidate comparison, negative/mutation coverage, three-arm rehearsal and applicable D2 soak of at least ten days. D3 retains explicit cutover approval. This audit runs no new soak and promotes no provider. - **Rollback:** reviewed fenced export/import and schema-aware downgrade; replacing a binary cannot restore old write authority. +The [Goal instance/recovery proposal](goal-instance-identity-and-orphan-recovery-v0.md) +adds a bounded R5 dependency for R2/R3 retirement and late-result safety. Its M0 +codec is shipped; lifetime admission, commit fencing and recovery are not. Reuse +TS transaction and existing provider owners, qualify the local path first, and +keep R6 service identity and D1–D3 promotion separate. This checkpoint does not +activate identity or require every R1–R4 change to wait for the full lifecycle. + ### R6: Local/Cloud Convergence - **Owner:** shared-authority provider/service, session host adapters and collaboration. Extend existing PostgreSQL admission/identity-rotation foundations; do not rebuild the store. diff --git a/docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md b/docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md index a3e26f595..a8227cfe3 100644 --- a/docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md +++ b/docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md @@ -434,6 +434,12 @@ L3 检查点:独立领取/接管、原子 claim 准入与维护共用 typed le - **退出:** 相关真实 CLI/backend、不可变 baseline 与候选对照、负例/mutation、三臂演练及适用 D2 至少十日 soak;D3 切换保留明确批准。此次审计没有执行新的 soak,也未晋升 provider。 - **回滚:** 按已审阅的 fenced export/import 和 schema-aware downgrade,不能靠替换二进制恢复旧写权威。 +[Goal instance/recovery 提案](goal-instance-identity-and-orphan-recovery-v0.zh-CN.md) +为 R2/R3 retirement 和迟到结果安全提供有界 R5 依赖。M0 codec 已交付,lifetime +admission、commit fence 和 recovery 尚未交付。复用 TS transaction 与既有 provider +owner,先资格化本地路径;R6 service identity、D1–D3 promotion 独立验收。本检查点 +不激活 identity,也不要求所有 R1–R4 改动等待完整 lifecycle。 + ### R6:本地与云端汇合 - **Owner:** shared-authority provider/service、session host adapter、collaboration;基础是已有 PostgreSQL admission/identity-rotation seam,不重写 store。