From 58632cc2fdcaf1cce5a5732bdbb7cf2dd21049c0 Mon Sep 17 00:00:00 2001 From: song Date: Tue, 13 Oct 2026 01:13:13 +0800 Subject: [PATCH 1/5] feat(amendment): govern local template work under immutable Goal intent Signed-off-by: song --- .../dashboard/src/data/goal-amendment.ts | 45 +++ .../goal-amendment-settings.tsx | 170 ++++++++ .../workspace-settings-page.tsx | 2 + loopx/bootstrap.py | 3 +- .../machine_configuration/goal_storage.py | 34 +- loopx/chat_actions.py | 2 +- loopx/chat_server.py | 5 +- loopx/chat_status_api.py | 6 +- loopx/cli_commands/goal_amendment_proposal.py | 56 +++ loopx/cli_commands/project.py | 9 +- .../coordination/new_goal_initialization.ts | 21 +- .../new_goal_storage_transport.py | 64 +++ .../control_plane/coordination/todo_claim.ts | 25 +- .../control_plane/effect_runtime_handlers.ts | 1 + .../goals/goal_amendment_authority.ts | 378 ++++++++++++++++++ .../goals/goal_amendment_proposal.py | 4 + .../goals/goal_amendment_runtime.py | 129 ++++++ .../goals/source_session_lifetime.ts | 18 + .../goals/source_session_registration.py | 51 +++ .../goals/source_session_registry_state.py | 3 +- loopx/control_plane/projects/registry.py | 7 + .../control_plane/projects/registry_codec.py | 11 + loopx/presentation/configuration_api.py | 4 + loopx/presentation/goal_amendment_api.py | 128 ++++++ 24 files changed, 1137 insertions(+), 39 deletions(-) create mode 100644 apps/presentation/dashboard/src/data/goal-amendment.ts create mode 100644 apps/presentation/dashboard/src/features/personal-workspace/goal-amendment-settings.tsx create mode 100644 loopx/control_plane/coordination/new_goal_storage_transport.py create mode 100644 loopx/control_plane/goals/goal_amendment_authority.ts create mode 100644 loopx/control_plane/goals/goal_amendment_runtime.py create mode 100644 loopx/presentation/goal_amendment_api.py diff --git a/apps/presentation/dashboard/src/data/goal-amendment.ts b/apps/presentation/dashboard/src/data/goal-amendment.ts new file mode 100644 index 0000000000..f555962789 --- /dev/null +++ b/apps/presentation/dashboard/src/data/goal-amendment.ts @@ -0,0 +1,45 @@ +import {z} from "zod"; +import {requestJson} from "./chat"; + +const goalRef = z.object({goal_id: z.string(), goal_instance_id: z.string()}); +const permissionScope = z.object({write_scopes: z.array(z.string()), capabilities: z.array(z.string())}); +const commitments = z.object({objective: z.string(), non_goals: z.array(z.string()), acceptance: z.array(z.string()), stop_condition: z.string()}); +const template = z.object({id: z.string(), digest: z.string(), todo: z.object({text: z.string(), action_kind: z.string(), target_key: z.string(), + required_write_scopes: z.array(z.string()), required_capabilities: z.array(z.string())})}); +export const amendmentViewSchema = z.object({ + ok: z.literal(true), schema_version: z.literal("goal_amendment_result_v0"), status: z.literal("loaded"), + goal_ref: goalRef, provider_revision: z.string(), work_digest: z.string(), current_lifetime: z.boolean(), + source_commitments: commitments.nullable(), execution_authority_granted: z.literal(false), + mutation_readiness: z.object({kind: z.enum(["admit", "reject"]), code: z.string().optional()}), + intent: z.object({goal_ref: goalRef, goal_revision: z.number().int().positive(), intent_digest: z.string(), + root_intent: z.object({permission_scope: permissionScope, objective: z.string(), non_goals: z.array(z.string()), acceptance: z.array(z.string()), stop_conditions: z.array(z.string()), terminal_conditions: z.array(z.string())}), + policy: z.object({eligible_agents: z.array(z.string()), templates: z.array(template), max_created_todos: z.number().int()}), + created_todo_ids: z.array(z.string()), proposals: z.array(z.object({proposal_id: z.string(), proposer_agent_id: z.string(), template_id: z.string(), base_goal_revision: z.number(), replan_obligation_id: z.string()})), + }).nullable(), + peer_adoption: z.array(z.object({agent_id: z.string(), based_on_goal_revision: z.number().nullable(), current: z.boolean()})), + replan_observations: z.array(z.object({obligation_id: z.string(), agent_id: z.string(), state: z.enum(["pending", "settled"])})), +}); +export type AmendmentView = z.infer; +const rootParametersSchema = z.object({expected_provider_revision: z.string(), permission_scope: permissionScope, + terminal_conditions: z.array(z.string()), policy: z.object({out_of_policy: z.literal("reject"), eligible_agents: z.array(z.string()), + max_created_todos: z.number().int(), templates: z.array(template.omit({digest: true}))})}); +export const rootRequestSchema = z.object({goal_id: z.string(), goal_instance_id: z.string(), operation_id: z.string(), + parameters: rootParametersSchema, preview_digest: z.string().optional()}); +export const savedRootRequestSchema = z.object({request: rootRequestSchema.extend({preview_digest: z.string().regex(/^[a-f0-9]{64}$/)}), attempted: z.boolean()}); +export type RootRequest = z.infer; +const resultSchema = z.object({ok: z.boolean(), schema_version: z.literal("goal_amendment_result_v0"), status: z.string(), + intent_digest: z.string().optional(), reason: z.string().optional(), reason_code: z.string().optional(), execution_authority_granted: z.literal(false), + current_projection: amendmentViewSchema.omit({ok: true}).optional()}); +export async function fetchAmendment(goalId: string): Promise { + return amendmentViewSchema.parse(await requestJson(`/api/chat/goal-amendment?${new URLSearchParams({goal_id: goalId})}`)); +} +export async function configureAmendment(request: RootRequest, execute: boolean) { + return resultSchema.parse(await requestJson("/api/chat/goal-amendment", {method: "POST", body: JSON.stringify({...request, execute})})); +} + +export async function fetchAmendmentReceipt(request: RootRequest) { + const query = new URLSearchParams({goal_id: request.goal_id, goal_instance_id: request.goal_instance_id, operation_id: request.operation_id}); + return z.object({ok: z.boolean(), status: z.string(), historical_only: z.literal(true), execution_authority_granted: z.literal(false), + receipts: z.array(z.object({operation_id: z.string(), result: z.object({goal_ref: goalRef, intent_digest: z.string().optional()})})).optional() + }).parse(await requestJson(`/api/chat/goal-amendment?${query}`)); +} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-amendment-settings.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-amendment-settings.tsx new file mode 100644 index 0000000000..448b5235c2 --- /dev/null +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-amendment-settings.tsx @@ -0,0 +1,170 @@ +import {useEffect, useRef, useState} from "react"; +import {configureAmendment, fetchAmendment, fetchAmendmentReceipt, savedRootRequestSchema, type AmendmentView, type RootRequest} from "../../data/goal-amendment"; +import {useWorkspaceI18n} from "./i18n"; + +/** Operator setup and readback only. Peer adoption is recorded by the peer's + * own operation; this page never sends an Agent id to impersonate it. */ +export function GoalAmendmentSettings({goalId}: {goalId: string}) { + const {locale} = useWorkspaceI18n();const zh = locale === "zh-CN"; + const [open, setOpen] = useState(false), [view, setView] = useState(null); + const [busy, setBusy] = useState(false), [error, setError] = useState(null); + const [text, setText] = useState(""), [action, setAction] = useState(""), [target, setTarget] = useState(""); + const [writes, setWrites] = useState(""), [capabilities, setCapabilities] = useState(""), [terminal, setTerminal] = useState(""); + const [agents, setAgents] = useState([]), [limit, setLimit] = useState(1); + const [preview, setPreview] = useState(null); + const generation = useRef(0), inFlight = useRef(null); + const [attempted, setAttempted] = useState(false); + const [invalidSaved, setInvalidSaved] = useState(false); + const key = `loopx-root-intent:${goalId}`; + const lines = (value: string) => value.split("\n").map(v => v.trim()).filter(Boolean); + const wording = (cn: string, en: string) => zh ? cn : en; + + async function read() { + if (inFlight.current === generation.current) return; + const current = generation.current;inFlight.current = current;setBusy(true);setError(null); + try { + const value = await fetchAmendment(goalId); + if (current !== generation.current) return; + setView(value);setAgents(value.peer_adoption.map(row => row.agent_id)); + setInvalidSaved(true); + const raw = sessionStorage.getItem(key); + if (raw) { + const parsed = savedRootRequestSchema.safeParse(JSON.parse(raw)); + if (!parsed.success || parsed.data.request.goal_id !== goalId) {setInvalidSaved(true);throw new Error(wording("保存的操作信息不完整,无法安全恢复或创建新操作。", "Saved operation details are incomplete; recovery or a new operation cannot safely proceed."));} + const saved = parsed.data; + if (saved.request?.goal_id === goalId && typeof saved.request.preview_digest === "string") { + setPreview(saved.request);setAttempted(saved.attempted === true); + const parameters = saved.request.parameters, work = parameters.policy.templates[0]?.todo; + setText(work?.text ?? "");setAction(work?.action_kind ?? "");setTarget(work?.target_key ?? ""); + setWrites(parameters.permission_scope.write_scopes.join("\n"));setCapabilities(parameters.permission_scope.capabilities.join("\n")); + setTerminal(parameters.terminal_conditions.join("\n"));setAgents(parameters.policy.eligible_agents);setLimit(parameters.policy.max_created_todos); + } + } + setInvalidSaved(false); + } catch (cause) { + if (current === generation.current) setError(cause instanceof Error ? cause.message : wording("无法读取目标变更状态。", "Goal amendment state is unavailable.")); + } finally {if (inFlight.current === current) inFlight.current = null;if (current === generation.current) setBusy(false);} + } + useEffect(() => { + generation.current++;setView(null);setPreview(null);setAttempted(false);setInvalidSaved(false);setError(null); + if (open) void read(); + return () => {generation.current++;}; + // The target, not a language toggle, owns this observation. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [goalId, open]); + + function edit(change: () => void) { + if (attempted || invalidSaved) return; + change();setPreview(null);sessionStorage.removeItem(key);setError(null); + } + async function submit(apply: boolean) { + if (invalidSaved || inFlight.current === generation.current || !view || apply && !preview) return; + const token = generation.current;inFlight.current = token;setBusy(true);setError(null); + const request: RootRequest = apply ? preview! : {goal_id: goalId, goal_instance_id: view.goal_ref.goal_instance_id, + operation_id: `root:${crypto.randomUUID()}`, parameters: {expected_provider_revision: view.provider_revision, + permission_scope: {write_scopes: lines(writes), capabilities: lines(capabilities)}, terminal_conditions: lines(terminal), + policy: {out_of_policy: "reject", eligible_agents: agents, max_created_todos: limit, + templates: [{id: "owner-work", todo: {text: text.trim(), action_kind: action.trim(), target_key: target.trim(), + required_write_scopes: lines(writes), required_capabilities: lines(capabilities)}}]}}}; + try { + if (apply) { + sessionStorage.setItem(key, JSON.stringify({request, attempted: true}));setAttempted(true); + } + const result = await configureAmendment(request, apply); + if (token !== generation.current) return; + if (!result.ok || !result.intent_digest) { + if (result.reason_code === "goal_amendment_needs_rebase") { + // The pinned old provider basis cannot become current again. This + // definitive refusal permits a fresh preview; unknown outcomes do not. + sessionStorage.removeItem(key);setPreview(null);setAttempted(false); + const fresh = await fetchAmendment(goalId); + if (token === generation.current) setView(fresh); + } + throw new Error(result.reason ?? wording("配置未被接纳。", "Configuration was not admitted.")); + } + if (!apply) { + const saved = {...request, preview_digest: result.intent_digest}; + sessionStorage.setItem(key, JSON.stringify({request: saved, attempted: false}));setPreview(saved);setAttempted(false); + } else { + sessionStorage.removeItem(key);setPreview(null);setAttempted(false); + // Read the current owner state even when the write was historical replay. + const fresh = await fetchAmendment(goalId); + if (token === generation.current) setView(fresh); + } + } catch (cause) { + if (token === generation.current) setError(cause instanceof Error ? cause.message : wording("响应未知,请读取当前结果或重试原操作。", "Response unknown; read back or retry the original operation.")); + } finally {if (inFlight.current === token) inFlight.current = null;if (token === generation.current) setBusy(false);} + } + + async function recover() { + if (!preview || inFlight.current === generation.current) return; + const token = generation.current;inFlight.current = token;setBusy(true);setError(null); + try { + const found = await fetchAmendmentReceipt(preview); + if (token !== generation.current) return; + const receipt = found.receipts?.[0]; + if (found.status !== "found" || receipt?.operation_id !== preview.operation_id || receipt.result.intent_digest !== preview.preview_digest || receipt.result.goal_ref.goal_id !== preview.goal_id || receipt.result.goal_ref.goal_instance_id !== preview.goal_instance_id) { + throw new Error(wording("暂未确认原操作回执;不能据此断言没有提交。请重试原操作。", "The original receipt is not confirmed; absence does not prove no commit. Retry the original operation.")); + } + sessionStorage.removeItem(key);setPreview(null);setAttempted(false); + const fresh = await fetchAmendment(goalId); + if (token === generation.current) setView(fresh); + } catch (cause) {if (token === generation.current) setError(cause instanceof Error ? cause.message : "Readback failed");} + finally {if (inFlight.current === token) inFlight.current = null;if (token === generation.current) setBusy(false);} + } + + return
setOpen(e.currentTarget.open)}> + {wording("目标约束与受治理变更", "Goal commitments and governed changes")} + {open ?
+

{wording("先固定原始目标和允许的工作模板。Agent 可在授权范围内提出变更;变更记录不代表工作已执行或目标已完成。", "Freeze the original Goal and permitted work template. Agents may propose changes within this policy; a change record does not prove execution or Goal completion.")}

+ {view?.source_commitments ?
+ {view.source_commitments.objective} +

{wording("不包含:", "Excluded: ")}{view.source_commitments.non_goals.join("; ")}

+
    {view.source_commitments.acceptance.map(value =>
  • {value}
  • )}
+

{view.source_commitments.stop_condition}

+
: null} + {view && (!view.current_lifetime || !view.source_commitments || view.mutation_readiness.kind !== "admit") ?

+ {wording("当前目标实例或原始声明不可用于初始化;请由目标生命周期负责人处理。历史结果不会授予当前执行权限。", "The current Goal lifetime or original declaration cannot be used for initialization. Its lifecycle owner must resolve this; historical results grant no current execution authority.")} +

: null} + {view?.intent ? <> +

{wording(`已固定目标约束 · 版本 ${view.intent.goal_revision}`, `Goal commitments frozen · revision ${view.intent.goal_revision}`)}

+
    {view.intent.policy.templates.map(row =>
  • {row.todo.text}
  • )}
+

{wording("写入范围:", "Write scopes: ")}{view.intent.root_intent.permission_scope.write_scopes.join(", ") || wording("无", "None")}

+

{wording("所需能力:", "Required capabilities: ")}{view.intent.root_intent.permission_scope.capabilities.join(", ") || wording("无", "None")}

+

{wording("终止条件:", "Terminal conditions: ")}{view.intent.root_intent.terminal_conditions.join("; ")}

+

{wording("Agent 基线确认", "Agent basis acknowledgements")}

+
    {view.peer_adoption.map(row =>
  • {row.agent_id} · {row.current ? wording("当前版本确认已记录", "Current basis acknowledged") : wording("尚未确认当前版本", "Current basis not acknowledged")}
  • )}
+

{wording(`已创建工作 ${view.intent.created_todo_ids.length} 项;提案 ${view.intent.proposals.length} 项。`, `${view.intent.created_todo_ids.length} work items created; ${view.intent.proposals.length} proposals.`)}

+
    {view.replan_observations.map(row =>
  • {row.agent_id} · {row.state === "settled" ? wording("对应 replan 已由原有规则验证", "Replan validated by its existing owner") : wording("仍待后续工作采纳与验证", "Work adoption and validation remain pending")}
  • )}
+ : view?.current_lifetime && view.source_commitments && view.mutation_readiness.kind === "admit" ? <> +
+