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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion installer/bootstrap_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,12 +754,18 @@ def _resolve_selected_payload_bundle(
return (None, None, {}, "GLOBAL_INDEX_CORRUPTED", str(exc))
bundle_root = bundle_manifest_path.parent
if not bundle_manifest_path.is_file():
message = _stale_stub_diagnostic(
requested_version=requested_version,
payload_manifest=payload_manifest,
payload_root=payload_root,
bundle_manifest_path=bundle_manifest_path,
)
return (
bundle_root,
bundle_manifest_path,
{},
"GLOBAL_BUNDLE_MISSING",
f"Selected global bundle is missing: {bundle_manifest_path}",
message,
)
bundle_manifest = _read_json(bundle_manifest_path)
if not bundle_manifest:
Expand All @@ -773,6 +779,26 @@ def _resolve_selected_payload_bundle(
return (bundle_root, bundle_manifest_path, bundle_manifest, None, None)


def _stale_stub_diagnostic(
*,
requested_version: str | None,
payload_manifest: dict[str, Any],
payload_root: Path,
bundle_manifest_path: Path,
) -> str:
"""Build a diagnostic message distinguishing stale stub from truly missing bundle."""
active_version = _legacy_payload_bundle_version(payload_manifest)
if requested_version and active_version and requested_version != active_version:
return (
f"Workspace stub requests bundle version {requested_version}, "
f"but the active version is {active_version} "
f"(payload_root: {payload_root}). "
f"The workspace stub is stale. "
f"Reinstall for this workspace or update .sopify-skills/sopify.json."
)
return f"Selected global bundle is missing: {bundle_manifest_path} (payload_root: {payload_root})"


def _resolve_payload_bundle_manifest_path(
*,
payload_root: Path,
Expand Down
6 changes: 6 additions & 0 deletions installer/inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,12 @@ def _workspace_bundle_recommendation(host_id: str, workspace_root: Path, reason_
REASON_GLOBAL_BUNDLE_INCOMPATIBLE,
REASON_GLOBAL_INDEX_CORRUPTED,
}:
# For GLOBAL_BUNDLE_MISSING, prefer `message` which may carry
# stale-stub diagnostic from _stale_stub_diagnostic(); fall back
# to generic _payload_bundle_recommendation for other codes or
# when message is empty.
if reason_code == REASON_GLOBAL_BUNDLE_MISSING and message:
return message
return _payload_bundle_recommendation(host_id, reason_code) or message
return message

Expand Down
2 changes: 1 addition & 1 deletion runtime/gate_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def render_outcome_summary(payload: Mapping[str, object]) -> str:
"stub_selected": "Selected global bundle is ready for this workspace.",
"stub_invalid": "Repair or recreate the workspace activation manifest (`.sopify-skills/sopify.json`), then retry.",
"missing_bundle": "Trigger Sopify in this workspace with `~go` to bootstrap on demand.",
"global_bundle_missing": "Refresh the installed payload because the selected global bundle is missing.",
"global_bundle_missing": "The selected global bundle is missing. This may indicate a stale workspace stub (requesting an older version) or a missing payload install. Check `.sopify-skills/sopify.json` bundle_version or reinstall.",
"global_bundle_incompatible": "Refresh the installed payload because the selected global bundle is incomplete or incompatible.",
"global_index_corrupted": "Refresh the installed payload because the global bundle index is invalid or inconsistent.",
"payload_manifest_not_found": "Install Sopify for this host, or pass payload_root explicitly when running runtime_gate.",
Expand Down
12 changes: 6 additions & 6 deletions skills/en/skills/sopify/develop/assets/output-partial.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ Completed: {M}/{N} tasks
Blocked: {K} items

Incomplete items:
| Task | Block reason | reason_code | root_cause |
|------|-------------|-------------|------------|
| {task} | {reason} | {code} | {root_cause / —} |
| Task | Block reason | Root cause |
|------|-------------|------------|
| {task} | {reason} | {root_cause / —} |

Verification summary:
| Task | Source | Command | Result | reason_code | Retries |
|------|--------|---------|--------|-------------|---------|
| {task} | {verification_source} | {command} | {result} | {reason_code / —} | {retry_count} |
| Task | Source | Command | Result | Note | Retries |
|------|--------|---------|--------|------|---------|
| {task} | {verification_source} | {command} | {result} | {why skipped/failed; blank when passed} | {retry_count} |

---
Changes: {N} files
Expand Down
6 changes: 3 additions & 3 deletions skills/en/skills/sopify/develop/assets/output-quick-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Fix: {one-line description}

Verification summary:
| Source | Command | Result | reason_code |
|--------|---------|--------|-------------|
| {verification_source} | {command} | {result} | {reason_code / —} |
| Source | Command | Result |
|--------|---------|--------|
| {verification_source} | {command} | {result} |

---
Changes: {N} files
Expand Down
6 changes: 3 additions & 3 deletions skills/en/skills/sopify/develop/assets/output-success.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Completed: {N}/{N} tasks
Review: spec_compliance {✓/!} — {one-line evidence} | code_quality {✓/!} — {one-line evidence}

Verification summary:
| Task | Source | Command | Result | reason_code | Retries |
|------|--------|---------|--------|-------------|---------|
| {task} | {verification_source} | {command} | {result} | {reason_code / —} | {retry_count} |
| Task | Source | Command | Result | Retries |
|------|--------|---------|--------|---------|
| {task} | {verification_source} | {command} | {result} | {retry_count} |

---
Changes: {N} files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Additional rules:

- `.sopify-skills/project.md` is the future long-term home for a project-level `verify` contract. When present, it has the highest priority, but it is not a prerequisite for v1.
- `verification_source` is a source field only. Degrade/skip outcomes must be expressed through `result + reason_code`.
- `reason_code` is an internal verification field and must not appear in user-facing output. Use a human-readable "Note" column when explanation is needed.

### 2.3 Verification discovery order

Expand Down
4 changes: 2 additions & 2 deletions skills/en/skills/sopify/references/output-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ All stages (analyze / design / develop / consult) must follow this contract for
| design/summary | Score lines, Changes, Next | — | — |
| consult | Changes, Next | — (adaptive) | — |

Table column rule: only show columns that carry meaningful information for the current scenario. When all `reason_code=—` in a success scenario, that column may be omitted. Column omission only affects final display, not internal verification logic.
Table column rule: only show columns that carry meaningful information for the current scenario. Column omission only affects final display, not internal verification logic. `reason_code` is an internal verification field and is not shown in user-facing output.

## 3. Conditional Enhancement & Format Selection

Expand Down Expand Up @@ -51,6 +51,6 @@ Constraint: use at most one primary structure per reply; avoid stacking table +
Before outputting the final reply, verify:

1. Required sections present: check §2 for the current output type.
2. Status symbol correct: `✓` only when all `result=passed` and `reason_code=—`; otherwise `!`.
2. Status symbol correct: `✓` only when all verifications passed with no degradation or skips; otherwise `!`.
3. Footer complete: `Changes:` + `Next:` must be present.
4. Conditional enhancement applied: if the reply meets a §3 trigger condition, use the corresponding structured format.
12 changes: 6 additions & 6 deletions skills/zh/skills/sopify/develop/assets/output-partial.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
阻塞: {K} 项

未完成项:
| 任务 | 阻塞原因 | reason_code | root_cause |
|------|---------|-------------|------------|
| {task} | {reason} | {code} | {root_cause / —} |
| 任务 | 阻塞原因 | 根因分类 |
|------|---------|---------|
| {task} | {reason} | {root_cause / —} |

验证摘要:
| 任务 | 验证来源 | 命令 | 结果 | reason_code | 重试 |
|------|---------|------|------|-------------|------|
| {task} | {verification_source} | {command} | {result} | {reason_code / —} | {retry_count} |
| 任务 | 验证来源 | 命令 | 结果 | 说明 | 重试 |
|------|---------|------|------|------|------|
| {task} | {verification_source} | {command} | {result} | {为什么跳过/失败,passed 时留空} | {retry_count} |

---
Changes: {N} files
Expand Down
6 changes: 3 additions & 3 deletions skills/zh/skills/sopify/develop/assets/output-quick-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
修复: {一句话描述}

验证摘要:
| 验证来源 | 命令 | 结果 | reason_code |
|---------|------|------|-------------|
| {verification_source} | {command} | {result} | {reason_code / —} |
| 验证来源 | 命令 | 结果 |
|---------|------|------|
| {verification_source} | {command} | {result} |

---
Changes: {N} files
Expand Down
6 changes: 3 additions & 3 deletions skills/zh/skills/sopify/develop/assets/output-success.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
复审: spec_compliance {✓/!} — {1句依据} | code_quality {✓/!} — {1句依据}

验证摘要:
| 任务 | 验证来源 | 命令 | 结果 | reason_code | 重试 |
|------|---------|------|------|-------------|------|
| {task} | {verification_source} | {command} | {result} | {reason_code / —} | {retry_count} |
| 任务 | 验证来源 | 命令 | 结果 | 重试 |
|------|---------|------|------|------|
| {task} | {verification_source} | {command} | {result} | {retry_count} |

---
Changes: {N} files
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ develop 阶段统一使用以下字段名,不再混用 `discovery_source`、`s

- `.sopify-skills/project.md` 的 `verify` 约定是后续长期落点;当它已存在时,作为最高优先级来源,但不是当前 v1 落地前提。
- `verification_source` 只表示来源,不复用为结果态;“是否跳过/为何降级”统一通过 `result + reason_code` 表达。
- `reason_code` 是内部验证字段,最终用户输出不得展示原始值;需要解释时用人话填入"说明"列。

### 2.3 验证发现顺序

Expand Down
4 changes: 2 additions & 2 deletions skills/zh/skills/sopify/references/output-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
| design/summary | 评分行、Changes、Next | — | — |
| consult | Changes、Next | — (adaptive) | — |

表格列约束:只展示当前场景有信息量的列。success 场景下全部 `reason_code=—` 时,可省略该列。列省略只影响最终展示,不影响内部验证判断
表格列约束:只展示当前场景有信息量的列。列省略只影响最终展示,不影响内部验证判断。`reason_code` 是内部验证字段,不在用户面输出中展示

## 3. Conditional Enhancement & Format Selection

Expand Down Expand Up @@ -51,6 +51,6 @@
输出最终回复前检查:

1. 必需 section 是否存在:按 §2 核对当前输出类型的必需 section。
2. 状态符是否正确:`✓` 仅当全部 `result=passed` 且 `reason_code=—`;否则必须 `!`。
2. 状态符是否正确:`✓` 仅当全部验证通过且无降级/跳过;否则必须 `!`。
3. footer 是否完整:`Changes:` + `Next:` 必须存在。
4. 条件增强是否触发:如当前回复满足 §3 增强触发条件,应使用对应结构化表达。
8 changes: 4 additions & 4 deletions tests/golden-snapshots.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
"codex:en-US:header": "58206658e6d10e0243ff9423f50759d6e71f9635b6633fe0732615fbe22217d7",
"claude:zh-CN:header": "0f246a780d7940e21ca1fab16faf8a54ce324759a721ba8ffa0783d64c87daf6",
"claude:en-US:header": "7543d624a6cf0b4d12f7c44e8c2c2bef252974d6245e6a49151c3c613965cdf4",
"copilot:zh-CN:managed_block_payload": "1b0bcb77a93570e0fa73379ccf264b1a2ea4f8240229d935d8b386f3ee59c632",
"copilot:en-US:managed_block_payload": "874f20a2ff8a780be6a26e94fd8c11b4388671664b0c77e509088171a1b63805",
"skills:zh-CN:tree": "a50c07ce08381c9f30a7b0ffaf29ed311cca02780352c5cf2cb7161f257fa801",
"skills:en-US:tree": "8647f117ed3992a5b369c17b61a9b75928511286f12efaa857ac25ac141594eb"
"copilot:zh-CN:managed_block_payload": "116e0edf3c52064f65194a0c0f9aac3ec3980512c6b864fcce0f6c60dcdf1a8d",
"copilot:en-US:managed_block_payload": "8a03ac2f134eda90595ff6e7e813f37e94496706dbc94b054a6a7ccfb66a0985",
"skills:zh-CN:tree": "d9af054a30aa6ec4d23921ac2ac139781f8990f7c8b7320fb6dcc0965e8a161e",
"skills:en-US:tree": "034bfacfef1f2bc2ee6bf1cc06f08934df017a7002f881ef51780e67de4226a7"
}
}
22 changes: 22 additions & 0 deletions tests/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
_REQUIRED_BUNDLE_FILES,
_classify_workspace_bundle,
_resolve_payload_bundle_manifest_path as _bootstrap_resolve_payload_bundle_manifest_path,
_stale_stub_diagnostic,
_write_workspace_stub_overlay,
)
from installer.hosts.base import install_host_assets
Expand Down Expand Up @@ -579,6 +580,27 @@ def test_global_first_workspace_fail_closes_when_selected_global_bundle_is_incom
self.assertIn("incompatible", message)
self.assertEqual(from_version, "2026-02-13")

def test_stale_stub_diagnostic_reports_version_mismatch(self) -> None:
msg = _stale_stub_diagnostic(
requested_version="2026-05-03.203432",
payload_manifest={"active_version": "2026-05-27.220559"},
payload_root=Path("/fake/payload"),
bundle_manifest_path=Path("/fake/payload/bundles/2026-05-03.203432/manifest.json"),
)
self.assertIn("2026-05-03.203432", msg)
self.assertIn("2026-05-27.220559", msg)
self.assertIn("stale", msg.lower())

def test_stale_stub_diagnostic_falls_back_when_versions_match(self) -> None:
msg = _stale_stub_diagnostic(
requested_version="2026-05-27.220559",
payload_manifest={"active_version": "2026-05-27.220559"},
payload_root=Path("/fake/payload"),
bundle_manifest_path=Path("/fake/payload/bundles/2026-05-27.220559/manifest.json"),
)
self.assertNotIn("stale", msg.lower())
self.assertIn("missing", msg.lower())

def test_validate_bundle_install_requires_runtime_bridge_modules(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
bundle_root = Path(temp_dir) / "bundle-root"
Expand Down
Loading