From 22f0b794ae465a84017f783326d8708957178f6d Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:43:30 +0800 Subject: [PATCH 1/4] fix(storage): keep canonical SQLite creation opt-in for release Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../smoke/capability-configuration-smoke.ts | 10 ++++-- .../capability-localization.ts | 4 +-- ...shared-goal-authority-state-provider-v0.md | 21 ++++++----- ...-goal-authority-state-provider-v0.zh-CN.md | 5 +++ .../local-authority-provider-selection.md | 36 ++++++++++--------- docs/reference/sqlite-authority-store.md | 13 +++---- loopx/capabilities/configuration_ui.py | 2 +- .../machine_configuration/goal_storage.py | 14 ++++---- .../test_new_goal_storage_defaults.py | 32 +++++++++++++---- 9 files changed, 85 insertions(+), 52 deletions(-) diff --git a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts index b587db6a0b..d24733f19c 100644 --- a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts +++ b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts @@ -13,15 +13,19 @@ const periodicReportEditor = { }; const storageEditor = { fields: [{ key: "new_goal_provider" }, { key: "canonical_creation" }, { key: "new_goal_handoff_mode" }] }; -const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "sqlite", - canonical_creation: true, new_goal_handoff_mode: "hard_lease" }; +const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "file", + canonical_creation: false, new_goal_handoff_mode: "hard_lease" }; assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, goalStorageEditorValue({ schema_version: "loopx_goal_storage_defaults_v0", new_goal_provider: "file" }), storageDefault), { new_goal_provider: "file", canonical_creation: false, new_goal_handoff_mode: "hard_lease" }, "opening a historical selector must not enable canonical creation through today's defaults"); assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, goalStorageEditorValue(storageDefault), storageDefault), + { new_goal_provider: "file", canonical_creation: false, new_goal_handoff_mode: "hard_lease" }, + "an unconfigured device editor preserves explicit creation opt-in"); +assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, + goalStorageEditorValue({ ...storageDefault, new_goal_provider: "sqlite", canonical_creation: true }), storageDefault), { new_goal_provider: "sqlite", canonical_creation: true, new_goal_handoff_mode: "hard_lease" }, - "an unconfigured device editor shows the shared new-Goal default"); + "an explicit SQLite creation setting remains enabled"); assert.equal((goalStorageEditorValue({ ...storageDefault, canonical_creation: false }) as typeof storageDefault).canonical_creation, false, "an explicit v1 opt-out survives editor mode changes"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts b/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts index df82a8e38e..9e4ee2ae61 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts @@ -13,7 +13,7 @@ type FieldCopy = Record> = { en: { content_ops: {displayName: "Reference styles", description: "Retrieve source styles from an existing private catalog and prepare attributed outlines. No source or material-store writes."}, - goal_storage: { displayName: "New Goal authority", description: "New Goals default to canonical SQLite with hard leases. Choose storage and execution policy for future Goals. Existing Goals require a separate backed-up migration." }, + goal_storage: { displayName: "New Goal authority", description: "Opt in to canonical creation and choose storage and execution policy for future Goals. Existing Goals require a separate backed-up migration." }, manager_runtime: { displayName: "Runtime", description: "Selects the persistent host-tool profile used by owner manager conversations.", @@ -80,7 +80,7 @@ const capabilityCopy: Record> = { }, "zh-CN": { content_ops: {displayName: "素材与风格", description: "从原有私有目录检索表达结构,准备带来源署名的提纲;不执行来源或素材库写入。"}, - goal_storage: { displayName: "新 Goal 的权威存储", description: "新 Goal 默认使用 canonical SQLite 与 hard lease;可选择之后新 Goal 的存储与执行策略。已有 Goal 仍需单独备份、迁移。" }, + goal_storage: { displayName: "新 Goal 的权威存储", description: "显式启用 canonical 创建,并选择之后新 Goal 的存储与执行策略。已有 Goal 仍需单独备份、迁移。" }, manager_runtime: { displayName: "运行环境", description: "选择管家会话持续生效的宿主工具模式。", diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md index 448d54e49c..0a08078295 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md @@ -3336,17 +3336,16 @@ new implementation work; changing languages or moving a helper is not an exit. | C / L8: Whole-Goal rehearsal and cohort migration | Integrate one exact revision/profile after L2–L7; drain capture, fence old writers, verify canonical readback and projection, then rehearse fenced export/rollback. | D3 evidence packet binds lineage, cursor, source digest, command coverage and profile. Existing Goal migration requires explicit cohort approval; no per-command split authority or stale Markdown revival. | | D / L9: New-Goal default and bounded retirement | A dedicated default-change PR makes new-Goal creation/onboarding choose the qualified local profile, including settings/readback, installer and packaged clients. Retire old business writers only as their final callers and migration window close. | L8's integrated product/rollback qualification; distinguish new Goal default from existing Goal migration. Publish compatibility/disable guidance, keep explicit provider choice, permanent rendering and validated import/export. T4 can continue after the default ships. | -**L9 creation-default candidate.** Unconfigured new-Goal CLI/App creation now -uses the same registered SQLite/hard-lease configuration and existing TS -initialization owner. Explicit v0 selectors and v1 creation-off remain supported; -the guided editor preserves v0 opt-out when upgrading its envelope. Existing -Goals, frozen operations, provider receipts and migration fences retain their -own route. Namespace removal restores the new default, so disable guidance -uses an explicit creation-off setting. Candidate implementation and packaged -interaction evidence remain distinct from L8/D3 integrated recovery, D2 -sustained/platform qualification and release-default activation; none of those -gates is replaced by this checkpoint. No old writer or historical backup reader -is retired by this change. +**L9 release activation checkpoint.** The earlier SQLite/hard-lease default +candidate demonstrated shared configuration and typed initialization, including +v0 opt-out and frozen existing routes. The 1.4 release candidate restores the +1.3.1 unconfigured creation path and creation-off editor defaults while D2/D3 +release-default evidence remains incomplete. Explicit File/SQLite canonical +creation and either supported execution policy remain available. Namespace +removal disables future canonical creation and preserves existing authority, +original operations, receipts and fences. Candidate interaction evidence does +not replace sustained/platform qualification or integrated recovery. No old +writer or historical backup reader is retired by this release adjustment. **L9 App recovery checkpoint.** Post-commit creation must retain the original operation/workspace, initial Todo identities and accepted Session/Turn when diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md index f35bec1760..228dd6981b 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md @@ -2552,6 +2552,11 @@ SQLite 证明与 provider-neutral 归档恢复共用持有私有状态的 TS 重 | C/L8:整 Goal 演练与分组迁移 | L2–L7 后汇合一个精确 revision/profile;drain capture、fence 旧 writer、回读 canonical 与投影、演练 fenced export/rollback。 | D3 包绑定 lineage、cursor、source digest、命令覆盖和 profile;已有 Goal 分组迁移需明确批准,不能按命令拆 authority 或复活旧 Markdown。 | | D/L9:新 Goal 默认与有界退役 | 单独 default-change PR 让新建/onboarding 选择合格本地 profile,配齐 settings/readback、installer 和打包客户端;最后 caller 与迁移窗口退出才删除旧业务 writer。 | L8 整体产品/回滚资格;区分新 Goal 默认和已有 Goal 迁移。发布兼容/停用说明,保留显式 provider、永久 renderer 和合法 import/export。T4 可在默认启用后继续收尾。 | +**L9 发布启用检查点。** 1.4 发布候选在 D2/D3 默认资格未齐时恢复 1.3.1 的未配置创建 +路径和关闭的新建设置默认;显式 File/SQLite canonical 创建及两种执行策略保留。 +删除命名空间只关闭未来 canonical 新建,不改变已有 authority、原操作、回执与 fence。 +之前默认候选的打包交互证据不替代长期/平台/恢复资格,本调整不退役旧 writer 或备份 reader。 + **2026-09-24 基线核对。** 保留 claim 的 #4870、reviewed cutover #4888、shadow drain 规划 #4920 已合并,快照分页 #4922、SQLite runtime 准入 #4960 与刷新显示恢复 #4961 也已 合并,后续应验收组合 head,而不是继续沿用旧的 PR hold;SQLite 读取证明优化 #4931 当时仍在 diff --git a/docs/reference/local-authority-provider-selection.md b/docs/reference/local-authority-provider-selection.md index 2ab66a292a..174bfb6ed2 100644 --- a/docs/reference/local-authority-provider-selection.md +++ b/docs/reference/local-authority-provider-selection.md @@ -62,18 +62,20 @@ See [reviewed promotion and recovery](reviewed-coordination-promotion.md) for th ## New Goal authority (machine setting) **Settings → Capability Center → Device defaults → New Goal authority** selects -File or SQLite independently of the execution policy. In the new-Goal default -candidate, a device without a `goal_storage` namespace creates canonical SQLite -with `hard_lease`; CLI bootstrap and App creation reuse the same configuration -and typed initialization owner. **Create canonical authority** can be disabled -explicitly, or used with File and either `soft_claim` or `hard_lease`. Agents +File or SQLite independently of the execution policy. The 1.4 release candidate +preserves the 1.3.1 creation path when `goal_storage` is absent: it does not +initialize canonical authority or open SQLite. **Create canonical authority** +requires explicit opt-in, with File or SQLite and either `soft_claim` or +`hard_lease`. CLI bootstrap and App creation reuse the same configuration and +typed initialization owner. Agents inherit the Goal policy; this grants no tool, repository, scheduler, account or network permission and does not migrate existing data. -The candidate changes only unconfigured new creation. Released v0 settings and +SQLite default activation remains held on its separate D2/D3 qualification. +Released v0 settings and v1 with `canonical_creation=false` retain the post-promotion target behavior. Opening v0 in the guided editor previews a v1 envelope upgrade with creation -still disabled; switching editor modes cannot enable it through the new default. +still disabled; switching editor modes cannot enable it implicitly. The CLI continues to accept v0. Existing Goals keep their recorded target, including its absence, even when device settings change. Invalid configuration fails before registry publication rather than being treated as an absent setting. @@ -175,10 +177,11 @@ Released v0 preferences remain target-only when opened in the candidate editor. ![Existing File preference stays disabled](images/new-goal-authority/v0-retained.png) -Removing that preference restores canonical SQLite/hard-lease defaults, with -native removal readback. It does not migrate the existing Goals. +The following view records the earlier SQLite-default implementation candidate, +not the 1.4 opt-in release setting. In 1.4, removal restores creation-off and +leaves existing Goals on their recorded route. -![Candidate SQLite defaults after preference removal](images/new-goal-authority/sqlite-default.png) +![Historical SQLite-default candidate before release qualification](images/new-goal-authority/sqlite-default.png) The existing preview/apply revision gate is unchanged. Unsupported `legacy` policy is rejected before apply; correct the policy and preview again. No new @@ -187,8 +190,8 @@ layout or responsive navigation is introduced. To disable future canonical creation, preview and apply the same v1 document -with `canonical_creation=false`. Removing the preference restores the candidate -SQLite/hard-lease default for future new Goals; removal is **not** opt-out. +with `canonical_creation=false`. Removing the preference also disables future +canonical creation; neither action migrates existing Goals. To remove the whole preference: ```sh @@ -213,8 +216,9 @@ default admission remain separate; the existing RFC acceptance is unchanged. ### 新 Goal 的权威存储 在“设置 → 能力中心 → 此设备默认 → 新 Goal 的权威存储”中,分别选择 File/SQLite -和 `soft_claim`/`hard_lease`。候选实现让未配置此命名空间的新 Goal 默认建立 -canonical SQLite、使用 `hard_lease`,CLI 与 App 共用配置和类型化创建归属。 +和 `soft_claim`/`hard_lease`。1.4 发布候选保留 1.3.1 未配置命名空间时的创建路径, +不初始化 canonical authority 或打开 SQLite;canonical 创建仍须显式启用。 +CLI 与 App 共用配置和类型化创建归属。SQLite 默认启用仍受独立 D2/D3 资格门约束。 旧 v0 或显式关闭状态仍只固定晋升后的目标;表单以关闭状态预览 v1 升级, 切换编辑模式也不会自动启用。CLI 继续接受旧格式,已有 Goal 保留原路径。 Registry 中的相对 runtime 路径按实际文件位置比较;等价路径不会误触发权威迁移检查。 @@ -231,8 +235,8 @@ CLI 使用上面的完整 JSON 和 preview/apply/inspect/bootstrap 命令;App 后的存储丢失须恢复完整备份,不能重新创建空库;通用 force 不能重建。原回执在 后续写入后仍可读回,重试不得丢失或重复 Todo。 -显式设置 `canonical_creation=false` 才关闭之后的新建;remove 删除偏好会恢复候选 -SQLite/hard-lease 默认,不能用于关闭。两者均不会迁回已有数据、 +显式设置 `canonical_creation=false` 或 remove 删除偏好,都会关闭之后的 canonical +新建。两者均不会迁回已有数据、 删除 fence 或重新开放旧 writer。既有 Goal 升级仍需备份、停止写入、结算租约和 审核计划;反向迁移须保留新增写入。受支持的旧备份、格式和原回执恢复能力保留。 配置损坏明确失败,不视为未配置;候选实现不代表完整升级、D2 长期资格或发布默认已通过。 diff --git a/docs/reference/sqlite-authority-store.md b/docs/reference/sqlite-authority-store.md index 6f3905ccdf..d55dcfce82 100644 --- a/docs/reference/sqlite-authority-store.md +++ b/docs/reference/sqlite-authority-store.md @@ -113,13 +113,14 @@ Before changing release defaults, the existing owners must close these gaps: including installed runtime admission, settings/readback and packaged entry points. Keep explicit provider selection and reviewed backup/rollback. -The L9 implementation candidate now resolves an absent `goal_storage` namespace -to canonical SQLite/hard-lease creation through the existing configuration and -TS initialization owners. Explicit v0 selectors, v1 opt-out and existing Goals -keep their recorded route. Settings preserves v0 opt-out during guided/JSON -conversion; removing a setting restores the new default. See [new-Goal creation +The L9 SQLite-default implementation remains a future activation target. The +1.4 release candidate preserves the 1.3.1 unconfigured creation path until the +separate default-qualification gates close. Explicit v1 canonical creation, +provider/policy selection and v0 compatibility remain available; removing the +namespace disables future canonical creation without retargeting existing Goals. +Settings and CLI share the same configuration owner. See [new-Goal creation and compatibility](local-authority-provider-selection.md#new-goal-authority-machine-setting). -This candidate does not certify the gates above or activate an installed release. +This release preparation does not certify D2 or activate a SQLite default. Both current local providers require Node >=22.22.3. SQLite uses built-in `node:sqlite`: it adds no database service or external SQLite package. Its diff --git a/loopx/capabilities/configuration_ui.py b/loopx/capabilities/configuration_ui.py index b88a7ad7ef..2b81266a7f 100644 --- a/loopx/capabilities/configuration_ui.py +++ b/loopx/capabilities/configuration_ui.py @@ -99,7 +99,7 @@ def capability_configuration_editor( options=["file", "sqlite"], required=True, description="Fixed at creation. Existing Goals need a separate backed-up migration."), _field("canonical_creation", "Create canonical authority", "boolean", required=True, - description="Enabled by default for new Goals. Disabled retains the post-promotion target behavior."), + description="Explicit opt-in for new Goals. Disabled retains the post-promotion target behavior."), _field("new_goal_handoff_mode", "New Goal execution policy", "select", options=["soft_claim", "hard_lease"], required=True, description="Used only with canonical creation. Agents inherit the Goal policy; this grants no tool permissions.")], diff --git a/loopx/capabilities/machine_configuration/goal_storage.py b/loopx/capabilities/machine_configuration/goal_storage.py index 526592c315..6e27003a2a 100644 --- a/loopx/capabilities/machine_configuration/goal_storage.py +++ b/loopx/capabilities/machine_configuration/goal_storage.py @@ -32,25 +32,25 @@ def goal_storage_machine_configuration_namespace() -> MachineConfigurationNamesp normalize=normalize_goal_storage_defaults, project_public=dict, apply_public_update=lambda _current, update: dict(update), title="New Goal authority", - description=("New Goals default to canonical SQLite with hard leases. Choose storage and execution policy. " + description=("Opt in to canonical creation and choose storage and execution policy. " "Fixed for future Goals only; existing data requires a separate reviewed migration."), - default_configuration={"schema_version": CANONICAL_GOAL_STORAGE_DEFAULTS_SCHEMA, "new_goal_provider": "sqlite", - "canonical_creation": True, "new_goal_handoff_mode": "hard_lease"}, + default_configuration={"schema_version": CANONICAL_GOAL_STORAGE_DEFAULTS_SCHEMA, "new_goal_provider": "file", + "canonical_creation": False, "new_goal_handoff_mode": "hard_lease"}, documentation={"path": "docs/reference/local-authority-provider-selection.md", "url": "https://github.com/loopx-project/loopx/blob/main/docs/reference/local-authority-provider-selection.md"}, ) -def new_goal_storage_target(runtime_root: Path) -> dict[str, Any]: +def new_goal_storage_target(runtime_root: Path) -> dict[str, Any] | None: from .builtins import build_builtin_machine_configuration_registry from .store import read_machine_configuration registry = build_builtin_machine_configuration_registry() configuration = read_machine_configuration(runtime_root, registry=registry) raw = (configuration or {}).get("namespaces", {}).get("goal_storage") if raw is None: - # Reuse the registered configuration owner also projected by Settings. - # Existing Goals do not enter this creation-time resolution path. - raw = registry.resolve("goal_storage").default_configuration + # Preserve the released creation path until the separate SQLite + # default qualification is complete. Explicit settings remain TS-owned. + return None return effect_runtime_result(NEW_GOAL_STORAGE_METHOD, {"action": "resolve", "configuration": raw}) diff --git a/tests/control_plane/test_new_goal_storage_defaults.py b/tests/control_plane/test_new_goal_storage_defaults.py index 9f86f04654..7a98ffd136 100644 --- a/tests/control_plane/test_new_goal_storage_defaults.py +++ b/tests/control_plane/test_new_goal_storage_defaults.py @@ -75,14 +75,34 @@ def test_existing_implicit_file_goal_is_not_retargeted(environment): @pytest.mark.parametrize("configuration_present", [False, True]) -def test_unconfigured_new_goal_defaults_to_canonical_sqlite_and_retains_later_writes(environment, configuration_present): +def test_unconfigured_new_goal_preserves_released_creation_without_sqlite(environment, configuration_present): + _, bootstrap, marker, project, runtime = environment + if configuration_present: + (runtime / "machine/configuration.json").write_text(json.dumps({ + "schema_version": "loopx_machine_configuration_v0", "namespaces": { + "todo_replan_cadence": {"schema_version": "todo_replan_cadence_machine_defaults_v1", + "count_unit": "completed_todos", "count": 5}}})) + assert bootstrap("first", "--dry-run")["storage_target"] is None + created = bootstrap() + assert created["storage_target"] is None + assert created["storage_selection"] is None + assert not marker().exists() + assert not (runtime / "authority/sqlite-v0").exists() + added = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(project / ".loopx/registry.json"), + "--runtime-root", str(runtime), "--format", "json", "todo", "add", "--goal-id", "first", "--role", "agent", + "--text", "Retain the released creation path"], capture_output=True, text=True, timeout=60) + assert added.returncode == 0, added.stdout + added.stderr + restart_effect_runtime() + assert bootstrap()["storage_selection"] is None + assert "Retain the released creation path" in Path(created["state_file"]).read_text() + + +def test_explicit_sqlite_creation_retains_later_writes_after_setting_removal(environment): from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted configure, bootstrap, _, project, runtime = environment + configure("sqlite", mode="hard_lease") configuration = runtime / "machine/configuration.json" - if configuration_present: - configuration.write_text(json.dumps({"schema_version": "loopx_machine_configuration_v0", "namespaces": { - "todo_replan_cadence": {"schema_version": "todo_replan_cadence_machine_defaults_v1", "count_unit": "completed_todos", "count": 5}}})) target = {"schema_version": "loopx_new_goal_storage_target_v1", "provider": "sqlite", "handoff_mode": "hard_lease"} assert bootstrap("first", "--dry-run")["storage_target"] == target assert not (runtime / "authority-transition").exists() @@ -91,13 +111,13 @@ def test_unconfigured_new_goal_defaults_to_canonical_sqlite_and_retains_later_wr assert created["storage_selection"]["authority_initialized"] is True added = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(project / ".loopx/registry.json"), "--runtime-root", str(runtime), "--format", "json", "todo", "add", "--goal-id", "first", "--role", "agent", - "--text", "Retain the default Goal's later work"], capture_output=True, text=True, timeout=60) + "--text", "Retain the opted-in Goal's later work"], capture_output=True, text=True, timeout=60) assert added.returncode == 0, added.stdout + added.stderr before = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="first", include_leases=True) assert before["source_authority"] == "sqlite_v0" assert before["handoff_mode"] == "hard_lease" assert len(before["todos"]) == 1 - configure("file", mode="soft_claim") + configuration.unlink() Path(created["state_file"]).unlink() restart_effect_runtime() retried = bootstrap() From 07727ebd84bfc25c7f77686b79e8e3eabf35189b Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:43:30 +0800 Subject: [PATCH 2/4] test(chat): align machine catalog with six-Turn cadence Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- tests/test_chat_machine_configuration_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_chat_machine_configuration_api.py b/tests/test_chat_machine_configuration_api.py index 34b0d877a2..adf21d44f2 100644 --- a/tests/test_chat_machine_configuration_api.py +++ b/tests/test_chat_machine_configuration_api.py @@ -352,7 +352,7 @@ def test_inspection_lists_registered_namespaces_without_local_refs( assert namespace_catalog["todo_replan_cadence"]["configuration_template"] == { "schema_version": "todo_replan_cadence_machine_defaults_v1", "count_unit": "effective_turns", - "count": 5, + "count": 6, } assert namespace_catalog["change_quality_qualification"][ "configuration_template" From cd06c16dbe7bbb80ee5966247e73f7cc29fab303 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:43:30 +0800 Subject: [PATCH 3/4] chore(release): prepare version 1.4.0 Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/book/chapters/00-reading-guide.md | 2 +- docs/book/en/chapters/00-reading-guide.md | 4 ++-- docs/book/en/index.md | 2 +- docs/book/index.md | 2 +- loopx/__init__.py | 2 +- man/loopx.1 | 2 +- pyproject.toml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/book/chapters/00-reading-guide.md b/docs/book/chapters/00-reading-guide.md index e444a7dc92..96a65b06ed 100644 --- a/docs/book/chapters/00-reading-guide.md +++ b/docs/book/chapters/00-reading-guide.md @@ -180,7 +180,7 @@ lifecycle 选择,不是所有贡献的默认终点。 ## 版本基线 -当前内容面向 LoopX release `v1.3.1`;本地命令示例已按 `loopx 1.3.1` 的 +当前内容面向 LoopX release `v1.4.0`;本地命令示例已按 `loopx 1.4.0` 的 公开 CLI 与协议表面复核。该版本要求 Python 3.11+ 与 Node.js 22.22.3+。LoopX 会自动管理 空闲后退出的 TypeScript Effect runtime,用户不需要手工维护 daemon。 diff --git a/docs/book/en/chapters/00-reading-guide.md b/docs/book/en/chapters/00-reading-guide.md index 691ef99773..4c37a259a3 100644 --- a/docs/book/en/chapters/00-reading-guide.md +++ b/docs/book/en/chapters/00-reading-guide.md @@ -199,8 +199,8 @@ Do not bypass a newer permission or lifecycle check just to make an older exampl ## Version baseline -This book targets LoopX release `v1.3.1`. Local command examples were checked against -the public `loopx 1.3.1` CLI and protocol surface. It requires Python 3.11+ and Node.js 22.22.3+. +This book targets LoopX release `v1.4.0`. Local command examples were checked against +the public `loopx 1.4.0` CLI and protocol surface. It requires Python 3.11+ and Node.js 22.22.3+. LoopX starts and reuses its managed, idle-exiting TypeScript Effect runtime automatically; users do not operate that runtime as a manual daemon. diff --git a/docs/book/en/index.md b/docs/book/en/index.md index 075fd60a2a..b63b7a8a47 100644 --- a/docs/book/en/index.md +++ b/docs/book/en/index.md @@ -79,7 +79,7 @@ versioned or optional functionality, not the default shape of every contribution - Source format: Markdown - Site generator: MkDocs Material - Hosting: GitHub Pages -- LoopX release anchor: `v1.3.1` +- LoopX release anchor: `v1.4.0` - Runtime prerequisites: Python 3.11+ and Node.js 22.22.3+ The official public protocols remain authoritative for protocol facts. Commands that change across diff --git a/docs/book/index.md b/docs/book/index.md index 479ec9a5dc..bb358d3d96 100644 --- a/docs/book/index.md +++ b/docs/book/index.md @@ -70,7 +70,7 @@ Extension 只是可独立版本化和交付的一种路径。 - 正文格式:Markdown; - 站点生成器:MkDocs Material; - 在线发布:GitHub Pages; -- LoopX 发布锚点:`v1.3.1`; +- LoopX 发布锚点:`v1.4.0`; - 运行时前提:Python 3.11+ 与 Node.js 22.22.3+。 协议解释以 LoopX 官方公开合同为事实源。易变化的命令仍以对应发布物、官方文档和当前 diff --git a/loopx/__init__.py b/loopx/__init__.py index 49d4a8b9d4..eb16acc782 100644 --- a/loopx/__init__.py +++ b/loopx/__init__.py @@ -2,4 +2,4 @@ __all__ = ["__version__"] -__version__ = "1.3.1" +__version__ = "1.4.0" diff --git a/man/loopx.1 b/man/loopx.1 index fe478647f5..156ebef2a2 100644 --- a/man/loopx.1 +++ b/man/loopx.1 @@ -1,4 +1,4 @@ -.TH LOOPX 1 "" "LoopX 1.3.1" "User Commands" +.TH LOOPX 1 "" "LoopX 1.4.0" "User Commands" .SH NAME loopx \- control-plane helper for long-running agent work .SH SYNOPSIS diff --git a/pyproject.toml b/pyproject.toml index 1c1999a476..8827e33c17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "loopx" -version = "1.3.1" +version = "1.4.0" description = "A Loop Engineering control plane for long-running agent goals (requires Node.js 22.22.3+)." readme = "README.md" requires-python = ">=3.11" From 42e7c9620167650be870ebb590f1531bc7dec4c9 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:47:36 +0800 Subject: [PATCH 4/4] Revert "fix(storage): keep canonical SQLite creation opt-in for release" This reverts commit 22f0b794ae465a84017f783326d8708957178f6d. Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../smoke/capability-configuration-smoke.ts | 10 ++---- .../capability-localization.ts | 4 +-- ...shared-goal-authority-state-provider-v0.md | 21 +++++------ ...-goal-authority-state-provider-v0.zh-CN.md | 5 --- .../local-authority-provider-selection.md | 36 +++++++++---------- docs/reference/sqlite-authority-store.md | 13 ++++--- loopx/capabilities/configuration_ui.py | 2 +- .../machine_configuration/goal_storage.py | 14 ++++---- .../test_new_goal_storage_defaults.py | 32 ++++------------- 9 files changed, 52 insertions(+), 85 deletions(-) diff --git a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts index d24733f19c..b587db6a0b 100644 --- a/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts +++ b/apps/presentation/dashboard/smoke/capability-configuration-smoke.ts @@ -13,19 +13,15 @@ const periodicReportEditor = { }; const storageEditor = { fields: [{ key: "new_goal_provider" }, { key: "canonical_creation" }, { key: "new_goal_handoff_mode" }] }; -const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "file", - canonical_creation: false, new_goal_handoff_mode: "hard_lease" }; +const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "sqlite", + canonical_creation: true, new_goal_handoff_mode: "hard_lease" }; assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, goalStorageEditorValue({ schema_version: "loopx_goal_storage_defaults_v0", new_goal_provider: "file" }), storageDefault), { new_goal_provider: "file", canonical_creation: false, new_goal_handoff_mode: "hard_lease" }, "opening a historical selector must not enable canonical creation through today's defaults"); assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, goalStorageEditorValue(storageDefault), storageDefault), - { new_goal_provider: "file", canonical_creation: false, new_goal_handoff_mode: "hard_lease" }, - "an unconfigured device editor preserves explicit creation opt-in"); -assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor, - goalStorageEditorValue({ ...storageDefault, new_goal_provider: "sqlite", canonical_creation: true }), storageDefault), { new_goal_provider: "sqlite", canonical_creation: true, new_goal_handoff_mode: "hard_lease" }, - "an explicit SQLite creation setting remains enabled"); + "an unconfigured device editor shows the shared new-Goal default"); assert.equal((goalStorageEditorValue({ ...storageDefault, canonical_creation: false }) as typeof storageDefault).canonical_creation, false, "an explicit v1 opt-out survives editor mode changes"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts b/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts index 9e4ee2ae61..df82a8e38e 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/capability-localization.ts @@ -13,7 +13,7 @@ type FieldCopy = Record> = { en: { content_ops: {displayName: "Reference styles", description: "Retrieve source styles from an existing private catalog and prepare attributed outlines. No source or material-store writes."}, - goal_storage: { displayName: "New Goal authority", description: "Opt in to canonical creation and choose storage and execution policy for future Goals. Existing Goals require a separate backed-up migration." }, + goal_storage: { displayName: "New Goal authority", description: "New Goals default to canonical SQLite with hard leases. Choose storage and execution policy for future Goals. Existing Goals require a separate backed-up migration." }, manager_runtime: { displayName: "Runtime", description: "Selects the persistent host-tool profile used by owner manager conversations.", @@ -80,7 +80,7 @@ const capabilityCopy: Record> = { }, "zh-CN": { content_ops: {displayName: "素材与风格", description: "从原有私有目录检索表达结构,准备带来源署名的提纲;不执行来源或素材库写入。"}, - goal_storage: { displayName: "新 Goal 的权威存储", description: "显式启用 canonical 创建,并选择之后新 Goal 的存储与执行策略。已有 Goal 仍需单独备份、迁移。" }, + goal_storage: { displayName: "新 Goal 的权威存储", description: "新 Goal 默认使用 canonical SQLite 与 hard lease;可选择之后新 Goal 的存储与执行策略。已有 Goal 仍需单独备份、迁移。" }, manager_runtime: { displayName: "运行环境", description: "选择管家会话持续生效的宿主工具模式。", diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md index 0a08078295..448d54e49c 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md @@ -3336,16 +3336,17 @@ new implementation work; changing languages or moving a helper is not an exit. | C / L8: Whole-Goal rehearsal and cohort migration | Integrate one exact revision/profile after L2–L7; drain capture, fence old writers, verify canonical readback and projection, then rehearse fenced export/rollback. | D3 evidence packet binds lineage, cursor, source digest, command coverage and profile. Existing Goal migration requires explicit cohort approval; no per-command split authority or stale Markdown revival. | | D / L9: New-Goal default and bounded retirement | A dedicated default-change PR makes new-Goal creation/onboarding choose the qualified local profile, including settings/readback, installer and packaged clients. Retire old business writers only as their final callers and migration window close. | L8's integrated product/rollback qualification; distinguish new Goal default from existing Goal migration. Publish compatibility/disable guidance, keep explicit provider choice, permanent rendering and validated import/export. T4 can continue after the default ships. | -**L9 release activation checkpoint.** The earlier SQLite/hard-lease default -candidate demonstrated shared configuration and typed initialization, including -v0 opt-out and frozen existing routes. The 1.4 release candidate restores the -1.3.1 unconfigured creation path and creation-off editor defaults while D2/D3 -release-default evidence remains incomplete. Explicit File/SQLite canonical -creation and either supported execution policy remain available. Namespace -removal disables future canonical creation and preserves existing authority, -original operations, receipts and fences. Candidate interaction evidence does -not replace sustained/platform qualification or integrated recovery. No old -writer or historical backup reader is retired by this release adjustment. +**L9 creation-default candidate.** Unconfigured new-Goal CLI/App creation now +uses the same registered SQLite/hard-lease configuration and existing TS +initialization owner. Explicit v0 selectors and v1 creation-off remain supported; +the guided editor preserves v0 opt-out when upgrading its envelope. Existing +Goals, frozen operations, provider receipts and migration fences retain their +own route. Namespace removal restores the new default, so disable guidance +uses an explicit creation-off setting. Candidate implementation and packaged +interaction evidence remain distinct from L8/D3 integrated recovery, D2 +sustained/platform qualification and release-default activation; none of those +gates is replaced by this checkpoint. No old writer or historical backup reader +is retired by this change. **L9 App recovery checkpoint.** Post-commit creation must retain the original operation/workspace, initial Todo identities and accepted Session/Turn when diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md index 228dd6981b..f35bec1760 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md @@ -2552,11 +2552,6 @@ SQLite 证明与 provider-neutral 归档恢复共用持有私有状态的 TS 重 | C/L8:整 Goal 演练与分组迁移 | L2–L7 后汇合一个精确 revision/profile;drain capture、fence 旧 writer、回读 canonical 与投影、演练 fenced export/rollback。 | D3 包绑定 lineage、cursor、source digest、命令覆盖和 profile;已有 Goal 分组迁移需明确批准,不能按命令拆 authority 或复活旧 Markdown。 | | D/L9:新 Goal 默认与有界退役 | 单独 default-change PR 让新建/onboarding 选择合格本地 profile,配齐 settings/readback、installer 和打包客户端;最后 caller 与迁移窗口退出才删除旧业务 writer。 | L8 整体产品/回滚资格;区分新 Goal 默认和已有 Goal 迁移。发布兼容/停用说明,保留显式 provider、永久 renderer 和合法 import/export。T4 可在默认启用后继续收尾。 | -**L9 发布启用检查点。** 1.4 发布候选在 D2/D3 默认资格未齐时恢复 1.3.1 的未配置创建 -路径和关闭的新建设置默认;显式 File/SQLite canonical 创建及两种执行策略保留。 -删除命名空间只关闭未来 canonical 新建,不改变已有 authority、原操作、回执与 fence。 -之前默认候选的打包交互证据不替代长期/平台/恢复资格,本调整不退役旧 writer 或备份 reader。 - **2026-09-24 基线核对。** 保留 claim 的 #4870、reviewed cutover #4888、shadow drain 规划 #4920 已合并,快照分页 #4922、SQLite runtime 准入 #4960 与刷新显示恢复 #4961 也已 合并,后续应验收组合 head,而不是继续沿用旧的 PR hold;SQLite 读取证明优化 #4931 当时仍在 diff --git a/docs/reference/local-authority-provider-selection.md b/docs/reference/local-authority-provider-selection.md index 174bfb6ed2..2ab66a292a 100644 --- a/docs/reference/local-authority-provider-selection.md +++ b/docs/reference/local-authority-provider-selection.md @@ -62,20 +62,18 @@ See [reviewed promotion and recovery](reviewed-coordination-promotion.md) for th ## New Goal authority (machine setting) **Settings → Capability Center → Device defaults → New Goal authority** selects -File or SQLite independently of the execution policy. The 1.4 release candidate -preserves the 1.3.1 creation path when `goal_storage` is absent: it does not -initialize canonical authority or open SQLite. **Create canonical authority** -requires explicit opt-in, with File or SQLite and either `soft_claim` or -`hard_lease`. CLI bootstrap and App creation reuse the same configuration and -typed initialization owner. Agents +File or SQLite independently of the execution policy. In the new-Goal default +candidate, a device without a `goal_storage` namespace creates canonical SQLite +with `hard_lease`; CLI bootstrap and App creation reuse the same configuration +and typed initialization owner. **Create canonical authority** can be disabled +explicitly, or used with File and either `soft_claim` or `hard_lease`. Agents inherit the Goal policy; this grants no tool, repository, scheduler, account or network permission and does not migrate existing data. -SQLite default activation remains held on its separate D2/D3 qualification. -Released v0 settings and +The candidate changes only unconfigured new creation. Released v0 settings and v1 with `canonical_creation=false` retain the post-promotion target behavior. Opening v0 in the guided editor previews a v1 envelope upgrade with creation -still disabled; switching editor modes cannot enable it implicitly. +still disabled; switching editor modes cannot enable it through the new default. The CLI continues to accept v0. Existing Goals keep their recorded target, including its absence, even when device settings change. Invalid configuration fails before registry publication rather than being treated as an absent setting. @@ -177,11 +175,10 @@ Released v0 preferences remain target-only when opened in the candidate editor. ![Existing File preference stays disabled](images/new-goal-authority/v0-retained.png) -The following view records the earlier SQLite-default implementation candidate, -not the 1.4 opt-in release setting. In 1.4, removal restores creation-off and -leaves existing Goals on their recorded route. +Removing that preference restores canonical SQLite/hard-lease defaults, with +native removal readback. It does not migrate the existing Goals. -![Historical SQLite-default candidate before release qualification](images/new-goal-authority/sqlite-default.png) +![Candidate SQLite defaults after preference removal](images/new-goal-authority/sqlite-default.png) The existing preview/apply revision gate is unchanged. Unsupported `legacy` policy is rejected before apply; correct the policy and preview again. No new @@ -190,8 +187,8 @@ layout or responsive navigation is introduced. To disable future canonical creation, preview and apply the same v1 document -with `canonical_creation=false`. Removing the preference also disables future -canonical creation; neither action migrates existing Goals. +with `canonical_creation=false`. Removing the preference restores the candidate +SQLite/hard-lease default for future new Goals; removal is **not** opt-out. To remove the whole preference: ```sh @@ -216,9 +213,8 @@ default admission remain separate; the existing RFC acceptance is unchanged. ### 新 Goal 的权威存储 在“设置 → 能力中心 → 此设备默认 → 新 Goal 的权威存储”中,分别选择 File/SQLite -和 `soft_claim`/`hard_lease`。1.4 发布候选保留 1.3.1 未配置命名空间时的创建路径, -不初始化 canonical authority 或打开 SQLite;canonical 创建仍须显式启用。 -CLI 与 App 共用配置和类型化创建归属。SQLite 默认启用仍受独立 D2/D3 资格门约束。 +和 `soft_claim`/`hard_lease`。候选实现让未配置此命名空间的新 Goal 默认建立 +canonical SQLite、使用 `hard_lease`,CLI 与 App 共用配置和类型化创建归属。 旧 v0 或显式关闭状态仍只固定晋升后的目标;表单以关闭状态预览 v1 升级, 切换编辑模式也不会自动启用。CLI 继续接受旧格式,已有 Goal 保留原路径。 Registry 中的相对 runtime 路径按实际文件位置比较;等价路径不会误触发权威迁移检查。 @@ -235,8 +231,8 @@ CLI 使用上面的完整 JSON 和 preview/apply/inspect/bootstrap 命令;App 后的存储丢失须恢复完整备份,不能重新创建空库;通用 force 不能重建。原回执在 后续写入后仍可读回,重试不得丢失或重复 Todo。 -显式设置 `canonical_creation=false` 或 remove 删除偏好,都会关闭之后的 canonical -新建。两者均不会迁回已有数据、 +显式设置 `canonical_creation=false` 才关闭之后的新建;remove 删除偏好会恢复候选 +SQLite/hard-lease 默认,不能用于关闭。两者均不会迁回已有数据、 删除 fence 或重新开放旧 writer。既有 Goal 升级仍需备份、停止写入、结算租约和 审核计划;反向迁移须保留新增写入。受支持的旧备份、格式和原回执恢复能力保留。 配置损坏明确失败,不视为未配置;候选实现不代表完整升级、D2 长期资格或发布默认已通过。 diff --git a/docs/reference/sqlite-authority-store.md b/docs/reference/sqlite-authority-store.md index d55dcfce82..6f3905ccdf 100644 --- a/docs/reference/sqlite-authority-store.md +++ b/docs/reference/sqlite-authority-store.md @@ -113,14 +113,13 @@ Before changing release defaults, the existing owners must close these gaps: including installed runtime admission, settings/readback and packaged entry points. Keep explicit provider selection and reviewed backup/rollback. -The L9 SQLite-default implementation remains a future activation target. The -1.4 release candidate preserves the 1.3.1 unconfigured creation path until the -separate default-qualification gates close. Explicit v1 canonical creation, -provider/policy selection and v0 compatibility remain available; removing the -namespace disables future canonical creation without retargeting existing Goals. -Settings and CLI share the same configuration owner. See [new-Goal creation +The L9 implementation candidate now resolves an absent `goal_storage` namespace +to canonical SQLite/hard-lease creation through the existing configuration and +TS initialization owners. Explicit v0 selectors, v1 opt-out and existing Goals +keep their recorded route. Settings preserves v0 opt-out during guided/JSON +conversion; removing a setting restores the new default. See [new-Goal creation and compatibility](local-authority-provider-selection.md#new-goal-authority-machine-setting). -This release preparation does not certify D2 or activate a SQLite default. +This candidate does not certify the gates above or activate an installed release. Both current local providers require Node >=22.22.3. SQLite uses built-in `node:sqlite`: it adds no database service or external SQLite package. Its diff --git a/loopx/capabilities/configuration_ui.py b/loopx/capabilities/configuration_ui.py index 2b81266a7f..b88a7ad7ef 100644 --- a/loopx/capabilities/configuration_ui.py +++ b/loopx/capabilities/configuration_ui.py @@ -99,7 +99,7 @@ def capability_configuration_editor( options=["file", "sqlite"], required=True, description="Fixed at creation. Existing Goals need a separate backed-up migration."), _field("canonical_creation", "Create canonical authority", "boolean", required=True, - description="Explicit opt-in for new Goals. Disabled retains the post-promotion target behavior."), + description="Enabled by default for new Goals. Disabled retains the post-promotion target behavior."), _field("new_goal_handoff_mode", "New Goal execution policy", "select", options=["soft_claim", "hard_lease"], required=True, description="Used only with canonical creation. Agents inherit the Goal policy; this grants no tool permissions.")], diff --git a/loopx/capabilities/machine_configuration/goal_storage.py b/loopx/capabilities/machine_configuration/goal_storage.py index 6e27003a2a..526592c315 100644 --- a/loopx/capabilities/machine_configuration/goal_storage.py +++ b/loopx/capabilities/machine_configuration/goal_storage.py @@ -32,25 +32,25 @@ def goal_storage_machine_configuration_namespace() -> MachineConfigurationNamesp normalize=normalize_goal_storage_defaults, project_public=dict, apply_public_update=lambda _current, update: dict(update), title="New Goal authority", - description=("Opt in to canonical creation and choose storage and execution policy. " + description=("New Goals default to canonical SQLite with hard leases. Choose storage and execution policy. " "Fixed for future Goals only; existing data requires a separate reviewed migration."), - default_configuration={"schema_version": CANONICAL_GOAL_STORAGE_DEFAULTS_SCHEMA, "new_goal_provider": "file", - "canonical_creation": False, "new_goal_handoff_mode": "hard_lease"}, + default_configuration={"schema_version": CANONICAL_GOAL_STORAGE_DEFAULTS_SCHEMA, "new_goal_provider": "sqlite", + "canonical_creation": True, "new_goal_handoff_mode": "hard_lease"}, documentation={"path": "docs/reference/local-authority-provider-selection.md", "url": "https://github.com/loopx-project/loopx/blob/main/docs/reference/local-authority-provider-selection.md"}, ) -def new_goal_storage_target(runtime_root: Path) -> dict[str, Any] | None: +def new_goal_storage_target(runtime_root: Path) -> dict[str, Any]: from .builtins import build_builtin_machine_configuration_registry from .store import read_machine_configuration registry = build_builtin_machine_configuration_registry() configuration = read_machine_configuration(runtime_root, registry=registry) raw = (configuration or {}).get("namespaces", {}).get("goal_storage") if raw is None: - # Preserve the released creation path until the separate SQLite - # default qualification is complete. Explicit settings remain TS-owned. - return None + # Reuse the registered configuration owner also projected by Settings. + # Existing Goals do not enter this creation-time resolution path. + raw = registry.resolve("goal_storage").default_configuration return effect_runtime_result(NEW_GOAL_STORAGE_METHOD, {"action": "resolve", "configuration": raw}) diff --git a/tests/control_plane/test_new_goal_storage_defaults.py b/tests/control_plane/test_new_goal_storage_defaults.py index 7a98ffd136..9f86f04654 100644 --- a/tests/control_plane/test_new_goal_storage_defaults.py +++ b/tests/control_plane/test_new_goal_storage_defaults.py @@ -75,34 +75,14 @@ def test_existing_implicit_file_goal_is_not_retargeted(environment): @pytest.mark.parametrize("configuration_present", [False, True]) -def test_unconfigured_new_goal_preserves_released_creation_without_sqlite(environment, configuration_present): - _, bootstrap, marker, project, runtime = environment - if configuration_present: - (runtime / "machine/configuration.json").write_text(json.dumps({ - "schema_version": "loopx_machine_configuration_v0", "namespaces": { - "todo_replan_cadence": {"schema_version": "todo_replan_cadence_machine_defaults_v1", - "count_unit": "completed_todos", "count": 5}}})) - assert bootstrap("first", "--dry-run")["storage_target"] is None - created = bootstrap() - assert created["storage_target"] is None - assert created["storage_selection"] is None - assert not marker().exists() - assert not (runtime / "authority/sqlite-v0").exists() - added = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(project / ".loopx/registry.json"), - "--runtime-root", str(runtime), "--format", "json", "todo", "add", "--goal-id", "first", "--role", "agent", - "--text", "Retain the released creation path"], capture_output=True, text=True, timeout=60) - assert added.returncode == 0, added.stdout + added.stderr - restart_effect_runtime() - assert bootstrap()["storage_selection"] is None - assert "Retain the released creation path" in Path(created["state_file"]).read_text() - - -def test_explicit_sqlite_creation_retains_later_writes_after_setting_removal(environment): +def test_unconfigured_new_goal_defaults_to_canonical_sqlite_and_retains_later_writes(environment, configuration_present): from loopx.control_plane.coordination.local_authority import read_canonical_todos_if_promoted configure, bootstrap, _, project, runtime = environment - configure("sqlite", mode="hard_lease") configuration = runtime / "machine/configuration.json" + if configuration_present: + configuration.write_text(json.dumps({"schema_version": "loopx_machine_configuration_v0", "namespaces": { + "todo_replan_cadence": {"schema_version": "todo_replan_cadence_machine_defaults_v1", "count_unit": "completed_todos", "count": 5}}})) target = {"schema_version": "loopx_new_goal_storage_target_v1", "provider": "sqlite", "handoff_mode": "hard_lease"} assert bootstrap("first", "--dry-run")["storage_target"] == target assert not (runtime / "authority-transition").exists() @@ -111,13 +91,13 @@ def test_explicit_sqlite_creation_retains_later_writes_after_setting_removal(env assert created["storage_selection"]["authority_initialized"] is True added = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(project / ".loopx/registry.json"), "--runtime-root", str(runtime), "--format", "json", "todo", "add", "--goal-id", "first", "--role", "agent", - "--text", "Retain the opted-in Goal's later work"], capture_output=True, text=True, timeout=60) + "--text", "Retain the default Goal's later work"], capture_output=True, text=True, timeout=60) assert added.returncode == 0, added.stdout + added.stderr before = read_canonical_todos_if_promoted(runtime_root=runtime, goal_id="first", include_leases=True) assert before["source_authority"] == "sqlite_v0" assert before["handoff_mode"] == "hard_lease" assert len(before["todos"]) == 1 - configuration.unlink() + configure("file", mode="soft_claim") Path(created["state_file"]).unlink() restart_effect_runtime() retried = bootstrap()