@foggy-projects/deepseek-harness-plugin 0.4.0-beta.6 → 0.4.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/lib/client.js +108 -3
- package/lib/index.js +63 -4
- package/package.json +1 -1
- package/skills/foggy-deepseek-onboarding/SKILL.md +15 -2
- package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +20 -0
- package/skills/foggy-deepseek-onboarding/assets/versions.json +1 -1
- package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +18 -4
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +373 -21
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ only when the user selects **Initialize Foggy**.
|
|
|
8
8
|
## Local beta installation
|
|
9
9
|
|
|
10
10
|
```powershell
|
|
11
|
-
dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.
|
|
11
|
+
dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.7.tgz
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Restart `dsh web`, open Settings → Plugins → Foggy Data Analysis, and initialize
|
|
@@ -38,7 +38,11 @@ evidence.
|
|
|
38
38
|
Opaque CLI profiles default to the private persistent
|
|
39
39
|
`<dataRoot>/cli-profiles` directory. Composite onboarding commands are
|
|
40
40
|
idempotent: unchanged completed phases are resumed rather than re-adding a
|
|
41
|
-
datasource or re-registering a published bundle.
|
|
41
|
+
datasource or re-registering a published bundle. Settings detects legacy
|
|
42
|
+
temporary profiles and offers an explicit, validated migration into the
|
|
43
|
+
persistent store. A completed profile can also be bound non-destructively to an
|
|
44
|
+
additional DSH workspace when its reviewed connection contract and published
|
|
45
|
+
semantic digest are unchanged.
|
|
42
46
|
|
|
43
47
|
CLI, Launcher, the analysis Skill, install state, and Runtime state live in the
|
|
44
48
|
user-level Foggy component directories. The managed CLI is intentionally isolated
|
|
@@ -47,6 +51,19 @@ analysis Skill, backs up modified or outdated managed content, restores it, and
|
|
|
47
51
|
invalidates DSH's Skill catalog. The onboarding Skill is bundled with the plugin
|
|
48
52
|
and is restored by reinstalling or upgrading the plugin package.
|
|
49
53
|
|
|
54
|
+
The Foggy settings tab shows the persisted database/semantic onboarding stages,
|
|
55
|
+
offers pinned checks and repair for CLI, Launcher, and the managed analysis
|
|
56
|
+
Skill, and exports a private redacted diagnostics report. Runtime start is
|
|
57
|
+
idempotent: an already-recorded process is verified with `wait-ready` and
|
|
58
|
+
`capabilities` instead of being treated as a failed second start.
|
|
59
|
+
|
|
60
|
+
This beta remains a local dev/test integration. The bundled Runtime reports
|
|
61
|
+
`securityMode=none-dev-test-only` and must not be exposed to a network or used as
|
|
62
|
+
a production service.
|
|
63
|
+
|
|
64
|
+
See [`docs/PUBLIC-BETA-READINESS.md`](./docs/PUBLIC-BETA-READINESS.md) for the
|
|
65
|
+
tested public Beta scope, release gates, and stable-release blockers.
|
|
66
|
+
|
|
50
67
|
## Linux and WSL2 experience
|
|
51
68
|
|
|
52
69
|
Ubuntu and WSL2 users can use the checked-in preflighted installer under
|
package/lib/client.js
CHANGED
|
@@ -10,7 +10,10 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
const passthroughSchema = { parse: (value) => value }
|
|
11
11
|
const TYPERT_REMOTE = {
|
|
12
12
|
package: '@foggy-projects/deepseek-harness-plugin',
|
|
13
|
-
descriptors: [
|
|
13
|
+
descriptors: [
|
|
14
|
+
'status', 'plan', 'initialize', 'repair', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
|
|
15
|
+
'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
|
|
16
|
+
].map((method) => ({
|
|
14
17
|
id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
|
|
15
18
|
service: 'foggyIntegration',
|
|
16
19
|
namespace: 'foggyIntegration',
|
|
@@ -51,6 +54,12 @@ window.__ModuleLoader__.load({
|
|
|
51
54
|
unavailable: '不可用',
|
|
52
55
|
initialize: '初始化 Foggy',
|
|
53
56
|
repair: '重新下载 / 修复',
|
|
57
|
+
repairTitle: '分组件检查与修复',
|
|
58
|
+
repairCli: '检查 / 修复 CLI',
|
|
59
|
+
repairLauncher: '检查 / 修复 Launcher',
|
|
60
|
+
repairAnalysisSkill: '检查 / 修复分析 Skill',
|
|
61
|
+
diagnostics: '导出诊断报告',
|
|
62
|
+
diagnosticsSaved: '诊断报告已保存',
|
|
54
63
|
start: '启动 Runtime',
|
|
55
64
|
stop: '停止 Runtime',
|
|
56
65
|
working: '操作进行中…',
|
|
@@ -69,6 +78,22 @@ window.__ModuleLoader__.load({
|
|
|
69
78
|
installRoot: '组件目录',
|
|
70
79
|
dataRoot: '数据目录',
|
|
71
80
|
profileStore: 'CLI Profile',
|
|
81
|
+
profileMigrationTitle: '旧 Profile 迁移',
|
|
82
|
+
profileMigrationPending: '检测到旧版临时 Profile。迁移只转移连接元数据和密码环境变量引用,不复制密码值。',
|
|
83
|
+
profileMigrationConflict: '旧 Profile 存在冲突或格式问题,请先导出诊断报告。',
|
|
84
|
+
migrateProfiles: '迁移到持久目录',
|
|
85
|
+
onboardingProgress: '数据库与语义层进度',
|
|
86
|
+
noOnboarding: '尚无数据库引导记录;请在对话中调用 Foggy onboarding Skill。',
|
|
87
|
+
projectRoot: '工作区',
|
|
88
|
+
resumeInChat: '未完成步骤应回到对应工作区,在对话中继续。',
|
|
89
|
+
stepPlanned: '规划',
|
|
90
|
+
stepDatasourceConfigured: '数据库连接',
|
|
91
|
+
stepDatasourceVerified: '连接验证',
|
|
92
|
+
stepSchemaDiscovered: 'Schema 发现',
|
|
93
|
+
stepSemanticDrafted: '语义层草拟',
|
|
94
|
+
stepSemanticValidated: '模型校验',
|
|
95
|
+
stepSemanticPublished: '发布',
|
|
96
|
+
stepSemanticVerified: '首次查询',
|
|
72
97
|
runtimeUrl: 'Runtime 地址',
|
|
73
98
|
nextTitle: '后续配置',
|
|
74
99
|
nextCopy: 'Skills 已通过 DeepSeek Harness 原生注册表提供给所有工作区;每个会话直接使用自己的工作目录。Runtime 启动成功后,将继续进入数据库连接与语义层向导。',
|
|
@@ -100,6 +125,12 @@ window.__ModuleLoader__.load({
|
|
|
100
125
|
unavailable: 'Unavailable',
|
|
101
126
|
initialize: 'Initialize Foggy',
|
|
102
127
|
repair: 'Re-download / Repair',
|
|
128
|
+
repairTitle: 'Component checks and repair',
|
|
129
|
+
repairCli: 'Check / repair CLI',
|
|
130
|
+
repairLauncher: 'Check / repair Launcher',
|
|
131
|
+
repairAnalysisSkill: 'Check / repair analysis Skill',
|
|
132
|
+
diagnostics: 'Export diagnostics',
|
|
133
|
+
diagnosticsSaved: 'Diagnostics saved',
|
|
103
134
|
start: 'Start Runtime',
|
|
104
135
|
stop: 'Stop Runtime',
|
|
105
136
|
working: 'Operation in progress…',
|
|
@@ -118,6 +149,22 @@ window.__ModuleLoader__.load({
|
|
|
118
149
|
installRoot: 'Components',
|
|
119
150
|
dataRoot: 'Data',
|
|
120
151
|
profileStore: 'CLI profiles',
|
|
152
|
+
profileMigrationTitle: 'Legacy profile migration',
|
|
153
|
+
profileMigrationPending: 'Legacy temporary profiles were found. Migration transfers connection metadata and password environment-variable references, never password values.',
|
|
154
|
+
profileMigrationConflict: 'A legacy profile has a conflict or invalid format. Export diagnostics before continuing.',
|
|
155
|
+
migrateProfiles: 'Move to persistent store',
|
|
156
|
+
onboardingProgress: 'Database and semantic-layer progress',
|
|
157
|
+
noOnboarding: 'No database onboarding record yet. Invoke the Foggy onboarding Skill in a conversation.',
|
|
158
|
+
projectRoot: 'Workspace',
|
|
159
|
+
resumeInChat: 'Resume incomplete steps from a conversation in the matching workspace.',
|
|
160
|
+
stepPlanned: 'Plan',
|
|
161
|
+
stepDatasourceConfigured: 'Database connection',
|
|
162
|
+
stepDatasourceVerified: 'Connection test',
|
|
163
|
+
stepSchemaDiscovered: 'Schema discovery',
|
|
164
|
+
stepSemanticDrafted: 'Semantic draft',
|
|
165
|
+
stepSemanticValidated: 'Model validation',
|
|
166
|
+
stepSemanticPublished: 'Publish',
|
|
167
|
+
stepSemanticVerified: 'First query',
|
|
121
168
|
runtimeUrl: 'Runtime URL',
|
|
122
169
|
nextTitle: 'Next configuration',
|
|
123
170
|
nextCopy: 'Skills are provided to every workspace through the native DeepSeek Harness registry, and each session uses its own working directory. After Runtime starts, the database connection and semantic-layer wizard comes next.',
|
|
@@ -133,7 +180,7 @@ window.__ModuleLoader__.load({
|
|
|
133
180
|
.foggy-actions{display:flex;gap:8px;flex-wrap:wrap}.foggy-button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:7px 12px;cursor:pointer}.foggy-button:hover{background:var(--dsw-alias-interactive-bg-hover)}.foggy-button:disabled{opacity:.55;cursor:not-allowed}.foggy-button-primary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:white}
|
|
134
181
|
.foggy-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.foggy-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-card-head{display:flex;justify-content:space-between;gap:8px}.foggy-card strong{font-size:13px}.foggy-badge{font-size:11px;color:var(--dsw-alias-label-secondary)}.foggy-card code{display:block;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;overflow-wrap:anywhere}
|
|
135
182
|
.foggy-progress{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-progress-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px}.foggy-progress-head strong{font-weight:600}.foggy-progress-percent{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.foggy-progress-track{height:8px;margin-top:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}.foggy-progress-fill{height:100%;border-radius:inherit;background:var(--dsw-alias-state-business-primary);transition:width .25s ease}.foggy-progress-meta{display:flex;justify-content:space-between;gap:12px;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.foggy-progress-file{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
136
|
-
.foggy-message{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.foggy-message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.foggy-paths,.foggy-next{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4{margin:0 0 8px;font-size:13px}.foggy-paths dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:6px 10px;margin:0}.foggy-paths dt{color:var(--dsw-alias-label-tertiary);font-size:12px}.foggy-paths dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:var(--ds-font-family-code);font-size:11px}.foggy-next p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
183
|
+
.foggy-message{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.foggy-message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.foggy-paths,.foggy-next,.foggy-section{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4,.foggy-section h4{margin:0 0 8px;font-size:13px}.foggy-paths dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:6px 10px;margin:0}.foggy-paths dt{color:var(--dsw-alias-label-tertiary);font-size:12px}.foggy-paths dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:var(--ds-font-family-code);font-size:11px}.foggy-next p,.foggy-section p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.foggy-section-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.foggy-onboarding-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.foggy-onboarding-head code{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.foggy-step-list{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;margin-top:10px}.foggy-step{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;font-size:11px;color:var(--dsw-alias-label-tertiary)}.foggy-step[data-state=completed]{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-success-primary)}.foggy-step[data-state=failed],.foggy-step[data-state=invalid]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.foggy-warning{border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary) 55%,var(--dsw-alias-border-l2));background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);border-radius:10px;padding:12px}
|
|
137
184
|
@media (width<=680px){.foggy-grid{grid-template-columns:minmax(0,1fr)}}
|
|
138
185
|
`
|
|
139
186
|
|
|
@@ -215,6 +262,41 @@ window.__ModuleLoader__.load({
|
|
|
215
262
|
})
|
|
216
263
|
}
|
|
217
264
|
|
|
265
|
+
function onboardingPanel(onboarding, t) {
|
|
266
|
+
const profile = onboarding?.profiles?.[0]
|
|
267
|
+
if (!profile) return jsxs('div', { className: 'foggy-section', children: [
|
|
268
|
+
jsx('h4', { children: t('onboardingProgress') }),
|
|
269
|
+
jsx('p', { children: t('noOnboarding') }),
|
|
270
|
+
] })
|
|
271
|
+
const steps = [
|
|
272
|
+
['planned', 'stepPlanned'],
|
|
273
|
+
['datasourceConfigured', 'stepDatasourceConfigured'],
|
|
274
|
+
['datasourceVerified', 'stepDatasourceVerified'],
|
|
275
|
+
['schemaDiscovered', 'stepSchemaDiscovered'],
|
|
276
|
+
['semanticDrafted', 'stepSemanticDrafted'],
|
|
277
|
+
['semanticValidated', 'stepSemanticValidated'],
|
|
278
|
+
['semanticPublished', 'stepSemanticPublished'],
|
|
279
|
+
['semanticVerified', 'stepSemanticVerified'],
|
|
280
|
+
]
|
|
281
|
+
const percent = Math.round(((profile.completedSteps || 0) / Math.max(profile.totalSteps || 8, 1)) * 100)
|
|
282
|
+
return jsxs('div', { className: 'foggy-section', children: [
|
|
283
|
+
jsxs('div', { className: 'foggy-onboarding-head', children: [
|
|
284
|
+
jsxs('div', { children: [
|
|
285
|
+
jsx('h4', { children: `${t('onboardingProgress')} · ${profile.profile}` }),
|
|
286
|
+
jsx('code', { children: `${t('projectRoot')}: ${profile.projectRoot || '—'}` }),
|
|
287
|
+
] }),
|
|
288
|
+
jsx('span', { className: 'foggy-progress-percent', children: `${percent}%` }),
|
|
289
|
+
] }),
|
|
290
|
+
jsx('div', { className: 'foggy-step-list', children: steps.map(([name, label]) => jsx('div', {
|
|
291
|
+
className: 'foggy-step',
|
|
292
|
+
'data-state': profile.steps?.[name]?.status || 'pending',
|
|
293
|
+
title: profile.steps?.[name]?.status || 'pending',
|
|
294
|
+
children: t(label),
|
|
295
|
+
}, name)) }),
|
|
296
|
+
profile.next?.status !== 'completed' ? jsx('p', { style: { marginTop: '9px' }, children: t('resumeInChat') }) : null,
|
|
297
|
+
] })
|
|
298
|
+
}
|
|
299
|
+
|
|
218
300
|
function FoggySettingsTab({ api, t }) {
|
|
219
301
|
const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false })
|
|
220
302
|
|
|
@@ -239,7 +321,12 @@ window.__ModuleLoader__.load({
|
|
|
239
321
|
setView((current) => ({ ...current, message: t('working'), error: false }))
|
|
240
322
|
try {
|
|
241
323
|
const result = unwrap(await api[method](), method)
|
|
242
|
-
|
|
324
|
+
const message = result.accepted
|
|
325
|
+
? t('accepted')
|
|
326
|
+
: result.path
|
|
327
|
+
? `${t('diagnosticsSaved')}: ${result.path}`
|
|
328
|
+
: ''
|
|
329
|
+
setView((current) => ({ ...current, message, error: result.success === false }))
|
|
243
330
|
await refresh()
|
|
244
331
|
} catch (error) {
|
|
245
332
|
setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true }))
|
|
@@ -268,6 +355,7 @@ window.__ModuleLoader__.load({
|
|
|
268
355
|
jsxs('div', { className: 'foggy-state', children: [jsx('span', { className: 'foggy-dot', 'data-state': status.state }), jsx('span', { children: t(stateLabels[status.state] || 'notInstalled') })] }),
|
|
269
356
|
jsxs('div', { className: 'foggy-actions', children: [
|
|
270
357
|
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: refresh, children: t('refresh') }),
|
|
358
|
+
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('diagnostics'), children: t('diagnostics') }),
|
|
271
359
|
status.state === 'not-installed' ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('initialize'), children: t('initialize') }) : null,
|
|
272
360
|
status.state !== 'not-installed' ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repair'), children: t('repair') }) : null,
|
|
273
361
|
status.installed && !status.running && status.components.java.available ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('runtimeStart'), children: t('start') }) : null,
|
|
@@ -285,6 +373,23 @@ window.__ModuleLoader__.load({
|
|
|
285
373
|
componentCard(t('onboardingSkill'), status.components.onboardingSkill, 'runtime', t),
|
|
286
374
|
componentCard(t('skillRegistry'), { installed: status.components.onboardingSkill?.provider === 'foggy-managed-skills', version: 'native' }, 'runtime', t),
|
|
287
375
|
] }),
|
|
376
|
+
jsxs('div', { className: 'foggy-section', children: [
|
|
377
|
+
jsx('h4', { children: t('repairTitle') }),
|
|
378
|
+
jsx('div', { className: 'foggy-section-actions', children: [
|
|
379
|
+
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairCli'), children: t('repairCli') }),
|
|
380
|
+
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairLauncher'), children: t('repairLauncher') }),
|
|
381
|
+
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairAnalysisSkill'), children: t('repairAnalysisSkill') }),
|
|
382
|
+
] }),
|
|
383
|
+
] }),
|
|
384
|
+
status.profileMigration?.pendingCount || status.profileMigration?.conflictCount ? jsxs('div', { className: 'foggy-warning', children: [
|
|
385
|
+
jsx('h4', { children: t('profileMigrationTitle') }),
|
|
386
|
+
jsx('p', { children: status.profileMigration.conflictCount ? t('profileMigrationConflict') : t('profileMigrationPending') }),
|
|
387
|
+
!status.profileMigration.conflictCount ? jsx('div', { className: 'foggy-section-actions', children: jsx('button', {
|
|
388
|
+
className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy,
|
|
389
|
+
onClick: () => run('migrateProfiles'), children: t('migrateProfiles'),
|
|
390
|
+
}) }) : null,
|
|
391
|
+
] }) : null,
|
|
392
|
+
onboardingPanel(status.onboarding, t),
|
|
288
393
|
jsxs('div', { className: 'foggy-paths', children: [
|
|
289
394
|
jsx('h4', { children: t('roots') }),
|
|
290
395
|
jsxs('dl', { children: [
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process'
|
|
2
|
-
import { access, readFile } from 'node:fs/promises'
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { constants as fsConstants } from 'node:fs'
|
|
4
4
|
import { dirname, join, delimiter } from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
@@ -157,6 +157,16 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
157
157
|
let runtime = null
|
|
158
158
|
try { state = await readJson(statePath) } catch {}
|
|
159
159
|
try { runtime = await readJson(runtimeStatePath) } catch {}
|
|
160
|
+
let onboarding = { success: true, profiles: [], profileCount: 0 }
|
|
161
|
+
let profileMigration = { success: true, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore }
|
|
162
|
+
if (state) {
|
|
163
|
+
try { onboarding = await runOnboarding(['onboard-list']) } catch (error) {
|
|
164
|
+
onboarding = { success: false, profiles: [], profileCount: 0, error: String(error.message ?? error) }
|
|
165
|
+
}
|
|
166
|
+
try { profileMigration = await runOnboarding(['profile-migration-status']) } catch (error) {
|
|
167
|
+
profileMigration = { success: false, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore, error: String(error.message ?? error) }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
160
170
|
const cliPath = state?.cli?.command
|
|
161
171
|
const launcherPath = state?.launcher?.path
|
|
162
172
|
const analysisSkillPath = state?.skills?.analysis?.path || join(roots.installRoot, 'skills', 'foggy-ai-analysis')
|
|
@@ -218,6 +228,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
218
228
|
roots,
|
|
219
229
|
components,
|
|
220
230
|
operation,
|
|
231
|
+
onboarding,
|
|
232
|
+
profileMigration,
|
|
221
233
|
next: installed ? (running ? 'configure-database' : 'start-runtime') : 'initialize',
|
|
222
234
|
}
|
|
223
235
|
}
|
|
@@ -249,6 +261,49 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
249
261
|
return this.startOperation('repair', true)
|
|
250
262
|
}
|
|
251
263
|
|
|
264
|
+
async repairCli() {
|
|
265
|
+
return this.startOperation('repair-cli', false, ['install', '--repair-component', 'cli'])
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async repairLauncher() {
|
|
269
|
+
return this.startOperation('repair-launcher', false, ['install', '--repair-component', 'launcher'])
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async repairAnalysisSkill() {
|
|
273
|
+
return this.startOperation('repair-analysis-skill', true, ['install', '--repair-component', 'analysis-skill'])
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async migrateProfiles() {
|
|
277
|
+
return this.startOperation('profile-migration', false, ['profile-migrate', '--approve'])
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async diagnostics() {
|
|
281
|
+
const roots = defaultRoots()
|
|
282
|
+
const [status, doctor] = await Promise.all([
|
|
283
|
+
this.status(),
|
|
284
|
+
runOnboarding(['doctor', '--no-fail']),
|
|
285
|
+
])
|
|
286
|
+
const report = {
|
|
287
|
+
schemaVersion: 'foggy-deepseek-diagnostics/v1',
|
|
288
|
+
generatedAt: new Date().toISOString(),
|
|
289
|
+
packageVersion: status.packageVersion,
|
|
290
|
+
state: status.state,
|
|
291
|
+
installed: status.installed,
|
|
292
|
+
running: status.running,
|
|
293
|
+
runtimeUrl: status.runtimeUrl,
|
|
294
|
+
roots: status.roots,
|
|
295
|
+
components: status.components,
|
|
296
|
+
onboarding: status.onboarding,
|
|
297
|
+
profileMigration: status.profileMigration,
|
|
298
|
+
doctor,
|
|
299
|
+
}
|
|
300
|
+
const directory = join(roots.dataRoot, 'diagnostics')
|
|
301
|
+
await mkdir(directory, { recursive: true })
|
|
302
|
+
const path = join(directory, `diagnostics-${Date.now()}.json`)
|
|
303
|
+
await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
|
|
304
|
+
return { success: true, path, report }
|
|
305
|
+
}
|
|
306
|
+
|
|
252
307
|
async runtimeStart() {
|
|
253
308
|
return this.startOperation('runtime-start', false, ['runtime-start'])
|
|
254
309
|
}
|
|
@@ -263,7 +318,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
263
318
|
}
|
|
264
319
|
const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
|
|
265
320
|
const args = explicitArgs ?? ['install']
|
|
266
|
-
|
|
321
|
+
const reportsProgress = args[0] === 'install'
|
|
322
|
+
if (reportsProgress) {
|
|
267
323
|
const roots = defaultRoots()
|
|
268
324
|
args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
|
|
269
325
|
}
|
|
@@ -279,7 +335,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
279
335
|
finishedAt: null,
|
|
280
336
|
result: null,
|
|
281
337
|
error: null,
|
|
282
|
-
progress:
|
|
338
|
+
progress: reportsProgress ? {
|
|
283
339
|
schemaVersion: 'foggy-deepseek-onboarding-progress/v1',
|
|
284
340
|
operationId: id,
|
|
285
341
|
kind,
|
|
@@ -327,7 +383,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
327
383
|
}
|
|
328
384
|
|
|
329
385
|
const markerInitializers = []
|
|
330
|
-
for (const method of [
|
|
386
|
+
for (const method of [
|
|
387
|
+
'status', 'plan', 'initialize', 'repair', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
|
|
388
|
+
'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
|
|
389
|
+
]) {
|
|
331
390
|
Remote(method)(FoggyIntegrationGateway.prototype[method], {
|
|
332
391
|
kind: 'method',
|
|
333
392
|
name: method,
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ copy this Skill into the current workspace.
|
|
|
22
22
|
its absolute command in the global install state.
|
|
23
23
|
- Do not independently download or reinstall the CLI. If the global install state, managed marker, or
|
|
24
24
|
analysis Skill is missing or invalid, ask the user to open the Foggy plugin settings and use
|
|
25
|
-
|
|
25
|
+
the matching component repair action. Repair restores managed components and invalidates the
|
|
26
26
|
native DSH Skill catalog. Reinstall or upgrade the plugin itself to restore this bundled Skill.
|
|
27
27
|
- Treat the current DSH session workspace as the authoritative `projectRoot` for the whole onboarding
|
|
28
28
|
run. Do not redirect semantic drafts or contracts to a different repository merely because another
|
|
@@ -75,7 +75,9 @@ For every new-database onboarding session, this Skill is the orchestration autho
|
|
|
75
75
|
plugin's Repair action. Use the matching install script only when the plugin UI is unavailable;
|
|
76
76
|
use `--dry-run` first when paths or permissions are uncertain.
|
|
77
77
|
3. Run `runtime-start` and require successful `wait-ready` plus `capabilities`. Record engine,
|
|
78
|
-
Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path.
|
|
78
|
+
Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path. If the
|
|
79
|
+
recorded Runtime is already running, `runtime-start` verifies and reuses it instead of starting a
|
|
80
|
+
second process.
|
|
79
81
|
4. Load `foggy-ai-analysis` from the native DSH Skill registry. Do not require a workspace copy.
|
|
80
82
|
5. For a new business database, read [references/onboarding-workflow.md](references/onboarding-workflow.md)
|
|
81
83
|
and prefer its two composite `onboard-datasource-run` / `onboard-semantic-run` commands. Require the
|
|
@@ -84,6 +86,9 @@ For every new-database onboarding session, this Skill is the orchestration autho
|
|
|
84
86
|
(`<dataRoot>/cli-profiles`), never `/tmp`. Accept only the opaque profile
|
|
85
87
|
ID, exact revision, datasource name/type, and namespace; never request JDBC URL, username,
|
|
86
88
|
password, or password environment-variable name in Harness.
|
|
89
|
+
If plugin settings report a legacy temporary profile, use the explicit migration action before
|
|
90
|
+
onboarding. It moves only validated connection metadata and environment-variable references; it
|
|
91
|
+
never copies a password value and leaves a recoverable private backup below the Foggy data root.
|
|
87
92
|
6. After schema discovery, use `foggy-ai-analysis` only to author TM/QM files in the standard project-local
|
|
88
93
|
draft directory. Register, validate, publish, and verify them through this Skill's wrapper using the deterministic commands in
|
|
89
94
|
[references/onboarding-workflow.md](references/onboarding-workflow.md). Do not publish, prune, replace
|
|
@@ -114,5 +119,13 @@ publication, and verification phases when the approved contract and draft digest
|
|
|
114
119
|
query payload in place and rerun the same semantic composite command; do not remove a successfully
|
|
115
120
|
published bundle merely to recover from a later query-validation failure.
|
|
116
121
|
|
|
122
|
+
An already-completed profile may be reused from another DSH workspace when the approved connection
|
|
123
|
+
contract is byte-for-byte equivalent. Run the datasource composite in the new workspace first; it adds
|
|
124
|
+
that workspace as a non-destructive binding and reuses datasource/schema checkpoints. Then pass the
|
|
125
|
+
current workspace explicitly as `--project-root` to the semantic composite. A secondary workspace may
|
|
126
|
+
reuse an identical published semantic digest and run its own bounded verification query, but it cannot
|
|
127
|
+
replace the published semantic layer; changes must be published from the original workspace or a new
|
|
128
|
+
profile.
|
|
129
|
+
|
|
117
130
|
Keep user business data separate from the sales-drop SQLite demo. Prefer a read-only database account,
|
|
118
131
|
opaque CLI profile references, and bounded query limits.
|
|
@@ -13,6 +13,26 @@
|
|
|
13
13
|
"installRoot": {"type": "string"},
|
|
14
14
|
"dataRoot": {"type": "string"},
|
|
15
15
|
"projectRoot": {"type": "string"},
|
|
16
|
+
"workspaceBindings": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": {"type": "string"},
|
|
19
|
+
"uniqueItems": true
|
|
20
|
+
},
|
|
21
|
+
"workspaceVerifications": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"required": ["projectRoot", "queryModel", "queryPayloadDigest", "verifiedAt"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"projectRoot": {"type": "string"},
|
|
28
|
+
"queryModel": {"type": "string"},
|
|
29
|
+
"queryPayloadDigest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
|
|
30
|
+
"rowCount": {"type": ["integer", "null"], "minimum": 0},
|
|
31
|
+
"verifiedAt": {"type": "string", "format": "date-time"}
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
}
|
|
35
|
+
},
|
|
16
36
|
"runtime": {"type": "object"},
|
|
17
37
|
"connection": {"$ref": "connection.schema.json"},
|
|
18
38
|
"semantic": {"type": "object"},
|
|
@@ -33,6 +33,12 @@ The wrapper defaults `FOGGY_RUNTIME_PROFILE_STORE` to the private persistent dir
|
|
|
33
33
|
`<dataRoot>/cli-profiles`. An explicit operator-provided value still wins. Do not use `/tmp` for a profile
|
|
34
34
|
that must survive a WSL or Harness restart.
|
|
35
35
|
|
|
36
|
+
If plugin settings detect profiles in the legacy temporary store, use **Move to persistent store**.
|
|
37
|
+
Migration validates the opaque profile schema, rejects embedded passwords and conflicts, writes the
|
|
38
|
+
destination with private permissions, verifies it, and moves the legacy JSON to a recoverable private
|
|
39
|
+
backup below the Foggy data root.
|
|
40
|
+
Do not manually copy or edit opaque profile JSON.
|
|
41
|
+
|
|
36
42
|
Treat every user-supplied identifier and bound as immutable input: profile, datasource, namespace,
|
|
37
43
|
models directory, bundle name, TM/QM name, query fields, and limit. Do not swap in demo names, add
|
|
38
44
|
fields, raise the limit, or introduce replacement flags. If one of these inputs is missing, pause for
|
|
@@ -52,11 +58,13 @@ For Harness-driven onboarding, use the two composite commands below. Each writes
|
|
|
52
58
|
evidence internally and refuses to cross an unapproved mutation gate:
|
|
53
59
|
|
|
54
60
|
```text
|
|
55
|
-
onboard-datasource-run --
|
|
61
|
+
onboard-datasource-run --project-root <current-session-workspace> \
|
|
62
|
+
--connection-file <approved-json> \
|
|
56
63
|
--approve-configure --approve-bind --include-indexes
|
|
57
64
|
|
|
58
65
|
# After authoring the registered TM/QM draft from schema metadata:
|
|
59
|
-
onboard-semantic-run --
|
|
66
|
+
onboard-semantic-run --project-root <current-session-workspace> \
|
|
67
|
+
--semantic-plan <approved-json> --query-payload <approved-json> \
|
|
60
68
|
--approve-validate --approve-publish --approve-execute
|
|
61
69
|
```
|
|
62
70
|
|
|
@@ -70,6 +78,12 @@ payload and rerun `onboard-semantic-run`; it resumes at query verification and l
|
|
|
70
78
|
place. A same-name datasource is accepted idempotently only when its public name and database type match
|
|
71
79
|
the approved plan; otherwise replacement still requires explicit approval.
|
|
72
80
|
|
|
81
|
+
When a completed profile already belongs to another workspace, the datasource composite may add the
|
|
82
|
+
current workspace as a binding only if the approved connection contract is identical and the datasource,
|
|
83
|
+
schema, and semantic publication checkpoints are complete. The semantic composite then accepts the same
|
|
84
|
+
published draft digest from the bound workspace and performs a workspace-specific bounded verification
|
|
85
|
+
query. It refuses semantic replacement from the secondary workspace.
|
|
86
|
+
|
|
73
87
|
The granular commands below remain available for manual troubleshooting and resumption. Do not expand
|
|
74
88
|
the composite commands into this list during a normal Harness turn.
|
|
75
89
|
|
|
@@ -85,8 +99,8 @@ semantic-validate --profile <profile>
|
|
|
85
99
|
semantic-validate --profile <profile> --apply
|
|
86
100
|
semantic-publish --profile <profile>
|
|
87
101
|
semantic-publish --profile <profile> --apply
|
|
88
|
-
semantic-verify --profile <profile> --query-payload <json>
|
|
89
|
-
semantic-verify --profile <profile> --query-payload <json> --execute
|
|
102
|
+
semantic-verify --profile <profile> --project-root <current-session-workspace> --query-payload <json>
|
|
103
|
+
semantic-verify --profile <profile> --project-root <current-session-workspace> --query-payload <json> --execute
|
|
90
104
|
onboard-status --profile <profile>
|
|
91
105
|
```
|
|
92
106
|
|
|
@@ -34,6 +34,7 @@ PROFILE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,62}$")
|
|
|
34
34
|
ENV_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
35
35
|
OPAQUE_PROFILE_PATTERN = re.compile(r"^fop_[a-f0-9]{32}$")
|
|
36
36
|
OPAQUE_REVISION_PATTERN = re.compile(r"^sha256:[a-f0-9]{64}$")
|
|
37
|
+
OPAQUE_PROFILE_SCHEMA = "foggy-runtime-onboarding-profile/v1"
|
|
37
38
|
MODEL_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,127}$")
|
|
38
39
|
|
|
39
40
|
|
|
@@ -283,11 +284,20 @@ def materialize(
|
|
|
283
284
|
progress_index: int = 0,
|
|
284
285
|
progress_total: int = 1,
|
|
285
286
|
progress_message: str = "Downloading and verifying asset",
|
|
287
|
+
replace_corrupt: bool = False,
|
|
286
288
|
) -> dict:
|
|
287
289
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
288
290
|
if destination.is_file():
|
|
289
|
-
|
|
290
|
-
|
|
291
|
+
try:
|
|
292
|
+
verify_asset(destination, asset["sha256"])
|
|
293
|
+
return {"file": asset["file"], "path": str(destination), "source": "existing", "sha256": asset["sha256"]}
|
|
294
|
+
except OnboardingError:
|
|
295
|
+
if not replace_corrupt:
|
|
296
|
+
raise
|
|
297
|
+
quarantine = destination.with_name(
|
|
298
|
+
destination.name + f".corrupt-{dt.datetime.now().strftime('%Y%m%d-%H%M%S-%f')}"
|
|
299
|
+
)
|
|
300
|
+
destination.replace(quarantine)
|
|
291
301
|
cached = cached_asset(asset["file"], asset["sha256"], cache_dirs)
|
|
292
302
|
if cached:
|
|
293
303
|
shutil.copy2(cached, destination)
|
|
@@ -461,6 +471,153 @@ def configure_profile_store(data_root: Path, *, create: bool = True) -> Path:
|
|
|
461
471
|
return destination
|
|
462
472
|
|
|
463
473
|
|
|
474
|
+
def legacy_profile_stores(destination: Path) -> list[Path]:
|
|
475
|
+
configured = [
|
|
476
|
+
normalized(item)
|
|
477
|
+
for item in os.environ.get("FOGGY_RUNTIME_PROFILE_LEGACY_STORES", "").split(os.pathsep)
|
|
478
|
+
if item.strip()
|
|
479
|
+
]
|
|
480
|
+
default_legacy = Path(tempfile.gettempdir()) / "foggy-profiles"
|
|
481
|
+
candidates = [*configured, normalized(default_legacy)]
|
|
482
|
+
unique: list[Path] = []
|
|
483
|
+
for candidate in candidates:
|
|
484
|
+
if candidate == destination or candidate in unique:
|
|
485
|
+
continue
|
|
486
|
+
unique.append(candidate)
|
|
487
|
+
return unique
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def validate_opaque_profile_document(payload: dict, path: Path) -> dict:
|
|
491
|
+
allowed = {"schemaVersion", "profileId", "revision", "createdAt", "updatedAt", "connection"}
|
|
492
|
+
unexpected = sorted(set(payload) - allowed)
|
|
493
|
+
if unexpected:
|
|
494
|
+
raise OnboardingError(f"Unsupported opaque profile fields in {path.name}: {', '.join(unexpected)}")
|
|
495
|
+
if payload.get("schemaVersion") != OPAQUE_PROFILE_SCHEMA:
|
|
496
|
+
raise OnboardingError(f"Unexpected opaque profile schema in {path.name}")
|
|
497
|
+
profile_id = payload.get("profileId")
|
|
498
|
+
revision = payload.get("revision")
|
|
499
|
+
if not isinstance(profile_id, str) or not OPAQUE_PROFILE_PATTERN.fullmatch(profile_id):
|
|
500
|
+
raise OnboardingError(f"Invalid opaque profile ID in {path.name}")
|
|
501
|
+
if path.stem != profile_id:
|
|
502
|
+
raise OnboardingError(f"Opaque profile filename does not match its ID: {path.name}")
|
|
503
|
+
if not isinstance(revision, str) or not OPAQUE_REVISION_PATTERN.fullmatch(revision):
|
|
504
|
+
raise OnboardingError(f"Invalid opaque profile revision in {path.name}")
|
|
505
|
+
connection = payload.get("connection")
|
|
506
|
+
if not isinstance(connection, dict):
|
|
507
|
+
raise OnboardingError(f"Opaque profile connection is missing in {path.name}")
|
|
508
|
+
allowed_connection = {"name", "type", "jdbcUrl", "username", "passwordEnv", "namespace"}
|
|
509
|
+
unexpected_connection = sorted(set(connection) - allowed_connection)
|
|
510
|
+
if unexpected_connection:
|
|
511
|
+
raise OnboardingError(
|
|
512
|
+
f"Unsupported opaque connection fields in {path.name}: {', '.join(unexpected_connection)}"
|
|
513
|
+
)
|
|
514
|
+
for name in ("name", "type", "jdbcUrl", "namespace"):
|
|
515
|
+
if not isinstance(connection.get(name), str) or not connection[name].strip():
|
|
516
|
+
raise OnboardingError(f"Opaque profile connection.{name} is invalid in {path.name}")
|
|
517
|
+
password_env = connection.get("passwordEnv")
|
|
518
|
+
if password_env is not None and (
|
|
519
|
+
not isinstance(password_env, str) or not ENV_NAME_PATTERN.fullmatch(password_env)
|
|
520
|
+
):
|
|
521
|
+
raise OnboardingError(f"Opaque profile passwordEnv is invalid in {path.name}")
|
|
522
|
+
jdbc_url = connection["jdbcUrl"]
|
|
523
|
+
if re.search(r"(?i)(?:password|passwd|pwd)\s*=", jdbc_url) or re.search(r"//[^/@:]+:[^/@]+@", jdbc_url):
|
|
524
|
+
raise OnboardingError(f"Opaque profile embeds a password in jdbcUrl: {path.name}")
|
|
525
|
+
return payload
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def profile_migration_inventory(data_root: Path) -> dict:
|
|
529
|
+
destination = configure_profile_store(data_root, create=False)
|
|
530
|
+
entries: list[dict] = []
|
|
531
|
+
for source in legacy_profile_stores(destination):
|
|
532
|
+
if not source.is_dir():
|
|
533
|
+
continue
|
|
534
|
+
for path in sorted(source.glob("fop_*.json")):
|
|
535
|
+
try:
|
|
536
|
+
payload = validate_opaque_profile_document(read_json_object(path, "Opaque profile"), path)
|
|
537
|
+
target = destination / path.name
|
|
538
|
+
status = "pending"
|
|
539
|
+
if target.is_file():
|
|
540
|
+
existing = validate_opaque_profile_document(read_json_object(target, "Opaque profile"), target)
|
|
541
|
+
status = "migrated" if existing == payload else "conflict"
|
|
542
|
+
entries.append({
|
|
543
|
+
"profileId": payload["profileId"],
|
|
544
|
+
"revision": payload["revision"],
|
|
545
|
+
"source": str(source),
|
|
546
|
+
"destination": str(destination),
|
|
547
|
+
"status": status,
|
|
548
|
+
})
|
|
549
|
+
except OnboardingError as exc:
|
|
550
|
+
entries.append({
|
|
551
|
+
"profileId": path.stem if OPAQUE_PROFILE_PATTERN.fullmatch(path.stem) else None,
|
|
552
|
+
"source": str(source),
|
|
553
|
+
"destination": str(destination),
|
|
554
|
+
"status": "invalid",
|
|
555
|
+
"error": str(exc),
|
|
556
|
+
})
|
|
557
|
+
return {
|
|
558
|
+
"schemaVersion": "foggy-deepseek-profile-migration-status/v1",
|
|
559
|
+
"profileStore": str(destination),
|
|
560
|
+
"legacyStores": [str(path) for path in legacy_profile_stores(destination)],
|
|
561
|
+
"entries": entries,
|
|
562
|
+
"pendingCount": sum(item["status"] == "pending" for item in entries),
|
|
563
|
+
"conflictCount": sum(item["status"] in {"conflict", "invalid"} for item in entries),
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def profile_migration_status_command(args: argparse.Namespace) -> dict:
|
|
568
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
569
|
+
install_state = read_install_state(install_root)
|
|
570
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
571
|
+
return {"success": True, **profile_migration_inventory(data_root), "productionReady": False}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def profile_migrate_command(args: argparse.Namespace) -> dict:
|
|
575
|
+
if not args.approve:
|
|
576
|
+
raise OnboardingError("Profile migration requires --approve")
|
|
577
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
578
|
+
install_state = read_install_state(install_root)
|
|
579
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
580
|
+
inventory = profile_migration_inventory(data_root)
|
|
581
|
+
if inventory["conflictCount"]:
|
|
582
|
+
raise OnboardingError("Legacy profile migration has conflicts or invalid entries; inspect status first")
|
|
583
|
+
destination = configure_profile_store(data_root)
|
|
584
|
+
migrated: list[dict] = []
|
|
585
|
+
for item in inventory["entries"]:
|
|
586
|
+
if item["status"] != "pending":
|
|
587
|
+
continue
|
|
588
|
+
source = normalized(Path(item["source"]) / f"{item['profileId']}.json")
|
|
589
|
+
payload = validate_opaque_profile_document(read_json_object(source, "Opaque profile"), source)
|
|
590
|
+
target = destination / source.name
|
|
591
|
+
atomic_json(target, payload)
|
|
592
|
+
if os.name != "nt":
|
|
593
|
+
target.chmod(0o600)
|
|
594
|
+
if read_json_object(target, "Migrated opaque profile") != payload:
|
|
595
|
+
target.unlink(missing_ok=True)
|
|
596
|
+
raise OnboardingError(f"Migrated profile verification failed: {source.name}")
|
|
597
|
+
backup_root = data_root / "profile-migration-backups" / dt.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
|
598
|
+
backup_root.mkdir(parents=True, exist_ok=True)
|
|
599
|
+
if os.name != "nt":
|
|
600
|
+
backup_root.chmod(0o700)
|
|
601
|
+
archived = backup_root / (source.name + ".bak")
|
|
602
|
+
source.replace(archived)
|
|
603
|
+
if os.name != "nt":
|
|
604
|
+
archived.chmod(0o600)
|
|
605
|
+
migrated.append({
|
|
606
|
+
"profileId": payload["profileId"],
|
|
607
|
+
"revision": payload["revision"],
|
|
608
|
+
"destination": str(target),
|
|
609
|
+
"legacyBackup": str(archived),
|
|
610
|
+
})
|
|
611
|
+
return {
|
|
612
|
+
"success": True,
|
|
613
|
+
"schemaVersion": "foggy-deepseek-profile-migration/v1",
|
|
614
|
+
"profileStore": str(destination),
|
|
615
|
+
"migrated": migrated,
|
|
616
|
+
"migratedCount": len(migrated),
|
|
617
|
+
"productionReady": False,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
|
|
464
621
|
def resolve_profile_name(data_root: Path, requested: str | None) -> str:
|
|
465
622
|
if requested:
|
|
466
623
|
return safe_profile(requested)
|
|
@@ -825,6 +982,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
825
982
|
data_root = normalized(args.data_root or default_data_root())
|
|
826
983
|
cache_dirs = [normalized(item) for item in args.asset_cache_dir]
|
|
827
984
|
components = versions["components"]
|
|
985
|
+
repair_component = getattr(args, "repair_component", None)
|
|
828
986
|
assert_managed_root(install_root, "Install root")
|
|
829
987
|
assert_managed_root(data_root, "Data root")
|
|
830
988
|
if install_root == data_root:
|
|
@@ -837,6 +995,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
837
995
|
"profileStore": str(profile_store),
|
|
838
996
|
"workspaceMode": "dsh-session-cwd",
|
|
839
997
|
"versions": {name: value.get("version") for name, value in components.items()},
|
|
998
|
+
"repairComponent": repair_component,
|
|
840
999
|
"operations": ["install isolated CLI", "verify Launcher assets", "install global analysis Skill", "write install state"],
|
|
841
1000
|
"productionReady": False,
|
|
842
1001
|
}
|
|
@@ -870,6 +1029,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
870
1029
|
progress=progress, progress_phase="cli", progress_step=1,
|
|
871
1030
|
progress_index=index, progress_total=len(cli_assets),
|
|
872
1031
|
progress_message="Downloading and verifying CLI",
|
|
1032
|
+
replace_corrupt=repair_component == "cli",
|
|
873
1033
|
))
|
|
874
1034
|
progress.update(
|
|
875
1035
|
"cli", 1, "Downloading and verifying CLI",
|
|
@@ -925,6 +1085,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
925
1085
|
progress=progress, progress_phase="launcher", progress_step=2,
|
|
926
1086
|
progress_index=index, progress_total=len(launcher_assets),
|
|
927
1087
|
progress_message="Downloading and verifying Launcher",
|
|
1088
|
+
replace_corrupt=repair_component == "launcher",
|
|
928
1089
|
))
|
|
929
1090
|
progress.update(
|
|
930
1091
|
"launcher", 2, "Downloading and verifying Launcher",
|
|
@@ -947,6 +1108,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
947
1108
|
progress=progress, progress_phase="analysis-skill", progress_step=3,
|
|
948
1109
|
progress_index=index, progress_total=len(analysis_assets),
|
|
949
1110
|
progress_message="Downloading and verifying analysis Skill",
|
|
1111
|
+
replace_corrupt=repair_component == "analysis-skill",
|
|
950
1112
|
))
|
|
951
1113
|
progress.update(
|
|
952
1114
|
"analysis-skill", 3, "Downloading and verifying analysis Skill",
|
|
@@ -957,7 +1119,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
957
1119
|
progress.update("analysis-skill", 3, "Installing analysis Skill", fraction=0.9, current_file=zip_asset["file"])
|
|
958
1120
|
analysis_skill = install_analysis_skill(
|
|
959
1121
|
downloads / "skill" / zip_asset["file"], install_root, components["analysisSkill"]["version"],
|
|
960
|
-
zip_asset["sha256"], versions["packageVersion"], args.replace_skill,
|
|
1122
|
+
zip_asset["sha256"], versions["packageVersion"], args.replace_skill or repair_component == "analysis-skill",
|
|
961
1123
|
)
|
|
962
1124
|
progress.update("analysis-skill", 3, "Analysis Skill ready", fraction=1.0)
|
|
963
1125
|
progress.update("workspace-skills", 4, "Registering native DSH Skills", fraction=0.1)
|
|
@@ -1088,8 +1250,61 @@ def runtime_start_command(args: argparse.Namespace) -> dict:
|
|
|
1088
1250
|
existing = data_root / "runtime-state.json"
|
|
1089
1251
|
if existing.is_file():
|
|
1090
1252
|
prior = json.loads(existing.read_text(encoding="utf-8"))
|
|
1091
|
-
|
|
1092
|
-
|
|
1253
|
+
prior_info = process_info(int(prior.get("pid", 0)))
|
|
1254
|
+
if prior_info["running"]:
|
|
1255
|
+
expected_jar = f"foggy-runtime-launcher-{state['launcher']['version']}.jar"
|
|
1256
|
+
if prior_info.get("commandLine") and expected_jar not in prior_info["commandLine"]:
|
|
1257
|
+
raise OnboardingError(
|
|
1258
|
+
f"Recorded Runtime PID {prior['pid']} does not match the pinned Launcher"
|
|
1259
|
+
)
|
|
1260
|
+
cli = state["cli"]["command"]
|
|
1261
|
+
namespace = args.namespace or prior.get("namespace") or versions["defaults"]["namespace"]
|
|
1262
|
+
base_url = prior.get("runtimeUrl")
|
|
1263
|
+
if not isinstance(base_url, str) or not base_url:
|
|
1264
|
+
raise OnboardingError("Recorded Runtime does not contain runtimeUrl")
|
|
1265
|
+
wait_result = command_result(
|
|
1266
|
+
[cli, "--base-url", base_url, "--namespace", namespace, "--output", "json", "wait-ready",
|
|
1267
|
+
"--timeout-seconds", str(args.timeout or versions["defaults"]["readinessTimeoutSeconds"]),
|
|
1268
|
+
"--interval-seconds", "1"],
|
|
1269
|
+
timeout=(args.timeout or versions["defaults"]["readinessTimeoutSeconds"]) + 30,
|
|
1270
|
+
)
|
|
1271
|
+
wait_payload = parse_json_output(wait_result, "wait-ready")
|
|
1272
|
+
if wait_payload.get("success") is not True:
|
|
1273
|
+
raise OnboardingError("wait-ready returned success=false for recorded Runtime")
|
|
1274
|
+
capabilities = parse_json_output(
|
|
1275
|
+
command_result(
|
|
1276
|
+
[cli, "--base-url", base_url, "--namespace", namespace, "--output", "json", "capabilities"],
|
|
1277
|
+
timeout=30,
|
|
1278
|
+
),
|
|
1279
|
+
"capabilities",
|
|
1280
|
+
)
|
|
1281
|
+
expected_contract = versions["components"]["launcher"]["runtimeApiContract"]
|
|
1282
|
+
if capabilities.get("success") is not True or capabilities.get("runtimeApiVersion") != expected_contract:
|
|
1283
|
+
raise OnboardingError(f"Unexpected Runtime API contract; expected {expected_contract}")
|
|
1284
|
+
if capabilities.get("data", {}).get("securityMode") != versions["defaults"]["securityMode"]:
|
|
1285
|
+
raise OnboardingError("Recorded Runtime did not report the expected dev/test security mode")
|
|
1286
|
+
verified_at = now_utc()
|
|
1287
|
+
identity = {
|
|
1288
|
+
"engine": capabilities.get("engine"),
|
|
1289
|
+
"runtimeApiVersion": capabilities.get("runtimeApiVersion"),
|
|
1290
|
+
"schemaVersion": capabilities.get("data", {}).get("schemaVersion"),
|
|
1291
|
+
"securityMode": capabilities.get("data", {}).get("securityMode"),
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
"success": True,
|
|
1295
|
+
**prior,
|
|
1296
|
+
"identity": identity,
|
|
1297
|
+
"lastVerifiedAt": verified_at,
|
|
1298
|
+
"verification": {
|
|
1299
|
+
"waitReady": True,
|
|
1300
|
+
"capabilities": identity,
|
|
1301
|
+
"persisted": False,
|
|
1302
|
+
"instruction": "Capture this JSON in the current workspace when fresh verification evidence is required",
|
|
1303
|
+
},
|
|
1304
|
+
"action": "already-running-verified",
|
|
1305
|
+
"resumed": True,
|
|
1306
|
+
"productionReady": False,
|
|
1307
|
+
}
|
|
1093
1308
|
existing.unlink()
|
|
1094
1309
|
port = args.port or int(versions["defaults"]["port"])
|
|
1095
1310
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
@@ -1834,7 +2049,9 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1834
2049
|
raise OnboardingError("--query-model must be declared in the registered semantic plan")
|
|
1835
2050
|
if not args.query_payload:
|
|
1836
2051
|
raise OnboardingError("--query-payload is required for semantic verification")
|
|
1837
|
-
project_root = normalized(state["projectRoot"])
|
|
2052
|
+
project_root = normalized(getattr(args, "project_root", None) or state["projectRoot"])
|
|
2053
|
+
if not project_root_is_bound(state, project_root):
|
|
2054
|
+
raise OnboardingError("Query projectRoot is not bound to this onboarding profile")
|
|
1838
2055
|
payload_path = normalized(args.query_payload)
|
|
1839
2056
|
if not is_child(payload_path, project_root):
|
|
1840
2057
|
raise OnboardingError("Query payload must stay inside projectRoot")
|
|
@@ -1885,7 +2102,20 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1885
2102
|
queryExecuted=True,
|
|
1886
2103
|
rowCount=row_count,
|
|
1887
2104
|
queryPayloadDigest=sha256(payload_path),
|
|
2105
|
+
projectRoot=str(project_root),
|
|
1888
2106
|
)
|
|
2107
|
+
workspace_verifications = state.setdefault("workspaceVerifications", [])
|
|
2108
|
+
workspace_verifications[:] = [
|
|
2109
|
+
item for item in workspace_verifications
|
|
2110
|
+
if not (item.get("projectRoot") == str(project_root) and item.get("queryModel") == query_model)
|
|
2111
|
+
]
|
|
2112
|
+
workspace_verifications.append({
|
|
2113
|
+
"projectRoot": str(project_root),
|
|
2114
|
+
"queryModel": query_model,
|
|
2115
|
+
"queryPayloadDigest": sha256(payload_path),
|
|
2116
|
+
"rowCount": row_count,
|
|
2117
|
+
"verifiedAt": now_utc(),
|
|
2118
|
+
})
|
|
1889
2119
|
state.setdefault("artifacts", {})["semanticVerifyEvidence"] = str(evidence_dir)
|
|
1890
2120
|
path = write_onboarding_state(data_root, state)
|
|
1891
2121
|
return {
|
|
@@ -1944,12 +2174,63 @@ def onboarding_status_command(args: argparse.Namespace) -> dict:
|
|
|
1944
2174
|
"passwordEnv": password_env,
|
|
1945
2175
|
"passwordEnvPresent": bool(password_env and os.environ.get(password_env)),
|
|
1946
2176
|
"steps": state["steps"],
|
|
2177
|
+
"projectRoot": state.get("projectRoot"),
|
|
2178
|
+
"workspaceBindings": [str(path) for path in bound_project_roots(state)],
|
|
2179
|
+
"workspaceVerifications": state.get("workspaceVerifications", []),
|
|
1947
2180
|
"artifacts": state.get("artifacts", {}),
|
|
1948
2181
|
"next": next_onboarding_action(state),
|
|
1949
2182
|
"productionReady": False,
|
|
1950
2183
|
}
|
|
1951
2184
|
|
|
1952
2185
|
|
|
2186
|
+
def onboarding_list_command(args: argparse.Namespace) -> dict:
|
|
2187
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
2188
|
+
install_state = read_install_state(install_root)
|
|
2189
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
2190
|
+
profiles_dir = data_root / "onboarding" / "profiles"
|
|
2191
|
+
profiles: list[dict] = []
|
|
2192
|
+
ordered_steps = [
|
|
2193
|
+
"planned", "datasourceConfigured", "datasourceVerified", "schemaDiscovered",
|
|
2194
|
+
"semanticDrafted", "semanticValidated", "semanticPublished", "semanticVerified",
|
|
2195
|
+
]
|
|
2196
|
+
for path in sorted(profiles_dir.glob("*.json")):
|
|
2197
|
+
if not PROFILE_PATTERN.fullmatch(path.stem):
|
|
2198
|
+
continue
|
|
2199
|
+
try:
|
|
2200
|
+
state = read_onboarding_state(data_root, path.stem)
|
|
2201
|
+
steps = state.get("steps", {})
|
|
2202
|
+
completed = sum(steps.get(name, {}).get("status") == "completed" for name in ordered_steps)
|
|
2203
|
+
profiles.append({
|
|
2204
|
+
"profile": state["profile"],
|
|
2205
|
+
"projectRoot": state.get("projectRoot"),
|
|
2206
|
+
"workspaceBindings": [str(item) for item in bound_project_roots(state)],
|
|
2207
|
+
"updatedAt": state.get("updatedAt"),
|
|
2208
|
+
"completedSteps": completed,
|
|
2209
|
+
"totalSteps": len(ordered_steps),
|
|
2210
|
+
"steps": {name: steps.get(name, {"status": "pending"}) for name in ordered_steps},
|
|
2211
|
+
"next": next_onboarding_action(state),
|
|
2212
|
+
})
|
|
2213
|
+
except OnboardingError as exc:
|
|
2214
|
+
profiles.append({
|
|
2215
|
+
"profile": path.stem,
|
|
2216
|
+
"projectRoot": None,
|
|
2217
|
+
"updatedAt": None,
|
|
2218
|
+
"completedSteps": 0,
|
|
2219
|
+
"totalSteps": len(ordered_steps),
|
|
2220
|
+
"steps": {},
|
|
2221
|
+
"next": {"status": "invalid"},
|
|
2222
|
+
"error": str(exc),
|
|
2223
|
+
})
|
|
2224
|
+
profiles.sort(key=lambda item: item.get("updatedAt") or "", reverse=True)
|
|
2225
|
+
return {
|
|
2226
|
+
"success": True,
|
|
2227
|
+
"schemaVersion": "foggy-deepseek-onboarding-list/v1",
|
|
2228
|
+
"profiles": profiles,
|
|
2229
|
+
"profileCount": len(profiles),
|
|
2230
|
+
"productionReady": False,
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
|
|
1953
2234
|
def onboarding_resume_command(args: argparse.Namespace) -> dict:
|
|
1954
2235
|
result = onboarding_status_command(args)
|
|
1955
2236
|
result["schemaVersion"] = "foggy-deepseek-onboarding-resume/v1"
|
|
@@ -1986,6 +2267,37 @@ def step_completed(state: dict, name: str) -> bool:
|
|
|
1986
2267
|
return state.get("steps", {}).get(name, {}).get("status") == "completed"
|
|
1987
2268
|
|
|
1988
2269
|
|
|
2270
|
+
def bound_project_roots(state: dict) -> list[Path]:
|
|
2271
|
+
values = [state.get("projectRoot"), *state.get("workspaceBindings", [])]
|
|
2272
|
+
result: list[Path] = []
|
|
2273
|
+
for value in values:
|
|
2274
|
+
if not isinstance(value, str) or not value:
|
|
2275
|
+
continue
|
|
2276
|
+
path = normalized(value)
|
|
2277
|
+
if path not in result:
|
|
2278
|
+
result.append(path)
|
|
2279
|
+
return result
|
|
2280
|
+
|
|
2281
|
+
|
|
2282
|
+
def project_root_is_bound(state: dict, project_root: Path) -> bool:
|
|
2283
|
+
return normalized(project_root) in bound_project_roots(state)
|
|
2284
|
+
|
|
2285
|
+
|
|
2286
|
+
def bind_completed_workspace(state: dict, data_root: Path, project_root: Path) -> bool:
|
|
2287
|
+
project_root = normalized(project_root)
|
|
2288
|
+
if project_root_is_bound(state, project_root):
|
|
2289
|
+
return False
|
|
2290
|
+
required = ("datasourceConfigured", "datasourceVerified", "schemaDiscovered", "semanticPublished")
|
|
2291
|
+
if not all(step_completed(state, name) for name in required):
|
|
2292
|
+
raise OnboardingError(
|
|
2293
|
+
"Existing onboarding profile belongs to a different projectRoot and is not complete enough for safe reuse; "
|
|
2294
|
+
"resume from its original DSH workspace or choose a new profile name"
|
|
2295
|
+
)
|
|
2296
|
+
state.setdefault("workspaceBindings", []).append(str(project_root))
|
|
2297
|
+
write_onboarding_state(data_root, state)
|
|
2298
|
+
return True
|
|
2299
|
+
|
|
2300
|
+
|
|
1989
2301
|
def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
1990
2302
|
_install_root, install_state, data_root, _runtime_state = onboarding_context(args, require_runtime=True)
|
|
1991
2303
|
project_root = normalized(args.project_root or Path.cwd())
|
|
@@ -2005,16 +2317,14 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
2005
2317
|
if existing:
|
|
2006
2318
|
if existing.get("connection") != requested_connection:
|
|
2007
2319
|
raise OnboardingError("Existing onboarding profile does not match the requested connection plan")
|
|
2008
|
-
|
|
2009
|
-
raise OnboardingError(
|
|
2010
|
-
"Existing onboarding profile belongs to a different projectRoot; resume from that DSH workspace "
|
|
2011
|
-
"or choose a new profile name"
|
|
2012
|
-
)
|
|
2320
|
+
adopted = bind_completed_workspace(existing, data_root, project_root)
|
|
2013
2321
|
plan_result = {
|
|
2014
2322
|
"success": True,
|
|
2015
2323
|
"schemaVersion": "foggy-deepseek-onboarding-plan-result/v1",
|
|
2016
2324
|
"profile": profile,
|
|
2017
2325
|
"resumed": True,
|
|
2326
|
+
"workspaceAdopted": adopted,
|
|
2327
|
+
"projectRoot": str(project_root),
|
|
2018
2328
|
"statePath": str(onboarding_state_path(data_root, profile)),
|
|
2019
2329
|
"next": next_onboarding_action(existing),
|
|
2020
2330
|
"productionReady": False,
|
|
@@ -2119,8 +2429,8 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
2119
2429
|
}
|
|
2120
2430
|
|
|
2121
2431
|
|
|
2122
|
-
def semantic_plan_snapshot(state: dict, plan: dict) -> tuple[Path, dict, bool]:
|
|
2123
|
-
project_root = normalized(state["projectRoot"])
|
|
2432
|
+
def semantic_plan_snapshot(state: dict, plan: dict, project_root: Path | None = None) -> tuple[Path, dict, bool]:
|
|
2433
|
+
project_root = normalized(project_root or state["projectRoot"])
|
|
2124
2434
|
draft_dir = normalized(project_root / plan["draftDir"])
|
|
2125
2435
|
if not is_child(draft_dir, project_root) or draft_dir == project_root:
|
|
2126
2436
|
raise OnboardingError("draftDir must stay inside projectRoot and cannot equal it")
|
|
@@ -2142,6 +2452,22 @@ def published_digest_matches(state: dict, digest: str) -> bool:
|
|
|
2142
2452
|
return published.get("status") == "completed" and published.get("digest") == digest
|
|
2143
2453
|
|
|
2144
2454
|
|
|
2455
|
+
def workspace_query_verification(state: dict, project_root: Path, query_model: str, digest: str) -> dict | None:
|
|
2456
|
+
requested_root = str(normalized(project_root))
|
|
2457
|
+
candidates = list(state.get("workspaceVerifications", []))
|
|
2458
|
+
legacy = state.get("steps", {}).get("semanticVerified", {})
|
|
2459
|
+
if legacy.get("status") == "completed" and legacy.get("projectRoot"):
|
|
2460
|
+
candidates.append(legacy)
|
|
2461
|
+
for item in candidates:
|
|
2462
|
+
if (
|
|
2463
|
+
item.get("projectRoot") == requested_root
|
|
2464
|
+
and item.get("queryModel") == query_model
|
|
2465
|
+
and item.get("queryPayloadDigest") == digest
|
|
2466
|
+
):
|
|
2467
|
+
return item
|
|
2468
|
+
return None
|
|
2469
|
+
|
|
2470
|
+
|
|
2145
2471
|
def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
2146
2472
|
approved_plan = validate_semantic_plan(read_json_object(normalized(args.semantic_plan), "Semantic plan"))
|
|
2147
2473
|
if not approved_plan.get("profile"):
|
|
@@ -2152,7 +2478,11 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2152
2478
|
profile_args = argparse.Namespace(**vars(args))
|
|
2153
2479
|
profile_args.profile = profile
|
|
2154
2480
|
state, _install_state, _data_root, _runtime_state = require_profile(profile_args, require_runtime=True)
|
|
2155
|
-
project_root = normalized(state["projectRoot"])
|
|
2481
|
+
project_root = normalized(getattr(args, "project_root", None) or state["projectRoot"])
|
|
2482
|
+
if not project_root_is_bound(state, project_root):
|
|
2483
|
+
raise OnboardingError(
|
|
2484
|
+
"Current projectRoot is not bound to this completed profile; run onboard-datasource-run from this workspace first"
|
|
2485
|
+
)
|
|
2156
2486
|
payload_path = normalized(args.query_payload)
|
|
2157
2487
|
if not is_child(payload_path, project_root):
|
|
2158
2488
|
raise OnboardingError(
|
|
@@ -2171,8 +2501,13 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2171
2501
|
evidence_dir = composite_evidence_dir(project_root, profile, str(project_root / contract_evidence) if contract_evidence else args.evidence_dir)
|
|
2172
2502
|
files: list[str] = []
|
|
2173
2503
|
|
|
2174
|
-
_draft_dir, manifest, draft_matches = semantic_plan_snapshot(state, approved_plan)
|
|
2504
|
+
_draft_dir, manifest, draft_matches = semantic_plan_snapshot(state, approved_plan, project_root)
|
|
2175
2505
|
published_matches = published_digest_matches(state, manifest["digest"])
|
|
2506
|
+
if normalized(state["projectRoot"]) != project_root and not published_matches:
|
|
2507
|
+
raise OnboardingError(
|
|
2508
|
+
"A secondary workspace may reuse an identical published semantic layer but cannot replace it; "
|
|
2509
|
+
"publish changes from the original projectRoot or choose a new profile"
|
|
2510
|
+
)
|
|
2176
2511
|
if draft_matches or published_matches:
|
|
2177
2512
|
drafted = resumed_phase(profile, "semanticDrafted", digest=manifest["digest"])
|
|
2178
2513
|
else:
|
|
@@ -2265,13 +2600,9 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2265
2600
|
))
|
|
2266
2601
|
save_composite_result(evidence_dir, "11-semantic-publish-apply.json", published, files)
|
|
2267
2602
|
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2268
|
-
verified_step = state.get("steps", {}).get("semanticVerified", {})
|
|
2269
2603
|
payload_digest = sha256(payload_path)
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
and verified_step.get("queryModel") == declared_query_model
|
|
2273
|
-
and verified_step.get("queryPayloadDigest") == payload_digest
|
|
2274
|
-
):
|
|
2604
|
+
verified_step = workspace_query_verification(state, project_root, declared_query_model, payload_digest)
|
|
2605
|
+
if verified_step:
|
|
2275
2606
|
resumed = resumed_phase(
|
|
2276
2607
|
profile,
|
|
2277
2608
|
"semanticVerified",
|
|
@@ -2307,6 +2638,7 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2307
2638
|
profile=profile,
|
|
2308
2639
|
query_model=declared_query_model,
|
|
2309
2640
|
query_payload=args.query_payload,
|
|
2641
|
+
project_root=str(project_root),
|
|
2310
2642
|
execute=False,
|
|
2311
2643
|
))
|
|
2312
2644
|
save_composite_result(evidence_dir, "12-query-validate.json", query_validated, files)
|
|
@@ -2330,6 +2662,7 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2330
2662
|
profile=profile,
|
|
2331
2663
|
query_model=declared_query_model,
|
|
2332
2664
|
query_payload=args.query_payload,
|
|
2665
|
+
project_root=str(project_root),
|
|
2333
2666
|
execute=True,
|
|
2334
2667
|
))
|
|
2335
2668
|
save_composite_result(evidence_dir, "13-query-execute.json", executed, files)
|
|
@@ -2464,6 +2797,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2464
2797
|
install.add_argument("--project-root")
|
|
2465
2798
|
install.add_argument("--asset-cache-dir", action="append", default=[])
|
|
2466
2799
|
install.add_argument("--replace-skill", action="store_true")
|
|
2800
|
+
install.add_argument("--repair-component", choices=("cli", "launcher", "analysis-skill"))
|
|
2467
2801
|
install.add_argument("--skip-cli-install", action="store_true")
|
|
2468
2802
|
install.add_argument("--cli-command")
|
|
2469
2803
|
install.add_argument("--progress-file")
|
|
@@ -2516,6 +2850,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2516
2850
|
resume.add_argument("--profile")
|
|
2517
2851
|
resume.set_defaults(handler=onboarding_resume_command)
|
|
2518
2852
|
|
|
2853
|
+
onboarding_list = sub.add_parser("onboard-list")
|
|
2854
|
+
onboarding_list.add_argument("--install-root")
|
|
2855
|
+
onboarding_list.add_argument("--data-root")
|
|
2856
|
+
onboarding_list.set_defaults(handler=onboarding_list_command)
|
|
2857
|
+
|
|
2858
|
+
migration_status = sub.add_parser("profile-migration-status")
|
|
2859
|
+
migration_status.add_argument("--install-root")
|
|
2860
|
+
migration_status.add_argument("--data-root")
|
|
2861
|
+
migration_status.set_defaults(handler=profile_migration_status_command)
|
|
2862
|
+
|
|
2863
|
+
migrate = sub.add_parser("profile-migrate")
|
|
2864
|
+
migrate.add_argument("--install-root")
|
|
2865
|
+
migrate.add_argument("--data-root")
|
|
2866
|
+
migrate.add_argument("--approve", action="store_true")
|
|
2867
|
+
migrate.set_defaults(handler=profile_migrate_command)
|
|
2868
|
+
|
|
2519
2869
|
datasource_run = sub.add_parser("onboard-datasource-run")
|
|
2520
2870
|
datasource_run.add_argument("--install-root")
|
|
2521
2871
|
datasource_run.add_argument("--data-root")
|
|
@@ -2536,6 +2886,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2536
2886
|
semantic_run = sub.add_parser("onboard-semantic-run")
|
|
2537
2887
|
semantic_run.add_argument("--install-root")
|
|
2538
2888
|
semantic_run.add_argument("--data-root")
|
|
2889
|
+
semantic_run.add_argument("--project-root")
|
|
2539
2890
|
semantic_run.add_argument("--profile")
|
|
2540
2891
|
semantic_run.add_argument("--semantic-plan", required=True)
|
|
2541
2892
|
semantic_run.add_argument("--query-payload", required=True)
|
|
@@ -2602,6 +2953,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2602
2953
|
semantic_verify = sub.add_parser("semantic-verify")
|
|
2603
2954
|
semantic_verify.add_argument("--install-root")
|
|
2604
2955
|
semantic_verify.add_argument("--data-root")
|
|
2956
|
+
semantic_verify.add_argument("--project-root")
|
|
2605
2957
|
semantic_verify.add_argument("--profile")
|
|
2606
2958
|
semantic_verify.add_argument("--query-model")
|
|
2607
2959
|
semantic_verify.add_argument("--query-payload", required=True)
|