Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions apps/presentation/dashboard/smoke/team-plan-proposal-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ const plan = {
gaps: [{ lane_id: "lane_review", reason_code: "agent_not_registered" }],
quota_envelope: { slots: 4, window: "1d" },
stop_condition: "every lane reports a typed outcome or a stated gap",
field_classification: {
objective: "advisory",
quota_envelope: "advisory",
stop_condition: "advisory",
"lane.first_todo": "execution_constraint",
"lane.acceptance": "retained_acceptance_reference",
},
applies: false,
};

Expand Down Expand Up @@ -130,6 +137,9 @@ const translate = (key: string, values?: Record<string, string | number>) => {
"proposal.field.quotaEnvelope": "Quota envelope",
"proposal.field.stopCondition": "Stop condition",
"proposal.teamPlan.acceptanceShort": "acceptance",
"proposal.teamPlan.class.advisory": "advisory, not enforced",
"proposal.teamPlan.class.executionConstraint": "enforced",
"proposal.teamPlan.class.retainedAcceptance": "retained acceptance",
"proposal.teamPlan.gapLane": "unstaffed",
"proposal.teamPlan.laneUnstaffed": "staffing gap, no first Todo",
};
Expand All @@ -149,23 +159,39 @@ check(readyLane?.label === "agent-backend", "a ready lane names the Agent that r
check(
readyLane?.value.includes("P1") === true
&& readyLane?.value.includes("Implement the bounded intake") === true
&& readyLane?.value.includes("acceptance: the bounded Todo is created through the canonical owner") === true,
"a ready lane shows its first bounded Todo, its priority and its acceptance signal",
&& readyLane?.value.includes("retained acceptance: the bounded Todo is created through the canonical owner") === true,
"a ready lane shows its first bounded Todo, its priority and its retained acceptance signal",
);
check(
gapLane?.value.startsWith("unstaffed · agent_not_registered") === true
&& gapLane?.value.includes("Independently review the intake") === true,
"a gap lane says it is unstaffed, names the reason and keeps the work it did not staff",
);
check(byKey.get("lane_gaps")?.value === "lane_review: agent_not_registered", "the gap summary joins lane and reason");
check(byKey.get("quota_envelope")?.value === "slots: 4 · window: 1d", "the quota envelope is shown as data");
check(
byKey.get("stop_condition")?.value === "every lane reports a typed outcome or a stated gap",
"the stop condition is shown",
byKey.get("quota_envelope")?.value === "slots: 4 · window: 1d · advisory, not enforced",
"the quota envelope is shown as data and as advisory",
);
check(
byKey.get("stop_condition")?.value
=== "every lane reports a typed outcome or a stated gap · advisory, not enforced",
"the stop condition is shown as advisory",
);
check(
fields.some((field) => field.value.includes("agent-") === true && field.value.includes("ready") === true) === false,
"the card never renders a lane as already created",
);

// A plan whose host emitted no classification must not get one invented for it:
// the card shows what it was given, and an unclassified field is not a binding.
const unclassified = teamPlanFields(
{ goal_id: GOAL_ID, plan: { ...plan, field_classification: undefined } },
translate as never,
);
check(
new Map(unclassified.map((field) => [field.key, field.value])).get("quota_envelope")
=== "slots: 4 · window: 1d",
"an unclassified envelope is shown without a classification the host did not state",
);

if (process.exitCode !== 1) console.log("team plan proposal smoke ok");
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,9 @@ const en = {
"proposal.field.laneGaps": "Unstaffed lanes",
"proposal.field.quotaEnvelope": "Quota envelope",
"proposal.teamPlan.acceptanceShort": "acceptance",
"proposal.teamPlan.class.advisory": "advisory, not enforced",
"proposal.teamPlan.class.executionConstraint": "enforced",
"proposal.teamPlan.class.retainedAcceptance": "retained acceptance",
"proposal.teamPlan.gapLane": "unstaffed",
"proposal.teamPlan.laneUnstaffed": "staffing gap, no first Todo",
"proposal.workspace.current": "Current local workspace (no Repository bound)",
Expand Down Expand Up @@ -1736,6 +1739,9 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"proposal.field.laneGaps": "未配齐的 lane",
"proposal.field.quotaEnvelope": "配额包络",
"proposal.teamPlan.acceptanceShort": "验收",
"proposal.teamPlan.class.advisory": "仅记录,不强制执行",
"proposal.teamPlan.class.executionConstraint": "强制执行",
"proposal.teamPlan.class.retainedAcceptance": "留存验收",
"proposal.teamPlan.gapLane": "未配齐",
"proposal.teamPlan.laneUnstaffed": "编制缺口,无首个 Todo",
"proposal.workspace.current": "当前本地工作区(未绑定 Repository)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,39 @@
return typeof value === "string" ? value : "";
}

/**
* What kind of claim a plan field is, as the host classified it.
*
* The host emits this beside the plan it admitted, and an admitted preview
* carries it inside the preview itself, so both places are host facts rather
* than a claim the plan makes. A field the host did not classify reads as
* unclassified instead of being assumed to bind.
*/
function fieldClassification(
parameters: Record<string, unknown>,
plan: Record<string, unknown>,
): Record<string, unknown> {
return { ...asRecord(parameters.field_classification), ...asRecord(plan.field_classification) };
}

function classificationLabel(
classification: Record<string, unknown>,
key: string,
t: WorkspaceTranslate,
): string {
const value = asText(classification[key]);
if (value === "execution_constraint") return t("proposal.teamPlan.class.executionConstraint");
if (value === "retained_acceptance_reference") return t("proposal.teamPlan.class.retainedAcceptance");
if (value === "advisory") return t("proposal.teamPlan.class.advisory");
return "";
}

export function teamPlanFields(
parameters: Record<string, unknown>,
t: WorkspaceTranslate,
): TeamPlanPreviewField[] {
const plan = asRecord(parameters.plan);
const classification = fieldClassification(parameters, plan);
const fields: TeamPlanPreviewField[] = [];
const goalId = asText(parameters.goal_id) || asText(plan.goal_id);
if (goalId) {
Expand Down Expand Up @@ -61,12 +89,18 @@
asText(todo.action_kind),
asText(todo.text),
].filter(Boolean).join(" · ");
// A lane's first Todo is the work this confirmation creates, so it carries

Check warning on line 92 in apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaCuuYzx7IDgnxLwoutu&open=AaCuuYzx7IDgnxLwoutu&pullRequest=4592
// its class; the acceptance the lane ends on is retained beside that work
// rather than enforced by it.
const acceptanceLabel =
classificationLabel(classification, "lane.acceptance", t)
|| t("proposal.teamPlan.acceptanceShort");
fields.push({
key: `lane_${laneId}`,
label: agentId || laneId,
value: [
laneValue || t("proposal.teamPlan.laneUnstaffed"),
acceptance ? `${t("proposal.teamPlan.acceptanceShort")}: ${acceptance}` : "",
acceptance ? `${acceptanceLabel}: ${acceptance}` : "",
].filter(Boolean).join(" · "),
});
});
Expand All @@ -90,15 +124,20 @@
fields.push({
key: "quota_envelope",
label: t("proposal.field.quotaEnvelope"),
value: envelopeEntries.map(([key, value]) => `${key}: ${String(value ?? "")}`).join(" · "),
value: [
envelopeEntries.map(([key, value]) => `${key}: ${String(value ?? "")}`).join(" · "),

Check warning on line 128 in apps/presentation/dashboard/src/features/personal-workspace/team-plan-preview.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'value ?? ""' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaCuuYzx7IDgnxLwoutv&open=AaCuuYzx7IDgnxLwoutv&pullRequest=4592
classificationLabel(classification, "quota_envelope", t),
].filter(Boolean).join(" · "),
});
}
const stopCondition = asText(plan.stop_condition);
if (stopCondition) {
fields.push({
key: "stop_condition",
label: t("proposal.field.stopCondition"),
value: stopCondition,
value: [stopCondition, classificationLabel(classification, "stop_condition", t)]
.filter(Boolean)
.join(" · "),
});
}
return fields;
Expand Down
11 changes: 8 additions & 3 deletions docs/architecture/rfcs/harness-selection-dsh-pi-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,14 +522,19 @@ The shipped boundary is `steward_team_plan_preview_v0`, kind
`steward_team_plan_preview`, dispatched through
`loopx/control_plane/work_items/governed_transition_proposal.py` and Chat
`team.plan`. It names an exact Goal, 1–8 lanes, registered Agent identities,
first Todo text/priority/class/action, acceptance, quota envelope and stop
condition. Validation produces `applies: false`. Preview is not execution.
first Todo text/priority/class/action, acceptance and a plan-level quota
envelope and stop condition. Validation produces `applies: false`, classifies
every field it validated -- a lane's first bounded Todo as an execution
constraint, its acceptance as a retained acceptance reference, the objective,
envelope and stop condition as advisory -- and refuses a plan that asks this
host to enforce a plan-level limit no owner here enforces. Preview is not
execution.

| Boundary | Shipped behavior | Remaining limitation |
| --- | --- | --- |
| Validation/admission (#4519/#4522/#4532/#4533) | Exact Goal, registered Agents, supported advancement kinds and bounded public-safe fields; channel-scoped Goal lookup; unavailable facts drop the proposal while preserving answer text | `ready` checks registration/action support, not executor health, tool eligibility or budget admission |
| Staffing gaps | Unknown Agent produces `agent_not_registered` and retains `declined_first_todo`; explicit `capability_not_granted` / `audience_not_authorized` gaps admit no work | These reason codes do not prove all capability/audience conditions are automatically detected |
| Materialization (#4524/#4528/#4535/#4538) | Revalidates the named Goal; calls canonical Todo owner per ready lane; records proposal digest and bounded `lane_todo_ids`; existing receipt shape remains readable; no monitor key | Confirmed priority is dropped; acceptance/quota/stop are not execution constraints on this path; no atomic team commit or automatic partial-recovery proof |
| Materialization (#4524/#4528/#4535/#4538) | Revalidates the named Goal; calls canonical Todo owner per ready lane; keeps the confirmed priority in the lane Todo's own label; records proposal digest, bounded `lane_todo_ids` and each lane's retained acceptance; existing receipt shape remains readable; no monitor key | The plan-level envelope and stop condition stay advisory and a claim that they are enforced is refused; no atomic team commit or automatic partial-recovery proof |
| Confirmation (#4547/#4548/#4552) | Existing frontend displays lanes/gaps and submits `team.plan`; bundle and browser fixture shipped; the manager conversation now lists the card its own channel stored, so an owner confirms where the sentence was typed while a Goal-scoped fetch stays in that Goal's workspace | Lark and real worker execution were not qualified by this fixture; the confirmation readback was repaired after this fixture (a confirmed lane keeps its declared priority, a partial application reports its gap count, and a plan that staffs no lane is a typed failure) |
| Freshness | Registry byte changes make the Chat preview stale; optional `intent_basis` reads alignment source facts before materialization | No exact Goal-intent/authorization/work precondition at commit; `intent_basis` is neither the full intent revision nor a CAS fence |

Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/rfcs/harness-selection-dsh-pi-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,13 @@ journal 与配额语义;B 作为上游接口出现时的低成本替代;只

在 `43d362532`,团队入端已交付有界预览、业主确认与首批 Todo 物化;尚未验收正在运行、预算受约束或完整意图对齐的团队。[整体路线总纲](loopx-overall-roadmap-v0.zh-CN.md) 拥有跨 RFC 优先级及 F1–F7/R1–R7;本文保留 runtime 选型及其资格证据,不另建团队架构。

已交付边界为 `steward_team_plan_preview_v0`、kind `steward_team_plan_preview`,经 `loopx/control_plane/work_items/governed_transition_proposal.py` 与 Chat `team.plan` 分派。计划点名确切 Goal、1–8 条 lane、注册 Agent、首个 Todo 的 text/priority/class/action、acceptance、quota envelope 与 stop condition。校验结果 `applies: false`;预览不等于执行。
已交付边界为 `steward_team_plan_preview_v0`、kind `steward_team_plan_preview`,经 `loopx/control_plane/work_items/governed_transition_proposal.py` 与 Chat `team.plan` 分派。计划点名确切 Goal、1–8 条 lane、注册 Agent、首个 Todo 的 text/priority/class/action、acceptance,以及计划级 quota envelope 与 stop condition。校验结果 `applies: false`,并对其校验的每个字段给出分类——lane 的首个有界 Todo 是执行约束、它的 acceptance 是留存验收引用、objective/envelope/stop condition 是 advisory——同时拒绝要求本机强制一个本机无人承担的计划级限制的计划。预览不等于执行。

| 边界 | 已交付行为 | 剩余限制 |
| --- | --- | --- |
| 校验/准入(#4519/#4522/#4532/#4533) | 确切 Goal、注册 Agent、支持的 advancement kind、有界公开安全字段;按通道范围查询 Goal;缺少事实时丢弃提案并保留答案正文 | `ready` 只校验注册/action 支持,不证明执行器健康、工具资格或预算准入 |
| Staffing gap | 未注册 Agent 产生 `agent_not_registered` 并保留 `declined_first_todo`;显式 `capability_not_granted` / `audience_not_authorized` gap 不允许工作 | 存在这些 reason code 不证明全部 capability/audience 条件已经自动检测 |
| 物化(#4524/#4528/#4535/#4538) | 重新校验点名 Goal,逐 ready lane 调 canonical Todo owner;回执保存 proposal digest 与有界 `lane_todo_ids`,兼容旧回执且没有 monitor key | 确认的 priority 丢失;acceptance/quota/stop 未成为此路径的执行约束;没有整队原子提交或自动 partial recovery 证明 |
| 物化(#4524/#4528/#4535/#4538) | 重新校验点名 Goal,逐 ready lane 调 canonical Todo owner;确认的 priority 保留在 lane Todo 自己的标签里;回执保存 proposal digest、有界 `lane_todo_ids` 与每条 lane 的留存 acceptance;兼容旧回执且没有 monitor key | 计划级 envelope 与 stop condition 仍为 advisory,声明为强制会被拒绝;没有整队原子提交或自动 partial recovery 证明 |
| 确认(#4547/#4548/#4552) | 现有前端展示 lanes/gaps 并提交 `team.plan`,bundle 与 browser fixture 已交付;产生计划的**管家会话本身也会列出该通道存入的卡片**,业主在说出这句话的地方即可确认,而为已选 Goal 拉取的提案仍留在该 Goal 工作区 | 此 fixture 未验收 Lark 或真实 worker 执行;确认回读在该 fixture 之后已修复(确认后的 lane 保留声明的优先级,部分落地返回缺口数量,无 lane 可组建的计划记为 typed failure) |
| 新鲜度 | registry bytes 变化会使 Chat preview stale;可选 `intent_basis` 在物化前读取 alignment source facts | commit 未绑定精确 Goal intent/授权/工作前置条件;`intent_basis` 不是完整意图修订或 CAS fence |

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ Progress means another independently reproducible user journey, not more fields

These are synthetic-fixture results at the exact baseline, without live user content. F1–F4 exercise existing `ChatActionService.preview/apply` with isolated Goals. F4 injects failure immediately before the second Todo write; the other writes use the actual local Todo writer. F5–F7 are source/contract findings.

**Repair status (2026-09-17, baseline `6979d528b`).** The rows below stay as the record of what the audit found. R1 has since closed the priority and acceptance half of F1, all of F3, F2, and the reconciliation half of F4: a confirmed lane now keeps the priority it declared in its own Todo label, the settlement receipt retains each lane's acceptance beside the Todo identity it became, a partial application reports `team_plan_partially_applied` with a gap count instead of a full success, a confirmation that staffed no lane is recorded as the typed failure `team_plan_no_staffable_lane` rather than as an applied plan with an empty Todo id, a team-plan preview now binds this Goal's active-state intent and the canonical basis its lanes would advance, and a lane write that fails after earlier lanes were committed is recorded as the typed failure `team_plan_lane_write_failed` whose details name the lanes that exist and the lane that failed, so the retry reconciles against real identities instead of duplicating them. The remainder is open and owned by canonical Todos: the plan-level quota/stop classification, F4's automatic recovery and execution barrier (the re-entrant apply that finishes a partial plan without a fresh owner confirmation), and rendering a partial application as partial on the confirmation card.
**Repair status (2026-09-17, baseline `6979d528b`).** The rows below stay as the record of what the audit found. R1 has since closed the priority and acceptance half of F1, all of F3, F2, and the reconciliation half of F4: a confirmed lane now keeps the priority it declared in its own Todo label, the settlement receipt retains each lane's acceptance beside the Todo identity it became, a partial application reports `team_plan_partially_applied` with a gap count instead of a full success, a confirmation that staffed no lane is recorded as the typed failure `team_plan_no_staffable_lane` rather than as an applied plan with an empty Todo id, a team-plan preview now binds this Goal's active-state intent and the canonical basis its lanes would advance, so rewriting the objective after the preview asks the owner to confirm again instead of applying the old plan, and a lane write that fails after earlier lanes were committed is recorded as the typed failure `team_plan_lane_write_failed` whose details name the lanes that exist and the lane that failed, so the retry reconciles against real identities instead of duplicating them. Every plan field also carries the host's classification -- a lane's first bounded Todo as an execution constraint, its acceptance as a retained acceptance reference, and the plan-level envelope and stop condition as advisory, which the confirmation surface shows -- and a plan that asks this host to enforce a plan-level limit it has no owner for is refused before the owner is offered a confirmation instead of being stored as JSON. The remainder is open and owned by canonical Todos: F4's automatic recovery and execution barrier (the re-entrant apply that finishes a partial plan without a fresh owner confirmation), and rendering a partial application as partial on the confirmation card.

| ID / Priority | Trigger, result and consequence | Location and successor |
| --- | --- | --- |
Expand Down
Loading