@foggy-projects/deepseek-harness-plugin 0.4.0-beta.5 → 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 +26 -3
- package/lib/client.js +111 -3
- package/lib/index.js +69 -7
- package/package.json +2 -2
- package/skills/foggy-deepseek-onboarding/SKILL.md +33 -4
- 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 +40 -4
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +719 -140
|
@@ -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)
|
|
@@ -448,6 +458,180 @@ def onboarding_state_path(data_root: Path, profile: str) -> Path:
|
|
|
448
458
|
return data_root / "onboarding" / "profiles" / f"{safe_profile(profile)}.json"
|
|
449
459
|
|
|
450
460
|
|
|
461
|
+
def configure_profile_store(data_root: Path, *, create: bool = True) -> Path:
|
|
462
|
+
"""Keep opaque CLI profiles in the persistent, private Foggy data root by default."""
|
|
463
|
+
configured = os.environ.get("FOGGY_RUNTIME_PROFILE_STORE")
|
|
464
|
+
destination = normalized(configured) if configured else data_root / "cli-profiles"
|
|
465
|
+
if not configured:
|
|
466
|
+
os.environ["FOGGY_RUNTIME_PROFILE_STORE"] = str(destination)
|
|
467
|
+
if create:
|
|
468
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
469
|
+
if os.name != "nt":
|
|
470
|
+
destination.chmod(0o700)
|
|
471
|
+
return destination
|
|
472
|
+
|
|
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
|
+
|
|
621
|
+
def resolve_profile_name(data_root: Path, requested: str | None) -> str:
|
|
622
|
+
if requested:
|
|
623
|
+
return safe_profile(requested)
|
|
624
|
+
profiles_dir = data_root / "onboarding" / "profiles"
|
|
625
|
+
candidates = sorted(path.stem for path in profiles_dir.glob("*.json") if PROFILE_PATTERN.fullmatch(path.stem))
|
|
626
|
+
if len(candidates) == 1:
|
|
627
|
+
return candidates[0]
|
|
628
|
+
if not candidates:
|
|
629
|
+
return "default"
|
|
630
|
+
raise OnboardingError(
|
|
631
|
+
"Multiple onboarding profiles exist; pass --profile explicitly: " + ", ".join(candidates)
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
|
|
451
635
|
def read_onboarding_state(data_root: Path, profile: str, required: bool = True) -> dict | None:
|
|
452
636
|
path = onboarding_state_path(data_root, profile)
|
|
453
637
|
if not path.is_file():
|
|
@@ -798,21 +982,26 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
798
982
|
data_root = normalized(args.data_root or default_data_root())
|
|
799
983
|
cache_dirs = [normalized(item) for item in args.asset_cache_dir]
|
|
800
984
|
components = versions["components"]
|
|
985
|
+
repair_component = getattr(args, "repair_component", None)
|
|
801
986
|
assert_managed_root(install_root, "Install root")
|
|
802
987
|
assert_managed_root(data_root, "Data root")
|
|
803
988
|
if install_root == data_root:
|
|
804
989
|
raise OnboardingError("Install root and data root must be different")
|
|
990
|
+
profile_store = normalized(os.environ.get("FOGGY_RUNTIME_PROFILE_STORE") or data_root / "cli-profiles")
|
|
805
991
|
plan = {
|
|
806
992
|
"schemaVersion": "foggy-deepseek-onboarding-plan/v1",
|
|
807
993
|
"installRoot": str(install_root),
|
|
808
994
|
"dataRoot": str(data_root),
|
|
995
|
+
"profileStore": str(profile_store),
|
|
809
996
|
"workspaceMode": "dsh-session-cwd",
|
|
810
997
|
"versions": {name: value.get("version") for name, value in components.items()},
|
|
998
|
+
"repairComponent": repair_component,
|
|
811
999
|
"operations": ["install isolated CLI", "verify Launcher assets", "install global analysis Skill", "write install state"],
|
|
812
1000
|
"productionReady": False,
|
|
813
1001
|
}
|
|
814
1002
|
if args.dry_run:
|
|
815
1003
|
return {"success": True, "dryRun": True, "plan": plan}
|
|
1004
|
+
profile_store = configure_profile_store(data_root)
|
|
816
1005
|
progress = ProgressReporter(
|
|
817
1006
|
getattr(args, "progress_file", None),
|
|
818
1007
|
getattr(args, "operation_id", None),
|
|
@@ -840,6 +1029,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
840
1029
|
progress=progress, progress_phase="cli", progress_step=1,
|
|
841
1030
|
progress_index=index, progress_total=len(cli_assets),
|
|
842
1031
|
progress_message="Downloading and verifying CLI",
|
|
1032
|
+
replace_corrupt=repair_component == "cli",
|
|
843
1033
|
))
|
|
844
1034
|
progress.update(
|
|
845
1035
|
"cli", 1, "Downloading and verifying CLI",
|
|
@@ -895,6 +1085,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
895
1085
|
progress=progress, progress_phase="launcher", progress_step=2,
|
|
896
1086
|
progress_index=index, progress_total=len(launcher_assets),
|
|
897
1087
|
progress_message="Downloading and verifying Launcher",
|
|
1088
|
+
replace_corrupt=repair_component == "launcher",
|
|
898
1089
|
))
|
|
899
1090
|
progress.update(
|
|
900
1091
|
"launcher", 2, "Downloading and verifying Launcher",
|
|
@@ -917,6 +1108,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
917
1108
|
progress=progress, progress_phase="analysis-skill", progress_step=3,
|
|
918
1109
|
progress_index=index, progress_total=len(analysis_assets),
|
|
919
1110
|
progress_message="Downloading and verifying analysis Skill",
|
|
1111
|
+
replace_corrupt=repair_component == "analysis-skill",
|
|
920
1112
|
))
|
|
921
1113
|
progress.update(
|
|
922
1114
|
"analysis-skill", 3, "Downloading and verifying analysis Skill",
|
|
@@ -927,7 +1119,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
927
1119
|
progress.update("analysis-skill", 3, "Installing analysis Skill", fraction=0.9, current_file=zip_asset["file"])
|
|
928
1120
|
analysis_skill = install_analysis_skill(
|
|
929
1121
|
downloads / "skill" / zip_asset["file"], install_root, components["analysisSkill"]["version"],
|
|
930
|
-
zip_asset["sha256"], versions["packageVersion"], args.replace_skill,
|
|
1122
|
+
zip_asset["sha256"], versions["packageVersion"], args.replace_skill or repair_component == "analysis-skill",
|
|
931
1123
|
)
|
|
932
1124
|
progress.update("analysis-skill", 3, "Analysis Skill ready", fraction=1.0)
|
|
933
1125
|
progress.update("workspace-skills", 4, "Registering native DSH Skills", fraction=0.1)
|
|
@@ -945,6 +1137,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
945
1137
|
"packageVersion": versions["packageVersion"],
|
|
946
1138
|
"installRoot": str(install_root),
|
|
947
1139
|
"dataRoot": str(data_root),
|
|
1140
|
+
"profileStore": str(profile_store),
|
|
948
1141
|
"workspaceMode": "dsh-session-cwd",
|
|
949
1142
|
"cli": {"version": cli_component["version"], "command": str(cli_command), "mode": cli_mode},
|
|
950
1143
|
"launcher": {"version": components["launcher"]["version"], "path": str(launcher_dir)},
|
|
@@ -1057,8 +1250,61 @@ def runtime_start_command(args: argparse.Namespace) -> dict:
|
|
|
1057
1250
|
existing = data_root / "runtime-state.json"
|
|
1058
1251
|
if existing.is_file():
|
|
1059
1252
|
prior = json.loads(existing.read_text(encoding="utf-8"))
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
+
}
|
|
1062
1308
|
existing.unlink()
|
|
1063
1309
|
port = args.port or int(versions["defaults"]["port"])
|
|
1064
1310
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
@@ -1163,6 +1409,7 @@ def onboarding_context(args: argparse.Namespace, require_runtime: bool = False)
|
|
|
1163
1409
|
install_state = read_install_state(install_root)
|
|
1164
1410
|
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
1165
1411
|
assert_managed_root(data_root, "Data root")
|
|
1412
|
+
configure_profile_store(data_root, create=False)
|
|
1166
1413
|
runtime_state_path = data_root / "runtime-state.json"
|
|
1167
1414
|
runtime_state = read_json_object(runtime_state_path, "Runtime state") if runtime_state_path.is_file() else None
|
|
1168
1415
|
if runtime_state and runtime_state.get("schemaVersion") != RUNTIME_STATE_SCHEMA:
|
|
@@ -1222,6 +1469,28 @@ def require_opaque_profile_cli(install_state: dict) -> None:
|
|
|
1222
1469
|
)
|
|
1223
1470
|
|
|
1224
1471
|
|
|
1472
|
+
def datasource_entries(payload: dict) -> list[dict]:
|
|
1473
|
+
for item in nested_data_objects(payload):
|
|
1474
|
+
entries = item.get("datasources")
|
|
1475
|
+
if isinstance(entries, list):
|
|
1476
|
+
return [entry for entry in entries if isinstance(entry, dict)]
|
|
1477
|
+
return []
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
def matching_datasource(payload: dict, connection: dict) -> dict | None:
|
|
1481
|
+
expected_name = connection["name"]
|
|
1482
|
+
expected_type = connection["type"].lower()
|
|
1483
|
+
if expected_type == "postgresql":
|
|
1484
|
+
expected_type = "postgres"
|
|
1485
|
+
for entry in datasource_entries(payload):
|
|
1486
|
+
entry_type = str(entry.get("type", "")).lower()
|
|
1487
|
+
if entry_type == "postgresql":
|
|
1488
|
+
entry_type = "postgres"
|
|
1489
|
+
if entry.get("name") == expected_name and entry_type == expected_type:
|
|
1490
|
+
return entry
|
|
1491
|
+
return None
|
|
1492
|
+
|
|
1493
|
+
|
|
1225
1494
|
def onboarding_plan_command(args: argparse.Namespace) -> dict:
|
|
1226
1495
|
install_root, install_state, data_root, runtime_state = onboarding_context(args, require_runtime=False)
|
|
1227
1496
|
profile = safe_profile(args.profile)
|
|
@@ -1283,7 +1552,9 @@ def onboarding_plan_command(args: argparse.Namespace) -> dict:
|
|
|
1283
1552
|
|
|
1284
1553
|
def require_profile(args: argparse.Namespace, require_runtime: bool = False) -> tuple[dict, dict, Path, dict | None]:
|
|
1285
1554
|
_install_root, install_state, data_root, runtime_state = onboarding_context(args, require_runtime=require_runtime)
|
|
1286
|
-
|
|
1555
|
+
profile = resolve_profile_name(data_root, getattr(args, "profile", None))
|
|
1556
|
+
args.profile = profile
|
|
1557
|
+
state = read_onboarding_state(data_root, profile)
|
|
1287
1558
|
if normalized(state["installRoot"]) != normalized(install_state["installRoot"]):
|
|
1288
1559
|
raise OnboardingError("Onboarding profile belongs to a different install root")
|
|
1289
1560
|
return state, install_state, data_root, runtime_state
|
|
@@ -1329,10 +1600,31 @@ def datasource_configure_command(args: argparse.Namespace) -> dict:
|
|
|
1329
1600
|
label = "datasources add"
|
|
1330
1601
|
if args.replace:
|
|
1331
1602
|
command.append("--replace")
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1603
|
+
already_present = False
|
|
1604
|
+
try:
|
|
1605
|
+
result = redact_connection_material(
|
|
1606
|
+
cli_json(install_state, runtime_state, connection["namespace"], command, label)
|
|
1607
|
+
)
|
|
1608
|
+
except OnboardingError as exc:
|
|
1609
|
+
if "DATASOURCE_ALREADY_EXISTS" not in str(exc):
|
|
1610
|
+
raise
|
|
1611
|
+
listed = cli_json(
|
|
1612
|
+
install_state, runtime_state, connection["namespace"], ["datasources", "list"], "datasources list"
|
|
1613
|
+
)
|
|
1614
|
+
existing = matching_datasource(listed, connection)
|
|
1615
|
+
if existing is None:
|
|
1616
|
+
raise OnboardingError(
|
|
1617
|
+
f"Datasource {connection['name']} already exists but its public type does not match the approved plan; "
|
|
1618
|
+
"do not replace it without explicit approval"
|
|
1619
|
+
) from exc
|
|
1620
|
+
already_present = True
|
|
1621
|
+
result = {
|
|
1622
|
+
"success": True,
|
|
1623
|
+
"idempotent": True,
|
|
1624
|
+
"status": "already-present",
|
|
1625
|
+
"dataSource": redact_connection_material(existing),
|
|
1626
|
+
}
|
|
1627
|
+
mark_step(state, "datasourceConfigured", "completed", replace=args.replace, alreadyPresent=already_present)
|
|
1336
1628
|
path = write_onboarding_state(data_root, state)
|
|
1337
1629
|
return {
|
|
1338
1630
|
"success": True,
|
|
@@ -1340,6 +1632,7 @@ def datasource_configure_command(args: argparse.Namespace) -> dict:
|
|
|
1340
1632
|
"profile": state["profile"],
|
|
1341
1633
|
"statePath": str(path),
|
|
1342
1634
|
"dataSource": connection["name"],
|
|
1635
|
+
"alreadyPresent": already_present,
|
|
1343
1636
|
"runtime": result,
|
|
1344
1637
|
"next": "run datasource-verify; add --bind to approve namespace binding",
|
|
1345
1638
|
"productionReady": False,
|
|
@@ -1756,7 +2049,9 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1756
2049
|
raise OnboardingError("--query-model must be declared in the registered semantic plan")
|
|
1757
2050
|
if not args.query_payload:
|
|
1758
2051
|
raise OnboardingError("--query-payload is required for semantic verification")
|
|
1759
|
-
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")
|
|
1760
2055
|
payload_path = normalized(args.query_payload)
|
|
1761
2056
|
if not is_child(payload_path, project_root):
|
|
1762
2057
|
raise OnboardingError("Query payload must stay inside projectRoot")
|
|
@@ -1798,7 +2093,29 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1798
2093
|
)
|
|
1799
2094
|
atomic_json(evidence_dir / "query-execute.json", executed)
|
|
1800
2095
|
row_count = response_row_count(executed)
|
|
1801
|
-
mark_step(
|
|
2096
|
+
mark_step(
|
|
2097
|
+
state,
|
|
2098
|
+
"semanticVerified",
|
|
2099
|
+
"completed",
|
|
2100
|
+
queryModel=query_model,
|
|
2101
|
+
queryValidated=True,
|
|
2102
|
+
queryExecuted=True,
|
|
2103
|
+
rowCount=row_count,
|
|
2104
|
+
queryPayloadDigest=sha256(payload_path),
|
|
2105
|
+
projectRoot=str(project_root),
|
|
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
|
+
})
|
|
1802
2119
|
state.setdefault("artifacts", {})["semanticVerifyEvidence"] = str(evidence_dir)
|
|
1803
2120
|
path = write_onboarding_state(data_root, state)
|
|
1804
2121
|
return {
|
|
@@ -1818,14 +2135,15 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1818
2135
|
|
|
1819
2136
|
|
|
1820
2137
|
def next_onboarding_action(state: dict) -> dict:
|
|
2138
|
+
profile_flag = f" --profile {state['profile']}"
|
|
1821
2139
|
ordered = [
|
|
1822
|
-
("datasourceConfigured", "datasource-configure --apply"),
|
|
1823
|
-
("datasourceVerified", "datasource-verify --bind"),
|
|
1824
|
-
("schemaDiscovered", "schema-discover"),
|
|
1825
|
-
("semanticDrafted", "semantic-draft --semantic-plan <json>"),
|
|
1826
|
-
("semanticValidated", "semantic-validate --apply"),
|
|
1827
|
-
("semanticPublished", "semantic-publish --apply"),
|
|
1828
|
-
("semanticVerified", "semantic-verify --query-payload <json> --execute"),
|
|
2140
|
+
("datasourceConfigured", f"datasource-configure{profile_flag} --apply"),
|
|
2141
|
+
("datasourceVerified", f"datasource-verify{profile_flag} --bind"),
|
|
2142
|
+
("schemaDiscovered", f"schema-discover{profile_flag}"),
|
|
2143
|
+
("semanticDrafted", f"semantic-draft{profile_flag} --semantic-plan <json>"),
|
|
2144
|
+
("semanticValidated", f"semantic-validate{profile_flag} --apply"),
|
|
2145
|
+
("semanticPublished", f"semantic-publish{profile_flag} --apply"),
|
|
2146
|
+
("semanticVerified", f"semantic-verify{profile_flag} --query-payload <json> --execute"),
|
|
1829
2147
|
]
|
|
1830
2148
|
for name, command in ordered:
|
|
1831
2149
|
current = state.get("steps", {}).get(name, {})
|
|
@@ -1838,7 +2156,7 @@ def next_onboarding_action(state: dict) -> dict:
|
|
|
1838
2156
|
"instruction": "Inspect semantic publish evidence and repair refresh before any republish attempt.",
|
|
1839
2157
|
}
|
|
1840
2158
|
if name == "semanticValidated" and current.get("status") == "failed":
|
|
1841
|
-
command = "repair TM/QM, then semantic-draft --semantic-plan <json>"
|
|
2159
|
+
command = f"repair TM/QM, then semantic-draft{profile_flag} --semantic-plan <json>"
|
|
1842
2160
|
return {"step": name, "command": command, "status": current.get("status", "pending")}
|
|
1843
2161
|
return {"step": None, "command": None, "status": "completed"}
|
|
1844
2162
|
|
|
@@ -1856,12 +2174,63 @@ def onboarding_status_command(args: argparse.Namespace) -> dict:
|
|
|
1856
2174
|
"passwordEnv": password_env,
|
|
1857
2175
|
"passwordEnvPresent": bool(password_env and os.environ.get(password_env)),
|
|
1858
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", []),
|
|
1859
2180
|
"artifacts": state.get("artifacts", {}),
|
|
1860
2181
|
"next": next_onboarding_action(state),
|
|
1861
2182
|
"productionReady": False,
|
|
1862
2183
|
}
|
|
1863
2184
|
|
|
1864
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
|
+
|
|
1865
2234
|
def onboarding_resume_command(args: argparse.Namespace) -> dict:
|
|
1866
2235
|
result = onboarding_status_command(args)
|
|
1867
2236
|
result["schemaVersion"] = "foggy-deepseek-onboarding-resume/v1"
|
|
@@ -1883,6 +2252,52 @@ def save_composite_result(evidence_dir: Path, name: str, payload: dict, files: l
|
|
|
1883
2252
|
files.append(str(path))
|
|
1884
2253
|
|
|
1885
2254
|
|
|
2255
|
+
def resumed_phase(profile: str, phase: str, **values: object) -> dict:
|
|
2256
|
+
return {
|
|
2257
|
+
"success": True,
|
|
2258
|
+
"schemaVersion": "foggy-deepseek-onboarding-resumed/v1",
|
|
2259
|
+
"profile": profile,
|
|
2260
|
+
"phase": phase,
|
|
2261
|
+
"resumed": True,
|
|
2262
|
+
**values,
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
|
|
2266
|
+
def step_completed(state: dict, name: str) -> bool:
|
|
2267
|
+
return state.get("steps", {}).get(name, {}).get("status") == "completed"
|
|
2268
|
+
|
|
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
|
+
|
|
1886
2301
|
def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
1887
2302
|
_install_root, install_state, data_root, _runtime_state = onboarding_context(args, require_runtime=True)
|
|
1888
2303
|
project_root = normalized(args.project_root or Path.cwd())
|
|
@@ -1902,11 +2317,14 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
1902
2317
|
if existing:
|
|
1903
2318
|
if existing.get("connection") != requested_connection:
|
|
1904
2319
|
raise OnboardingError("Existing onboarding profile does not match the requested connection plan")
|
|
2320
|
+
adopted = bind_completed_workspace(existing, data_root, project_root)
|
|
1905
2321
|
plan_result = {
|
|
1906
2322
|
"success": True,
|
|
1907
2323
|
"schemaVersion": "foggy-deepseek-onboarding-plan-result/v1",
|
|
1908
2324
|
"profile": profile,
|
|
1909
2325
|
"resumed": True,
|
|
2326
|
+
"workspaceAdopted": adopted,
|
|
2327
|
+
"projectRoot": str(project_root),
|
|
1910
2328
|
"statePath": str(onboarding_state_path(data_root, profile)),
|
|
1911
2329
|
"next": next_onboarding_action(existing),
|
|
1912
2330
|
"productionReady": False,
|
|
@@ -1921,59 +2339,81 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
1921
2339
|
replace_plan=False,
|
|
1922
2340
|
))
|
|
1923
2341
|
save_composite_result(evidence_dir, "01-plan.json", plan_result, files)
|
|
2342
|
+
state = read_onboarding_state(data_root, profile)
|
|
1924
2343
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
return {
|
|
1951
|
-
"success": True,
|
|
1952
|
-
"schemaVersion": "foggy-deepseek-datasource-run/v1",
|
|
1953
|
-
"profile": profile,
|
|
1954
|
-
"phaseStatus": "awaiting-bind-approval",
|
|
1955
|
-
"evidenceDir": str(evidence_dir),
|
|
1956
|
-
"evidenceFiles": files,
|
|
1957
|
-
"next": "rerun with --approve-configure --approve-bind after namespace binding is approved",
|
|
1958
|
-
"productionReady": False,
|
|
1959
|
-
}
|
|
2344
|
+
if step_completed(state, "datasourceConfigured"):
|
|
2345
|
+
configured = resumed_phase(profile, "datasourceConfigured")
|
|
2346
|
+
save_composite_result(evidence_dir, "02-datasource-dry.json", configured, files)
|
|
2347
|
+
save_composite_result(evidence_dir, "03-datasource-apply.json", configured, files)
|
|
2348
|
+
else:
|
|
2349
|
+
configure_dry = datasource_configure_command(argparse.Namespace(
|
|
2350
|
+
install_root=args.install_root, data_root=args.data_root, profile=profile, apply=False, replace=False,
|
|
2351
|
+
))
|
|
2352
|
+
save_composite_result(evidence_dir, "02-datasource-dry.json", configure_dry, files)
|
|
2353
|
+
if not args.approve_configure:
|
|
2354
|
+
return {
|
|
2355
|
+
"success": True,
|
|
2356
|
+
"schemaVersion": "foggy-deepseek-datasource-run/v1",
|
|
2357
|
+
"profile": profile,
|
|
2358
|
+
"phaseStatus": "awaiting-configure-approval",
|
|
2359
|
+
"evidenceDir": str(evidence_dir),
|
|
2360
|
+
"evidenceFiles": files,
|
|
2361
|
+
"next": "rerun with --approve-configure after the datasource mutation is approved",
|
|
2362
|
+
"productionReady": False,
|
|
2363
|
+
}
|
|
2364
|
+
configured = datasource_configure_command(argparse.Namespace(
|
|
2365
|
+
install_root=args.install_root, data_root=args.data_root, profile=profile, apply=True, replace=False,
|
|
2366
|
+
))
|
|
2367
|
+
save_composite_result(evidence_dir, "03-datasource-apply.json", configured, files)
|
|
2368
|
+
state = read_onboarding_state(data_root, profile)
|
|
1960
2369
|
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2370
|
+
if step_completed(state, "datasourceVerified"):
|
|
2371
|
+
bound = resumed_phase(profile, "datasourceVerified")
|
|
2372
|
+
save_composite_result(evidence_dir, "04-datasource-test.json", bound, files)
|
|
2373
|
+
save_composite_result(evidence_dir, "05-datasource-bind.json", bound, files)
|
|
2374
|
+
else:
|
|
2375
|
+
tested = datasource_verify_command(argparse.Namespace(
|
|
2376
|
+
install_root=args.install_root, data_root=args.data_root, profile=profile, bind=False,
|
|
2377
|
+
))
|
|
2378
|
+
save_composite_result(evidence_dir, "04-datasource-test.json", tested, files)
|
|
2379
|
+
if not args.approve_bind:
|
|
2380
|
+
return {
|
|
2381
|
+
"success": True,
|
|
2382
|
+
"schemaVersion": "foggy-deepseek-datasource-run/v1",
|
|
2383
|
+
"profile": profile,
|
|
2384
|
+
"phaseStatus": "awaiting-bind-approval",
|
|
2385
|
+
"evidenceDir": str(evidence_dir),
|
|
2386
|
+
"evidenceFiles": files,
|
|
2387
|
+
"next": "rerun with --approve-configure --approve-bind after namespace binding is approved",
|
|
2388
|
+
"productionReady": False,
|
|
2389
|
+
}
|
|
2390
|
+
bound = datasource_verify_command(argparse.Namespace(
|
|
2391
|
+
install_root=args.install_root, data_root=args.data_root, profile=profile, bind=True,
|
|
2392
|
+
))
|
|
2393
|
+
save_composite_result(evidence_dir, "05-datasource-bind.json", bound, files)
|
|
2394
|
+
state = read_onboarding_state(data_root, profile)
|
|
2395
|
+
|
|
2396
|
+
if step_completed(state, "schemaDiscovered"):
|
|
2397
|
+
schema_step = state["steps"]["schemaDiscovered"]
|
|
2398
|
+
discovered = resumed_phase(
|
|
2399
|
+
profile,
|
|
2400
|
+
"schemaDiscovered",
|
|
2401
|
+
selectedCount=schema_step.get("selectedCount", 0),
|
|
2402
|
+
artifactPath=state.get("artifacts", {}).get("schemaDiscovery"),
|
|
2403
|
+
)
|
|
2404
|
+
else:
|
|
2405
|
+
discovered = schema_discover_command(argparse.Namespace(
|
|
2406
|
+
install_root=args.install_root,
|
|
2407
|
+
data_root=args.data_root,
|
|
2408
|
+
profile=profile,
|
|
2409
|
+
schema=args.schema,
|
|
2410
|
+
pattern=args.pattern,
|
|
2411
|
+
table=args.table,
|
|
2412
|
+
max_tables=args.max_tables,
|
|
2413
|
+
list_only=False,
|
|
2414
|
+
no_views=args.no_views,
|
|
2415
|
+
include_indexes=args.include_indexes,
|
|
2416
|
+
))
|
|
1977
2417
|
save_composite_result(evidence_dir, "06-schema.json", discovered, files)
|
|
1978
2418
|
return {
|
|
1979
2419
|
"success": True,
|
|
@@ -1989,6 +2429,45 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
1989
2429
|
}
|
|
1990
2430
|
|
|
1991
2431
|
|
|
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"])
|
|
2434
|
+
draft_dir = normalized(project_root / plan["draftDir"])
|
|
2435
|
+
if not is_child(draft_dir, project_root) or draft_dir == project_root:
|
|
2436
|
+
raise OnboardingError("draftDir must stay inside projectRoot and cannot equal it")
|
|
2437
|
+
manifest = semantic_manifest(draft_dir)
|
|
2438
|
+
semantic = state.get("semantic", {})
|
|
2439
|
+
registered = semantic.get("draftManifest") or {}
|
|
2440
|
+
matches = bool(
|
|
2441
|
+
step_completed(state, "semanticDrafted")
|
|
2442
|
+
and normalized(semantic.get("draftDir", draft_dir)) == draft_dir
|
|
2443
|
+
and semantic.get("bundleName") == plan["bundleName"]
|
|
2444
|
+
and semantic.get("queryModels") == plan["queryModels"]
|
|
2445
|
+
and registered.get("digest") == manifest["digest"]
|
|
2446
|
+
)
|
|
2447
|
+
return draft_dir, manifest, matches
|
|
2448
|
+
|
|
2449
|
+
|
|
2450
|
+
def published_digest_matches(state: dict, digest: str) -> bool:
|
|
2451
|
+
published = state.get("steps", {}).get("semanticPublished", {})
|
|
2452
|
+
return published.get("status") == "completed" and published.get("digest") == digest
|
|
2453
|
+
|
|
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
|
+
|
|
1992
2471
|
def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
1993
2472
|
approved_plan = validate_semantic_plan(read_json_object(normalized(args.semantic_plan), "Semantic plan"))
|
|
1994
2473
|
if not approved_plan.get("profile"):
|
|
@@ -1999,7 +2478,22 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
1999
2478
|
profile_args = argparse.Namespace(**vars(args))
|
|
2000
2479
|
profile_args.profile = profile
|
|
2001
2480
|
state, _install_state, _data_root, _runtime_state = require_profile(profile_args, require_runtime=True)
|
|
2002
|
-
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
|
+
)
|
|
2486
|
+
payload_path = normalized(args.query_payload)
|
|
2487
|
+
if not is_child(payload_path, project_root):
|
|
2488
|
+
raise OnboardingError(
|
|
2489
|
+
"Query payload must stay inside projectRoot; place contracts under .foggy/onboarding-contracts before approval"
|
|
2490
|
+
)
|
|
2491
|
+
bounded_query_payload(payload_path)
|
|
2492
|
+
declared_query_model = args.query_model or (approved_plan["queryModels"][0] if len(approved_plan["queryModels"]) == 1 else None)
|
|
2493
|
+
if not declared_query_model:
|
|
2494
|
+
raise OnboardingError("--query-model is required when the semantic plan declares multiple query models")
|
|
2495
|
+
if declared_query_model not in approved_plan["queryModels"]:
|
|
2496
|
+
raise OnboardingError("--query-model must be declared in semanticPlan.queryModels")
|
|
2003
2497
|
contract_evidence = approved_plan.get("evidenceDir")
|
|
2004
2498
|
if contract_evidence and args.evidence_dir:
|
|
2005
2499
|
if normalized(project_root / contract_evidence) != normalized(args.evidence_dir):
|
|
@@ -2007,79 +2501,144 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2007
2501
|
evidence_dir = composite_evidence_dir(project_root, profile, str(project_root / contract_evidence) if contract_evidence else args.evidence_dir)
|
|
2008
2502
|
files: list[str] = []
|
|
2009
2503
|
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2504
|
+
_draft_dir, manifest, draft_matches = semantic_plan_snapshot(state, approved_plan, project_root)
|
|
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
|
+
)
|
|
2511
|
+
if draft_matches or published_matches:
|
|
2512
|
+
drafted = resumed_phase(profile, "semanticDrafted", digest=manifest["digest"])
|
|
2513
|
+
else:
|
|
2514
|
+
drafted = semantic_draft_command(argparse.Namespace(
|
|
2515
|
+
install_root=args.install_root,
|
|
2516
|
+
data_root=args.data_root,
|
|
2517
|
+
profile=profile,
|
|
2518
|
+
semantic_plan=args.semantic_plan,
|
|
2519
|
+
))
|
|
2520
|
+
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2521
|
+
manifest = state["semantic"]["draftManifest"]
|
|
2016
2522
|
save_composite_result(evidence_dir, "07-semantic-draft.json", drafted, files)
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2523
|
+
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2524
|
+
validation_step = state.get("steps", {}).get("semanticValidated", {})
|
|
2525
|
+
published_matches = published_digest_matches(state, manifest["digest"])
|
|
2526
|
+
validation_matches = validation_step.get("status") == "completed" and validation_step.get("digest") == manifest["digest"]
|
|
2527
|
+
if validation_matches or published_matches:
|
|
2528
|
+
if published_matches and not validation_matches:
|
|
2529
|
+
mark_step(state, "semanticValidated", "completed", digest=manifest["digest"], resumedFromPublished=True)
|
|
2530
|
+
write_onboarding_state(normalized(state["dataRoot"]), state)
|
|
2531
|
+
validated = resumed_phase(profile, "semanticValidated", digest=manifest["digest"])
|
|
2532
|
+
save_composite_result(evidence_dir, "08-semantic-validate-dry.json", validated, files)
|
|
2533
|
+
else:
|
|
2534
|
+
validate_dry = semantic_validate_command(argparse.Namespace(
|
|
2535
|
+
install_root=args.install_root,
|
|
2536
|
+
data_root=args.data_root,
|
|
2537
|
+
profile=profile,
|
|
2538
|
+
apply=False,
|
|
2539
|
+
include_stack_trace=False,
|
|
2540
|
+
))
|
|
2541
|
+
save_composite_result(evidence_dir, "08-semantic-validate-dry.json", validate_dry, files)
|
|
2542
|
+
if not args.approve_validate:
|
|
2543
|
+
return {
|
|
2544
|
+
"success": True,
|
|
2545
|
+
"schemaVersion": "foggy-deepseek-semantic-run/v1",
|
|
2546
|
+
"profile": profile,
|
|
2547
|
+
"phaseStatus": "awaiting-validate-approval",
|
|
2548
|
+
"evidenceDir": str(evidence_dir),
|
|
2549
|
+
"evidenceFiles": files,
|
|
2550
|
+
"next": "rerun with --approve-validate after the validation catalog mutation is approved",
|
|
2551
|
+
"productionReady": False,
|
|
2552
|
+
}
|
|
2553
|
+
validated = semantic_validate_command(argparse.Namespace(
|
|
2554
|
+
install_root=args.install_root,
|
|
2555
|
+
data_root=args.data_root,
|
|
2556
|
+
profile=profile,
|
|
2557
|
+
apply=True,
|
|
2558
|
+
include_stack_trace=False,
|
|
2559
|
+
))
|
|
2044
2560
|
save_composite_result(evidence_dir, "09-semantic-validate-apply.json", validated, files)
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2561
|
+
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2562
|
+
if published_digest_matches(state, manifest["digest"]):
|
|
2563
|
+
published = resumed_phase(
|
|
2564
|
+
profile,
|
|
2565
|
+
"semanticPublished",
|
|
2566
|
+
digest=manifest["digest"],
|
|
2567
|
+
bundleName=state["semantic"]["bundleName"],
|
|
2568
|
+
)
|
|
2569
|
+
save_composite_result(evidence_dir, "10-semantic-publish-dry.json", published, files)
|
|
2570
|
+
else:
|
|
2571
|
+
publish_dry = semantic_publish_command(argparse.Namespace(
|
|
2572
|
+
install_root=args.install_root,
|
|
2573
|
+
data_root=args.data_root,
|
|
2574
|
+
profile=profile,
|
|
2575
|
+
apply=False,
|
|
2576
|
+
replace_bundle=False,
|
|
2577
|
+
watch=False,
|
|
2578
|
+
prune=False,
|
|
2579
|
+
))
|
|
2580
|
+
save_composite_result(evidence_dir, "10-semantic-publish-dry.json", publish_dry, files)
|
|
2581
|
+
if not args.approve_publish:
|
|
2582
|
+
return {
|
|
2583
|
+
"success": True,
|
|
2584
|
+
"schemaVersion": "foggy-deepseek-semantic-run/v1",
|
|
2585
|
+
"profile": profile,
|
|
2586
|
+
"phaseStatus": "awaiting-publish-approval",
|
|
2587
|
+
"evidenceDir": str(evidence_dir),
|
|
2588
|
+
"evidenceFiles": files,
|
|
2589
|
+
"next": "rerun with --approve-validate --approve-publish after publication is approved",
|
|
2590
|
+
"productionReady": False,
|
|
2591
|
+
}
|
|
2592
|
+
published = semantic_publish_command(argparse.Namespace(
|
|
2593
|
+
install_root=args.install_root,
|
|
2594
|
+
data_root=args.data_root,
|
|
2595
|
+
profile=profile,
|
|
2596
|
+
apply=True,
|
|
2597
|
+
replace_bundle=False,
|
|
2598
|
+
watch=False,
|
|
2599
|
+
prune=False,
|
|
2600
|
+
))
|
|
2601
|
+
save_composite_result(evidence_dir, "11-semantic-publish-apply.json", published, files)
|
|
2602
|
+
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2603
|
+
payload_digest = sha256(payload_path)
|
|
2604
|
+
verified_step = workspace_query_verification(state, project_root, declared_query_model, payload_digest)
|
|
2605
|
+
if verified_step:
|
|
2606
|
+
resumed = resumed_phase(
|
|
2607
|
+
profile,
|
|
2608
|
+
"semanticVerified",
|
|
2609
|
+
queryModel=declared_query_model,
|
|
2610
|
+
queryValidated=True,
|
|
2611
|
+
queryExecuted=True,
|
|
2612
|
+
rowCount=verified_step.get("rowCount"),
|
|
2613
|
+
queryPayloadDigest=payload_digest,
|
|
2614
|
+
)
|
|
2615
|
+
save_composite_result(evidence_dir, "12-query-validate.json", resumed, files)
|
|
2616
|
+
save_composite_result(evidence_dir, "13-query-execute.json", resumed, files)
|
|
2617
|
+
status = onboarding_status_command(argparse.Namespace(
|
|
2618
|
+
install_root=args.install_root, data_root=args.data_root, profile=profile,
|
|
2619
|
+
))
|
|
2620
|
+
save_composite_result(evidence_dir, "14-status.json", status, files)
|
|
2056
2621
|
return {
|
|
2057
2622
|
"success": True,
|
|
2058
2623
|
"schemaVersion": "foggy-deepseek-semantic-run/v1",
|
|
2059
2624
|
"profile": profile,
|
|
2060
|
-
"phaseStatus": "
|
|
2625
|
+
"phaseStatus": "completed",
|
|
2626
|
+
"resumed": True,
|
|
2627
|
+
"queryModel": declared_query_model,
|
|
2628
|
+
"queryValidated": True,
|
|
2629
|
+
"queryExecuted": True,
|
|
2630
|
+
"rowCount": verified_step.get("rowCount"),
|
|
2061
2631
|
"evidenceDir": str(evidence_dir),
|
|
2062
2632
|
"evidenceFiles": files,
|
|
2063
|
-
"next": "rerun with --approve-validate --approve-publish after publication is approved",
|
|
2064
2633
|
"productionReady": False,
|
|
2065
2634
|
}
|
|
2066
|
-
|
|
2067
|
-
published = semantic_publish_command(argparse.Namespace(
|
|
2068
|
-
install_root=args.install_root,
|
|
2069
|
-
data_root=args.data_root,
|
|
2070
|
-
profile=profile,
|
|
2071
|
-
apply=True,
|
|
2072
|
-
replace_bundle=False,
|
|
2073
|
-
watch=False,
|
|
2074
|
-
prune=False,
|
|
2075
|
-
))
|
|
2076
|
-
save_composite_result(evidence_dir, "11-semantic-publish-apply.json", published, files)
|
|
2077
2635
|
query_validated = semantic_verify_command(argparse.Namespace(
|
|
2078
2636
|
install_root=args.install_root,
|
|
2079
2637
|
data_root=args.data_root,
|
|
2080
2638
|
profile=profile,
|
|
2081
|
-
query_model=
|
|
2639
|
+
query_model=declared_query_model,
|
|
2082
2640
|
query_payload=args.query_payload,
|
|
2641
|
+
project_root=str(project_root),
|
|
2083
2642
|
execute=False,
|
|
2084
2643
|
))
|
|
2085
2644
|
save_composite_result(evidence_dir, "12-query-validate.json", query_validated, files)
|
|
@@ -2101,8 +2660,9 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2101
2660
|
install_root=args.install_root,
|
|
2102
2661
|
data_root=args.data_root,
|
|
2103
2662
|
profile=profile,
|
|
2104
|
-
query_model=
|
|
2663
|
+
query_model=declared_query_model,
|
|
2105
2664
|
query_payload=args.query_payload,
|
|
2665
|
+
project_root=str(project_root),
|
|
2106
2666
|
execute=True,
|
|
2107
2667
|
))
|
|
2108
2668
|
save_composite_result(evidence_dir, "13-query-execute.json", executed, files)
|
|
@@ -2237,6 +2797,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2237
2797
|
install.add_argument("--project-root")
|
|
2238
2798
|
install.add_argument("--asset-cache-dir", action="append", default=[])
|
|
2239
2799
|
install.add_argument("--replace-skill", action="store_true")
|
|
2800
|
+
install.add_argument("--repair-component", choices=("cli", "launcher", "analysis-skill"))
|
|
2240
2801
|
install.add_argument("--skip-cli-install", action="store_true")
|
|
2241
2802
|
install.add_argument("--cli-command")
|
|
2242
2803
|
install.add_argument("--progress-file")
|
|
@@ -2280,15 +2841,31 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2280
2841
|
status = sub.add_parser("onboard-status")
|
|
2281
2842
|
status.add_argument("--install-root")
|
|
2282
2843
|
status.add_argument("--data-root")
|
|
2283
|
-
status.add_argument("--profile"
|
|
2844
|
+
status.add_argument("--profile")
|
|
2284
2845
|
status.set_defaults(handler=onboarding_status_command)
|
|
2285
2846
|
|
|
2286
2847
|
resume = sub.add_parser("onboard-resume")
|
|
2287
2848
|
resume.add_argument("--install-root")
|
|
2288
2849
|
resume.add_argument("--data-root")
|
|
2289
|
-
resume.add_argument("--profile"
|
|
2850
|
+
resume.add_argument("--profile")
|
|
2290
2851
|
resume.set_defaults(handler=onboarding_resume_command)
|
|
2291
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
|
+
|
|
2292
2869
|
datasource_run = sub.add_parser("onboard-datasource-run")
|
|
2293
2870
|
datasource_run.add_argument("--install-root")
|
|
2294
2871
|
datasource_run.add_argument("--data-root")
|
|
@@ -2309,6 +2886,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2309
2886
|
semantic_run = sub.add_parser("onboard-semantic-run")
|
|
2310
2887
|
semantic_run.add_argument("--install-root")
|
|
2311
2888
|
semantic_run.add_argument("--data-root")
|
|
2889
|
+
semantic_run.add_argument("--project-root")
|
|
2312
2890
|
semantic_run.add_argument("--profile")
|
|
2313
2891
|
semantic_run.add_argument("--semantic-plan", required=True)
|
|
2314
2892
|
semantic_run.add_argument("--query-payload", required=True)
|
|
@@ -2322,7 +2900,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2322
2900
|
datasource_configure = sub.add_parser("datasource-configure")
|
|
2323
2901
|
datasource_configure.add_argument("--install-root")
|
|
2324
2902
|
datasource_configure.add_argument("--data-root")
|
|
2325
|
-
datasource_configure.add_argument("--profile"
|
|
2903
|
+
datasource_configure.add_argument("--profile")
|
|
2326
2904
|
datasource_configure.add_argument("--apply", action="store_true")
|
|
2327
2905
|
datasource_configure.add_argument("--replace", action="store_true")
|
|
2328
2906
|
datasource_configure.set_defaults(handler=datasource_configure_command)
|
|
@@ -2330,14 +2908,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2330
2908
|
datasource_verify = sub.add_parser("datasource-verify")
|
|
2331
2909
|
datasource_verify.add_argument("--install-root")
|
|
2332
2910
|
datasource_verify.add_argument("--data-root")
|
|
2333
|
-
datasource_verify.add_argument("--profile"
|
|
2911
|
+
datasource_verify.add_argument("--profile")
|
|
2334
2912
|
datasource_verify.add_argument("--bind", action="store_true")
|
|
2335
2913
|
datasource_verify.set_defaults(handler=datasource_verify_command)
|
|
2336
2914
|
|
|
2337
2915
|
schema_discover = sub.add_parser("schema-discover")
|
|
2338
2916
|
schema_discover.add_argument("--install-root")
|
|
2339
2917
|
schema_discover.add_argument("--data-root")
|
|
2340
|
-
schema_discover.add_argument("--profile"
|
|
2918
|
+
schema_discover.add_argument("--profile")
|
|
2341
2919
|
schema_discover.add_argument("--schema", action="append")
|
|
2342
2920
|
schema_discover.add_argument("--pattern")
|
|
2343
2921
|
schema_discover.add_argument("--table", action="append")
|
|
@@ -2350,14 +2928,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2350
2928
|
semantic_draft = sub.add_parser("semantic-draft")
|
|
2351
2929
|
semantic_draft.add_argument("--install-root")
|
|
2352
2930
|
semantic_draft.add_argument("--data-root")
|
|
2353
|
-
semantic_draft.add_argument("--profile"
|
|
2931
|
+
semantic_draft.add_argument("--profile")
|
|
2354
2932
|
semantic_draft.add_argument("--semantic-plan", required=True)
|
|
2355
2933
|
semantic_draft.set_defaults(handler=semantic_draft_command)
|
|
2356
2934
|
|
|
2357
2935
|
semantic_validate = sub.add_parser("semantic-validate")
|
|
2358
2936
|
semantic_validate.add_argument("--install-root")
|
|
2359
2937
|
semantic_validate.add_argument("--data-root")
|
|
2360
|
-
semantic_validate.add_argument("--profile"
|
|
2938
|
+
semantic_validate.add_argument("--profile")
|
|
2361
2939
|
semantic_validate.add_argument("--apply", action="store_true")
|
|
2362
2940
|
semantic_validate.add_argument("--include-stack-trace", action="store_true")
|
|
2363
2941
|
semantic_validate.set_defaults(handler=semantic_validate_command)
|
|
@@ -2365,7 +2943,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2365
2943
|
semantic_publish = sub.add_parser("semantic-publish")
|
|
2366
2944
|
semantic_publish.add_argument("--install-root")
|
|
2367
2945
|
semantic_publish.add_argument("--data-root")
|
|
2368
|
-
semantic_publish.add_argument("--profile"
|
|
2946
|
+
semantic_publish.add_argument("--profile")
|
|
2369
2947
|
semantic_publish.add_argument("--apply", action="store_true")
|
|
2370
2948
|
semantic_publish.add_argument("--replace-bundle", action="store_true")
|
|
2371
2949
|
semantic_publish.add_argument("--watch", action="store_true")
|
|
@@ -2375,7 +2953,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2375
2953
|
semantic_verify = sub.add_parser("semantic-verify")
|
|
2376
2954
|
semantic_verify.add_argument("--install-root")
|
|
2377
2955
|
semantic_verify.add_argument("--data-root")
|
|
2378
|
-
semantic_verify.add_argument("--
|
|
2956
|
+
semantic_verify.add_argument("--project-root")
|
|
2957
|
+
semantic_verify.add_argument("--profile")
|
|
2379
2958
|
semantic_verify.add_argument("--query-model")
|
|
2380
2959
|
semantic_verify.add_argument("--query-payload", required=True)
|
|
2381
2960
|
semantic_verify.add_argument("--execute", action="store_true")
|