open-research-protocol 0.4.4 → 0.4.6
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 +14 -2
- package/cli/orp.py +504 -27
- package/docs/AGENT_LOOP.md +2 -0
- package/docs/CANONICAL_CLI_BOUNDARY.md +20 -1
- package/docs/ORP_REASONING_KERNEL_V0_1.md +495 -0
- package/examples/README.md +2 -0
- package/examples/kernel/trace-widget.task.kernel.yml +18 -0
- package/examples/orp.reasoning-kernel.starter.yml +61 -0
- package/package.json +1 -1
- package/spec/v1/kernel.schema.json +286 -0
- package/spec/v1/orp.config.schema.json +59 -0
- package/spec/v1/packet.schema.json +97 -0
package/cli/orp.py
CHANGED
|
@@ -4170,11 +4170,38 @@ def _effective_remote_context(
|
|
|
4170
4170
|
}
|
|
4171
4171
|
|
|
4172
4172
|
|
|
4173
|
-
def
|
|
4173
|
+
def _init_kernel_task_template(repo_name: str) -> str:
|
|
4174
|
+
safe_name = str(repo_name or "").strip() or "my-project"
|
|
4175
|
+
return (
|
|
4176
|
+
'schema_version: "1.0.0"\n'
|
|
4177
|
+
"artifact_class: task\n"
|
|
4178
|
+
f"object: bootstrap ORP governance for {safe_name}\n"
|
|
4179
|
+
"goal: establish a local-first ORP workflow with a promotable starter task artifact\n"
|
|
4180
|
+
"boundary:\n"
|
|
4181
|
+
" - repository bootstrap and governance setup\n"
|
|
4182
|
+
" - initial ORP validation and readiness loop\n"
|
|
4183
|
+
"constraints:\n"
|
|
4184
|
+
" - keep evidence in canonical artifact paths instead of ORP process metadata\n"
|
|
4185
|
+
" - preserve a clean handoff and checkpoint discipline from day one\n"
|
|
4186
|
+
"success_criteria:\n"
|
|
4187
|
+
" - default ORP gate profile passes\n"
|
|
4188
|
+
" - repo is ready for a first meaningful work branch and checkpoint sequence\n"
|
|
4189
|
+
"canonical_target:\n"
|
|
4190
|
+
" - orp/HANDOFF.md\n"
|
|
4191
|
+
" - orp/checkpoints/CHECKPOINT_LOG.md\n"
|
|
4192
|
+
"artifact_refs:\n"
|
|
4193
|
+
" - orp.yml\n"
|
|
4194
|
+
" - orp/HANDOFF.md\n"
|
|
4195
|
+
" - orp/checkpoints/CHECKPOINT_LOG.md\n"
|
|
4196
|
+
)
|
|
4197
|
+
|
|
4198
|
+
|
|
4199
|
+
def _init_config_starter(repo_name: str = "my-project") -> str:
|
|
4200
|
+
safe_name = str(repo_name or "").strip() or "my-project"
|
|
4174
4201
|
return (
|
|
4175
4202
|
'version: "1"\n'
|
|
4176
4203
|
"project:\n"
|
|
4177
|
-
" name:
|
|
4204
|
+
f" name: {safe_name}\n"
|
|
4178
4205
|
" repo_root: .\n"
|
|
4179
4206
|
" canonical_paths:\n"
|
|
4180
4207
|
" code: src/\n"
|
|
@@ -4192,6 +4219,25 @@ def _init_config_starter() -> str:
|
|
|
4192
4219
|
" blocked: blocked\n"
|
|
4193
4220
|
" done: reviewed\n"
|
|
4194
4221
|
"gates:\n"
|
|
4222
|
+
" - id: starter_kernel\n"
|
|
4223
|
+
" description: Validate the starter ORP reasoning-kernel task artifact\n"
|
|
4224
|
+
" phase: structure_kernel\n"
|
|
4225
|
+
" command: echo ORP_KERNEL_STARTER\n"
|
|
4226
|
+
" pass:\n"
|
|
4227
|
+
" exit_codes: [0]\n"
|
|
4228
|
+
" stdout_must_contain:\n"
|
|
4229
|
+
" - ORP_KERNEL_STARTER\n"
|
|
4230
|
+
" kernel:\n"
|
|
4231
|
+
" mode: hard\n"
|
|
4232
|
+
" artifacts:\n"
|
|
4233
|
+
" - path: analysis/orp.kernel.task.yml\n"
|
|
4234
|
+
" artifact_class: task\n"
|
|
4235
|
+
" evidence:\n"
|
|
4236
|
+
" status: process_only\n"
|
|
4237
|
+
" note: Starter kernel artifact records task structure, not evidence.\n"
|
|
4238
|
+
" paths:\n"
|
|
4239
|
+
" - analysis/orp.kernel.task.yml\n"
|
|
4240
|
+
" on_fail: stop\n"
|
|
4195
4241
|
" - id: smoke\n"
|
|
4196
4242
|
" description: Basic smoke gate\n"
|
|
4197
4243
|
" phase: verification\n"
|
|
@@ -4206,6 +4252,7 @@ def _init_config_starter() -> str:
|
|
|
4206
4252
|
" mode: discovery\n"
|
|
4207
4253
|
" packet_kind: problem_scope\n"
|
|
4208
4254
|
" gate_ids:\n"
|
|
4255
|
+
" - starter_kernel\n"
|
|
4209
4256
|
" - smoke\n"
|
|
4210
4257
|
)
|
|
4211
4258
|
|
|
@@ -5090,6 +5137,7 @@ def _about_payload() -> dict[str, Any]:
|
|
|
5090
5137
|
"schemas": {
|
|
5091
5138
|
"config": "spec/v1/orp.config.schema.json",
|
|
5092
5139
|
"packet": "spec/v1/packet.schema.json",
|
|
5140
|
+
"kernel": "spec/v1/kernel.schema.json",
|
|
5093
5141
|
"profile_pack": "spec/v1/profile-pack.schema.json",
|
|
5094
5142
|
"link_project": "spec/v1/link-project.schema.json",
|
|
5095
5143
|
"link_session": "spec/v1/link-session.schema.json",
|
|
@@ -5097,6 +5145,14 @@ def _about_payload() -> dict[str, Any]:
|
|
|
5097
5145
|
"runner_runtime": "spec/v1/runner-runtime.schema.json",
|
|
5098
5146
|
},
|
|
5099
5147
|
"abilities": [
|
|
5148
|
+
{
|
|
5149
|
+
"id": "kernel",
|
|
5150
|
+
"description": "Reasoning-kernel artifact scaffolding and validation for promotable repository truth.",
|
|
5151
|
+
"entrypoints": [
|
|
5152
|
+
["kernel", "validate"],
|
|
5153
|
+
["kernel", "scaffold"],
|
|
5154
|
+
],
|
|
5155
|
+
},
|
|
5100
5156
|
{
|
|
5101
5157
|
"id": "workspace",
|
|
5102
5158
|
"description": "Hosted workspace auth, ideas, features, worlds, checkpoints, and worker operations.",
|
|
@@ -5185,6 +5241,8 @@ def _about_payload() -> dict[str, Any]:
|
|
|
5185
5241
|
"commands": [
|
|
5186
5242
|
{"name": "home", "path": ["home"], "json_output": True},
|
|
5187
5243
|
{"name": "about", "path": ["about"], "json_output": True},
|
|
5244
|
+
{"name": "kernel_validate", "path": ["kernel", "validate"], "json_output": True},
|
|
5245
|
+
{"name": "kernel_scaffold", "path": ["kernel", "scaffold"], "json_output": True},
|
|
5188
5246
|
{"name": "auth_login", "path": ["auth", "login"], "json_output": True},
|
|
5189
5247
|
{"name": "auth_verify", "path": ["auth", "verify"], "json_output": True},
|
|
5190
5248
|
{"name": "auth_logout", "path": ["auth", "logout"], "json_output": True},
|
|
@@ -5252,6 +5310,7 @@ def _about_payload() -> dict[str, Any]:
|
|
|
5252
5310
|
"ORP files are process-only and are not evidence.",
|
|
5253
5311
|
"Canonical evidence lives in repo artifact paths outside ORP docs.",
|
|
5254
5312
|
"Default CLI output is human-readable; listed commands with json_output=true also support --json.",
|
|
5313
|
+
"Reasoning-kernel artifacts shape promotable repository truth for tasks, decisions, hypotheses, experiments, checkpoints, policies, and results.",
|
|
5255
5314
|
"Discovery profiles in ORP are portable search-intent files managed directly by ORP.",
|
|
5256
5315
|
"Collaboration is a built-in ORP ability exposed through `orp collaborate ...`.",
|
|
5257
5316
|
"Project/session linking is a built-in ORP ability exposed through `orp link ...` and stored machine-locally under `.git/orp/link/`.",
|
|
@@ -5434,41 +5493,48 @@ def _home_payload(repo_root: Path, config_arg: str) -> dict[str, Any]:
|
|
|
5434
5493
|
)
|
|
5435
5494
|
quick_actions.insert(
|
|
5436
5495
|
3,
|
|
5496
|
+
{
|
|
5497
|
+
"label": "Validate the starter kernel artifact",
|
|
5498
|
+
"command": "orp kernel validate analysis/orp.kernel.task.yml --json",
|
|
5499
|
+
},
|
|
5500
|
+
)
|
|
5501
|
+
quick_actions.insert(
|
|
5502
|
+
4,
|
|
5437
5503
|
{
|
|
5438
5504
|
"label": "Checkpoint and back up current work to a dedicated remote ref",
|
|
5439
5505
|
"command": 'orp backup -m "backup current work" --json',
|
|
5440
5506
|
},
|
|
5441
5507
|
)
|
|
5442
5508
|
quick_actions.insert(
|
|
5443
|
-
|
|
5509
|
+
5,
|
|
5444
5510
|
{
|
|
5445
5511
|
"label": "Mark the repo locally ready after validation",
|
|
5446
5512
|
"command": "orp ready --json",
|
|
5447
5513
|
},
|
|
5448
5514
|
)
|
|
5449
5515
|
quick_actions.insert(
|
|
5450
|
-
|
|
5516
|
+
6,
|
|
5451
5517
|
{
|
|
5452
5518
|
"label": "Inspect local project/session link state",
|
|
5453
5519
|
"command": "orp link status --json",
|
|
5454
5520
|
},
|
|
5455
5521
|
)
|
|
5456
5522
|
quick_actions.insert(
|
|
5457
|
-
|
|
5523
|
+
7,
|
|
5458
5524
|
{
|
|
5459
5525
|
"label": "Inspect machine runner state",
|
|
5460
5526
|
"command": "orp runner status --json",
|
|
5461
5527
|
},
|
|
5462
5528
|
)
|
|
5463
5529
|
quick_actions.insert(
|
|
5464
|
-
|
|
5530
|
+
8,
|
|
5465
5531
|
{
|
|
5466
5532
|
"label": "Inspect and repair governance health",
|
|
5467
5533
|
"command": "orp doctor --json",
|
|
5468
5534
|
},
|
|
5469
5535
|
)
|
|
5470
5536
|
quick_actions.insert(
|
|
5471
|
-
|
|
5537
|
+
9,
|
|
5472
5538
|
{
|
|
5473
5539
|
"label": "Inspect safe cleanup candidates",
|
|
5474
5540
|
"command": "orp cleanup --json",
|
|
@@ -5929,6 +5995,7 @@ def _perform_github_discovery_scan(
|
|
|
5929
5995
|
def cmd_init(args: argparse.Namespace) -> int:
|
|
5930
5996
|
repo_root = Path(args.repo_root).resolve()
|
|
5931
5997
|
repo_root.mkdir(parents=True, exist_ok=True)
|
|
5998
|
+
repo_name = repo_root.name or "my-project"
|
|
5932
5999
|
|
|
5933
6000
|
default_branch = str(getattr(args, "default_branch", "main") or "main").strip() or "main"
|
|
5934
6001
|
allow_protected_branch_work = bool(getattr(args, "allow_protected_branch_work", False))
|
|
@@ -5941,8 +6008,9 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
5941
6008
|
config_path = repo_root / args.config
|
|
5942
6009
|
config_action = "kept"
|
|
5943
6010
|
if not config_path.exists():
|
|
5944
|
-
config_path.write_text(_init_config_starter(), encoding="utf-8")
|
|
6011
|
+
config_path.write_text(_init_config_starter(repo_name), encoding="utf-8")
|
|
5945
6012
|
config_action = "created"
|
|
6013
|
+
kernel_starter_path = repo_root / "analysis" / "orp.kernel.task.yml"
|
|
5946
6014
|
|
|
5947
6015
|
initialized_at_utc = _now_utc()
|
|
5948
6016
|
git_snapshot = _git_governance_snapshot(
|
|
@@ -5971,7 +6039,9 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
5971
6039
|
|
|
5972
6040
|
if git_snapshot["dirty"]:
|
|
5973
6041
|
warnings.append("working tree is dirty; create an explicit checkpoint commit before agent work.")
|
|
5974
|
-
next_actions.append(
|
|
6042
|
+
next_actions.append(
|
|
6043
|
+
'git add orp.yml orp analysis/orp.kernel.task.yml && git commit -m "checkpoint: bootstrap ORP governance"'
|
|
6044
|
+
)
|
|
5975
6045
|
|
|
5976
6046
|
if not git_snapshot["has_commits"]:
|
|
5977
6047
|
notes.append("repo has no commits yet; treat the first commit as the initial ORP checkpoint.")
|
|
@@ -6013,6 +6083,10 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
6013
6083
|
"path": _path_for_state(checkpoint_log_path, repo_root),
|
|
6014
6084
|
"action": _write_text_if_missing(checkpoint_log_path, _init_checkpoint_log_template()),
|
|
6015
6085
|
}
|
|
6086
|
+
files["starter_kernel"] = {
|
|
6087
|
+
"path": _path_for_state(kernel_starter_path, repo_root),
|
|
6088
|
+
"action": _write_text_if_missing(kernel_starter_path, _init_kernel_task_template(repo_name)),
|
|
6089
|
+
}
|
|
6016
6090
|
|
|
6017
6091
|
agent_policy_exists = agent_policy_path.exists()
|
|
6018
6092
|
agent_policy = _agent_policy_payload(
|
|
@@ -6658,6 +6732,7 @@ def _apply_doctor_fixes(repo_root: Path, config_arg: str, status_payload: dict[s
|
|
|
6658
6732
|
state_path = repo_root / "orp" / "state.json"
|
|
6659
6733
|
state = _read_json_if_exists(state_path)
|
|
6660
6734
|
governance_state = state.get("governance") if isinstance(state.get("governance"), dict) else {}
|
|
6735
|
+
repo_name = repo_root.name or "my-project"
|
|
6661
6736
|
|
|
6662
6737
|
default_branch = str(governance_state.get("default_branch", "main")).strip() or "main"
|
|
6663
6738
|
allow_protected_branch_work = bool(governance_state.get("allow_protected_branch_work", False))
|
|
@@ -6669,7 +6744,7 @@ def _apply_doctor_fixes(repo_root: Path, config_arg: str, status_payload: dict[s
|
|
|
6669
6744
|
config_path = config_path.resolve()
|
|
6670
6745
|
|
|
6671
6746
|
if not config_path.exists():
|
|
6672
|
-
config_path.write_text(_init_config_starter(), encoding="utf-8")
|
|
6747
|
+
config_path.write_text(_init_config_starter(repo_name), encoding="utf-8")
|
|
6673
6748
|
|
|
6674
6749
|
git_snapshot = _git_governance_snapshot(
|
|
6675
6750
|
repo_root,
|
|
@@ -6685,6 +6760,7 @@ def _apply_doctor_fixes(repo_root: Path, config_arg: str, status_payload: dict[s
|
|
|
6685
6760
|
|
|
6686
6761
|
handoff_path = repo_root / "orp" / "HANDOFF.md"
|
|
6687
6762
|
checkpoint_log_path = repo_root / "orp" / "checkpoints" / "CHECKPOINT_LOG.md"
|
|
6763
|
+
kernel_starter_path = repo_root / "analysis" / "orp.kernel.task.yml"
|
|
6688
6764
|
governance_path = repo_root / "orp" / "governance.json"
|
|
6689
6765
|
agent_policy_path = repo_root / "orp" / "agent-policy.json"
|
|
6690
6766
|
|
|
@@ -6700,6 +6776,8 @@ def _apply_doctor_fixes(repo_root: Path, config_arg: str, status_payload: dict[s
|
|
|
6700
6776
|
fixes_applied.append("created_handoff")
|
|
6701
6777
|
if _write_text_if_missing(checkpoint_log_path, _init_checkpoint_log_template()) == "created":
|
|
6702
6778
|
fixes_applied.append("created_checkpoint_log")
|
|
6779
|
+
if _write_text_if_missing(kernel_starter_path, _init_kernel_task_template(repo_name)) == "created":
|
|
6780
|
+
fixes_applied.append("created_starter_kernel")
|
|
6703
6781
|
|
|
6704
6782
|
_write_json(
|
|
6705
6783
|
agent_policy_path,
|
|
@@ -7178,6 +7256,326 @@ def _gate_map(config: dict[str, Any]) -> dict[str, dict[str, Any]]:
|
|
|
7178
7256
|
return out
|
|
7179
7257
|
|
|
7180
7258
|
|
|
7259
|
+
KERNEL_ARTIFACT_CLASS_REQUIREMENTS: dict[str, list[str]] = {
|
|
7260
|
+
"task": ["object", "goal", "boundary", "constraints", "success_criteria"],
|
|
7261
|
+
"decision": ["question", "chosen_path", "rejected_alternatives", "rationale", "consequences"],
|
|
7262
|
+
"hypothesis": ["claim", "boundary", "assumptions", "test_path", "falsifiers"],
|
|
7263
|
+
"experiment": ["objective", "method", "inputs", "outputs", "evidence_expectations", "interpretation_limits"],
|
|
7264
|
+
"checkpoint": ["completed_unit", "current_state", "risks", "next_handoff_target", "artifact_refs"],
|
|
7265
|
+
"policy": ["scope", "rule", "rationale", "invariants", "enforcement_surface"],
|
|
7266
|
+
"result": ["claim", "evidence_paths", "status", "interpretation_limits", "next_follow_up"],
|
|
7267
|
+
}
|
|
7268
|
+
|
|
7269
|
+
|
|
7270
|
+
def _kernel_field_present(value: Any) -> bool:
|
|
7271
|
+
if isinstance(value, str):
|
|
7272
|
+
return bool(value.strip())
|
|
7273
|
+
if isinstance(value, list):
|
|
7274
|
+
return any(_kernel_field_present(item) for item in value)
|
|
7275
|
+
if isinstance(value, dict):
|
|
7276
|
+
return len(value) > 0
|
|
7277
|
+
return value is not None
|
|
7278
|
+
|
|
7279
|
+
|
|
7280
|
+
def _kernel_validation_mode(gate: dict[str, Any]) -> str:
|
|
7281
|
+
kernel_cfg = gate.get("kernel") if isinstance(gate.get("kernel"), dict) else {}
|
|
7282
|
+
default_mode = "hard" if str(gate.get("phase", "")).strip() == "structure_kernel" else "soft"
|
|
7283
|
+
mode = str(kernel_cfg.get("mode", default_mode)).strip().lower()
|
|
7284
|
+
if mode in {"soft", "hard"}:
|
|
7285
|
+
return mode
|
|
7286
|
+
return default_mode
|
|
7287
|
+
|
|
7288
|
+
|
|
7289
|
+
def _kernel_artifact_specs(
|
|
7290
|
+
gate: dict[str, Any],
|
|
7291
|
+
repo_root: Path,
|
|
7292
|
+
vars_map: dict[str, str],
|
|
7293
|
+
) -> list[dict[str, Any]]:
|
|
7294
|
+
kernel_cfg = gate.get("kernel") if isinstance(gate.get("kernel"), dict) else {}
|
|
7295
|
+
raw_artifacts = kernel_cfg.get("artifacts")
|
|
7296
|
+
if not isinstance(raw_artifacts, list):
|
|
7297
|
+
return []
|
|
7298
|
+
|
|
7299
|
+
specs: list[dict[str, Any]] = []
|
|
7300
|
+
for raw in raw_artifacts:
|
|
7301
|
+
if not isinstance(raw, dict):
|
|
7302
|
+
continue
|
|
7303
|
+
path_raw = str(raw.get("path", "")).strip()
|
|
7304
|
+
if not path_raw:
|
|
7305
|
+
continue
|
|
7306
|
+
replaced = _replace_vars(path_raw, vars_map)
|
|
7307
|
+
path = Path(replaced)
|
|
7308
|
+
if not path.is_absolute():
|
|
7309
|
+
path = repo_root / path
|
|
7310
|
+
specs.append(
|
|
7311
|
+
{
|
|
7312
|
+
"path": path.resolve(),
|
|
7313
|
+
"expected_artifact_class": str(raw.get("artifact_class", "")).strip(),
|
|
7314
|
+
"required": bool(raw.get("required", True)),
|
|
7315
|
+
"extra_required_fields": _unique_strings(
|
|
7316
|
+
[str(x).strip() for x in raw.get("extra_required_fields", []) if isinstance(x, str)]
|
|
7317
|
+
),
|
|
7318
|
+
}
|
|
7319
|
+
)
|
|
7320
|
+
return specs
|
|
7321
|
+
|
|
7322
|
+
|
|
7323
|
+
def _validate_kernel_gate(
|
|
7324
|
+
gate: dict[str, Any],
|
|
7325
|
+
repo_root: Path,
|
|
7326
|
+
vars_map: dict[str, str],
|
|
7327
|
+
) -> dict[str, Any] | None:
|
|
7328
|
+
if str(gate.get("phase", "")).strip() != "structure_kernel":
|
|
7329
|
+
return None
|
|
7330
|
+
if not isinstance(gate.get("kernel"), dict):
|
|
7331
|
+
return None
|
|
7332
|
+
|
|
7333
|
+
mode = _kernel_validation_mode(gate)
|
|
7334
|
+
specs = _kernel_artifact_specs(gate, repo_root, vars_map)
|
|
7335
|
+
issues: list[str] = []
|
|
7336
|
+
artifact_results: list[dict[str, Any]] = []
|
|
7337
|
+
|
|
7338
|
+
if not specs:
|
|
7339
|
+
issues.append("structure_kernel gate requires kernel.artifacts.")
|
|
7340
|
+
|
|
7341
|
+
for spec in specs:
|
|
7342
|
+
path = spec["path"]
|
|
7343
|
+
expected_class = str(spec.get("expected_artifact_class", "")).strip()
|
|
7344
|
+
required = bool(spec.get("required", True))
|
|
7345
|
+
extra_required_fields = [
|
|
7346
|
+
str(x).strip()
|
|
7347
|
+
for x in spec.get("extra_required_fields", [])
|
|
7348
|
+
if isinstance(x, str) and str(x).strip()
|
|
7349
|
+
]
|
|
7350
|
+
|
|
7351
|
+
artifact_issues: list[str] = []
|
|
7352
|
+
missing_fields: list[str] = []
|
|
7353
|
+
exists = path.exists()
|
|
7354
|
+
optional_skipped = False
|
|
7355
|
+
payload: dict[str, Any] = {}
|
|
7356
|
+
|
|
7357
|
+
if not exists:
|
|
7358
|
+
if required:
|
|
7359
|
+
artifact_issues.append("kernel artifact file is missing.")
|
|
7360
|
+
else:
|
|
7361
|
+
optional_skipped = True
|
|
7362
|
+
else:
|
|
7363
|
+
try:
|
|
7364
|
+
loaded_payload = _load_config(path)
|
|
7365
|
+
if isinstance(loaded_payload, dict):
|
|
7366
|
+
payload = loaded_payload
|
|
7367
|
+
else:
|
|
7368
|
+
artifact_issues.append("kernel artifact root must be an object.")
|
|
7369
|
+
except Exception as exc:
|
|
7370
|
+
artifact_issues.append(f"failed to parse kernel artifact: {exc}")
|
|
7371
|
+
|
|
7372
|
+
actual_class = ""
|
|
7373
|
+
if payload:
|
|
7374
|
+
schema_version = str(payload.get("schema_version", "")).strip()
|
|
7375
|
+
if schema_version != "1.0.0":
|
|
7376
|
+
artifact_issues.append("schema_version must be `1.0.0`.")
|
|
7377
|
+
|
|
7378
|
+
actual_class = str(payload.get("artifact_class", "")).strip()
|
|
7379
|
+
if actual_class not in KERNEL_ARTIFACT_CLASS_REQUIREMENTS:
|
|
7380
|
+
artifact_issues.append(
|
|
7381
|
+
f"unsupported artifact_class: {actual_class or '(missing)'}."
|
|
7382
|
+
)
|
|
7383
|
+
|
|
7384
|
+
if expected_class and actual_class and expected_class != actual_class:
|
|
7385
|
+
artifact_issues.append(
|
|
7386
|
+
f"artifact_class mismatch: expected `{expected_class}`, found `{actual_class}`."
|
|
7387
|
+
)
|
|
7388
|
+
|
|
7389
|
+
field_class = actual_class or expected_class
|
|
7390
|
+
required_fields = list(KERNEL_ARTIFACT_CLASS_REQUIREMENTS.get(field_class, []))
|
|
7391
|
+
for field in extra_required_fields:
|
|
7392
|
+
if field not in required_fields:
|
|
7393
|
+
required_fields.append(field)
|
|
7394
|
+
for field in required_fields:
|
|
7395
|
+
if not _kernel_field_present(payload.get(field)):
|
|
7396
|
+
missing_fields.append(field)
|
|
7397
|
+
if missing_fields:
|
|
7398
|
+
artifact_issues.append("missing required fields: " + ", ".join(missing_fields))
|
|
7399
|
+
|
|
7400
|
+
valid = optional_skipped or (exists and not artifact_issues)
|
|
7401
|
+
path_state = _path_for_state(path, repo_root)
|
|
7402
|
+
artifact_results.append(
|
|
7403
|
+
{
|
|
7404
|
+
"path": path_state,
|
|
7405
|
+
"exists": exists,
|
|
7406
|
+
"required": required,
|
|
7407
|
+
"optional_skipped": optional_skipped,
|
|
7408
|
+
"artifact_class": actual_class,
|
|
7409
|
+
"expected_artifact_class": expected_class,
|
|
7410
|
+
"valid": valid,
|
|
7411
|
+
"missing_fields": missing_fields,
|
|
7412
|
+
"issues": artifact_issues,
|
|
7413
|
+
}
|
|
7414
|
+
)
|
|
7415
|
+
issues.extend([f"{path_state}: {issue}" for issue in artifact_issues])
|
|
7416
|
+
|
|
7417
|
+
valid = bool(specs) and all(bool(row.get("valid")) for row in artifact_results)
|
|
7418
|
+
return {
|
|
7419
|
+
"mode": mode,
|
|
7420
|
+
"valid": valid,
|
|
7421
|
+
"artifacts_total": len(artifact_results),
|
|
7422
|
+
"artifacts_valid": sum(1 for row in artifact_results if row.get("valid")),
|
|
7423
|
+
"issues": issues,
|
|
7424
|
+
"artifacts": artifact_results,
|
|
7425
|
+
}
|
|
7426
|
+
|
|
7427
|
+
|
|
7428
|
+
def _kernel_template_payload(artifact_class: str, name_hint: str = "") -> dict[str, Any]:
|
|
7429
|
+
hint = str(name_hint or "").strip() or "this artifact"
|
|
7430
|
+
templates: dict[str, dict[str, Any]] = {
|
|
7431
|
+
"task": {
|
|
7432
|
+
"object": f"describe the task object for {hint}",
|
|
7433
|
+
"goal": "describe the intended outcome",
|
|
7434
|
+
"boundary": ["define what is in scope", "define what is out of scope"],
|
|
7435
|
+
"constraints": ["list the main constraints"],
|
|
7436
|
+
"success_criteria": ["describe how success will be recognized"],
|
|
7437
|
+
},
|
|
7438
|
+
"decision": {
|
|
7439
|
+
"question": f"describe the decision question for {hint}",
|
|
7440
|
+
"chosen_path": "describe the selected path",
|
|
7441
|
+
"rejected_alternatives": ["list key rejected alternatives"],
|
|
7442
|
+
"rationale": "describe why this path was chosen",
|
|
7443
|
+
"consequences": ["list immediate consequences and tradeoffs"],
|
|
7444
|
+
},
|
|
7445
|
+
"hypothesis": {
|
|
7446
|
+
"claim": f"state the hypothesis for {hint}",
|
|
7447
|
+
"boundary": "describe the domain or conditions where the claim is meant to hold",
|
|
7448
|
+
"assumptions": ["list assumptions the claim depends on"],
|
|
7449
|
+
"test_path": "describe how the hypothesis will be tested",
|
|
7450
|
+
"falsifiers": ["list what would falsify the hypothesis"],
|
|
7451
|
+
},
|
|
7452
|
+
"experiment": {
|
|
7453
|
+
"objective": f"describe the experiment objective for {hint}",
|
|
7454
|
+
"method": "describe the method or procedure",
|
|
7455
|
+
"inputs": ["list the required inputs"],
|
|
7456
|
+
"outputs": ["list the expected outputs"],
|
|
7457
|
+
"evidence_expectations": ["list the evidence this experiment should produce"],
|
|
7458
|
+
"interpretation_limits": ["list limits on interpretation"],
|
|
7459
|
+
},
|
|
7460
|
+
"checkpoint": {
|
|
7461
|
+
"completed_unit": f"describe the completed unit for {hint}",
|
|
7462
|
+
"current_state": "describe the current state of work",
|
|
7463
|
+
"risks": ["list immediate risks or unresolved concerns"],
|
|
7464
|
+
"next_handoff_target": "describe what the next operator or agent should pick up",
|
|
7465
|
+
"artifact_refs": ["list the key artifact paths tied to this checkpoint"],
|
|
7466
|
+
},
|
|
7467
|
+
"policy": {
|
|
7468
|
+
"scope": f"describe the policy scope for {hint}",
|
|
7469
|
+
"rule": "state the rule",
|
|
7470
|
+
"rationale": "describe why the rule exists",
|
|
7471
|
+
"invariants": ["list the invariants the rule protects"],
|
|
7472
|
+
"enforcement_surface": "describe where ORP should enforce or check this policy",
|
|
7473
|
+
},
|
|
7474
|
+
"result": {
|
|
7475
|
+
"claim": f"state the resulting claim for {hint}",
|
|
7476
|
+
"evidence_paths": ["list canonical evidence paths"],
|
|
7477
|
+
"status": "describe the result status",
|
|
7478
|
+
"interpretation_limits": ["list the limits on what this result means"],
|
|
7479
|
+
"next_follow_up": "describe the next follow-up action",
|
|
7480
|
+
},
|
|
7481
|
+
}
|
|
7482
|
+
if artifact_class not in templates:
|
|
7483
|
+
raise RuntimeError(f"unsupported kernel artifact class: {artifact_class}")
|
|
7484
|
+
return {
|
|
7485
|
+
"schema_version": "1.0.0",
|
|
7486
|
+
"artifact_class": artifact_class,
|
|
7487
|
+
**templates[artifact_class],
|
|
7488
|
+
}
|
|
7489
|
+
|
|
7490
|
+
|
|
7491
|
+
def _write_structured_payload(path: Path, payload: dict[str, Any], *, format_hint: str = "") -> str:
|
|
7492
|
+
fmt = str(format_hint or "").strip().lower()
|
|
7493
|
+
if not fmt:
|
|
7494
|
+
if path.suffix.lower() == ".json":
|
|
7495
|
+
fmt = "json"
|
|
7496
|
+
elif path.suffix.lower() in {".yaml", ".yml"}:
|
|
7497
|
+
fmt = "yaml"
|
|
7498
|
+
else:
|
|
7499
|
+
fmt = "yaml"
|
|
7500
|
+
|
|
7501
|
+
if fmt == "json":
|
|
7502
|
+
_write_json(path, payload)
|
|
7503
|
+
return "json"
|
|
7504
|
+
|
|
7505
|
+
try:
|
|
7506
|
+
import yaml # type: ignore
|
|
7507
|
+
except Exception as exc:
|
|
7508
|
+
raise RuntimeError("YAML output requires PyYAML. Use --format json or install PyYAML.") from exc
|
|
7509
|
+
_write_text(path, yaml.safe_dump(payload, sort_keys=False, allow_unicode=False))
|
|
7510
|
+
return "yaml"
|
|
7511
|
+
|
|
7512
|
+
|
|
7513
|
+
def cmd_kernel_validate(args: argparse.Namespace) -> int:
|
|
7514
|
+
repo_root = Path(args.repo_root).resolve()
|
|
7515
|
+
artifact_path = _resolve_cli_path(args.artifact, repo_root)
|
|
7516
|
+
fake_gate = {
|
|
7517
|
+
"phase": "structure_kernel",
|
|
7518
|
+
"kernel": {
|
|
7519
|
+
"mode": "hard",
|
|
7520
|
+
"artifacts": [
|
|
7521
|
+
{
|
|
7522
|
+
"path": str(artifact_path),
|
|
7523
|
+
"artifact_class": str(getattr(args, "artifact_class", "") or "").strip(),
|
|
7524
|
+
"extra_required_fields": list(getattr(args, "required_field", []) or []),
|
|
7525
|
+
}
|
|
7526
|
+
],
|
|
7527
|
+
},
|
|
7528
|
+
}
|
|
7529
|
+
validation = _validate_kernel_gate(fake_gate, repo_root, {})
|
|
7530
|
+
if validation is None:
|
|
7531
|
+
raise RuntimeError("failed to construct kernel validation request")
|
|
7532
|
+
|
|
7533
|
+
artifact_result = validation.get("artifacts", [None])[0] if isinstance(validation.get("artifacts"), list) else None
|
|
7534
|
+
result = {
|
|
7535
|
+
"ok": bool(validation.get("valid")),
|
|
7536
|
+
"artifact": _path_for_state(artifact_path, repo_root),
|
|
7537
|
+
"expected_artifact_class": str(getattr(args, "artifact_class", "") or "").strip(),
|
|
7538
|
+
"validation": validation,
|
|
7539
|
+
"artifact_result": artifact_result,
|
|
7540
|
+
}
|
|
7541
|
+
if args.json_output:
|
|
7542
|
+
_print_json(result)
|
|
7543
|
+
else:
|
|
7544
|
+
print(f"artifact={result['artifact']}")
|
|
7545
|
+
print(f"valid={'true' if result['ok'] else 'false'}")
|
|
7546
|
+
if artifact_result:
|
|
7547
|
+
print(f"artifact_class={artifact_result.get('artifact_class', '')}")
|
|
7548
|
+
missing = artifact_result.get("missing_fields", [])
|
|
7549
|
+
print("missing_fields=" + (",".join(missing) if missing else "(none)"))
|
|
7550
|
+
for issue in validation.get("issues", []):
|
|
7551
|
+
print(f"issue={issue}")
|
|
7552
|
+
return 0 if result["ok"] else 1
|
|
7553
|
+
|
|
7554
|
+
|
|
7555
|
+
def cmd_kernel_scaffold(args: argparse.Namespace) -> int:
|
|
7556
|
+
repo_root = Path(args.repo_root).resolve()
|
|
7557
|
+
out_path = _resolve_cli_path(args.out, repo_root)
|
|
7558
|
+
if out_path.exists() and not args.force:
|
|
7559
|
+
raise RuntimeError(
|
|
7560
|
+
f"kernel artifact already exists: {_path_for_state(out_path, repo_root)}. Use --force to overwrite."
|
|
7561
|
+
)
|
|
7562
|
+
payload = _kernel_template_payload(args.artifact_class, args.name or repo_root.name)
|
|
7563
|
+
emitted_format = _write_structured_payload(out_path, payload, format_hint=args.format)
|
|
7564
|
+
result = {
|
|
7565
|
+
"ok": True,
|
|
7566
|
+
"artifact_class": args.artifact_class,
|
|
7567
|
+
"path": _path_for_state(out_path, repo_root),
|
|
7568
|
+
"format": emitted_format,
|
|
7569
|
+
}
|
|
7570
|
+
if args.json_output:
|
|
7571
|
+
_print_json(result)
|
|
7572
|
+
else:
|
|
7573
|
+
print(f"path={result['path']}")
|
|
7574
|
+
print(f"artifact_class={result['artifact_class']}")
|
|
7575
|
+
print(f"format={result['format']}")
|
|
7576
|
+
return 0
|
|
7577
|
+
|
|
7578
|
+
|
|
7181
7579
|
def cmd_gate_run(args: argparse.Namespace) -> int:
|
|
7182
7580
|
repo_root = Path(args.repo_root).resolve()
|
|
7183
7581
|
_ensure_dirs(repo_root)
|
|
@@ -7284,7 +7682,19 @@ def cmd_gate_run(args: argparse.Namespace) -> int:
|
|
|
7284
7682
|
if not (repo_root / rel).exists():
|
|
7285
7683
|
file_issues.append(f"required file missing: {rel}")
|
|
7286
7684
|
|
|
7287
|
-
|
|
7685
|
+
kernel_validation = _validate_kernel_gate(gate, repo_root, vars_map)
|
|
7686
|
+
kernel_ok = True
|
|
7687
|
+
if kernel_validation is not None and kernel_validation.get("mode") == "hard":
|
|
7688
|
+
kernel_ok = bool(kernel_validation.get("valid"))
|
|
7689
|
+
|
|
7690
|
+
passed = (
|
|
7691
|
+
ok_exit
|
|
7692
|
+
and ok_out
|
|
7693
|
+
and ok_err
|
|
7694
|
+
and (len(file_issues) == 0)
|
|
7695
|
+
and (exec_status == "ok")
|
|
7696
|
+
and kernel_ok
|
|
7697
|
+
)
|
|
7288
7698
|
status = "pass" if passed else "fail"
|
|
7289
7699
|
issues = []
|
|
7290
7700
|
if not ok_exit:
|
|
@@ -7294,6 +7704,14 @@ def cmd_gate_run(args: argparse.Namespace) -> int:
|
|
|
7294
7704
|
issues.extend(file_issues)
|
|
7295
7705
|
if exec_status != "ok":
|
|
7296
7706
|
issues.append(exec_status)
|
|
7707
|
+
if kernel_validation is not None and not kernel_validation.get("valid"):
|
|
7708
|
+
issues.extend(
|
|
7709
|
+
[
|
|
7710
|
+
f"kernel validation: {issue}"
|
|
7711
|
+
for issue in kernel_validation.get("issues", [])
|
|
7712
|
+
if isinstance(issue, str)
|
|
7713
|
+
]
|
|
7714
|
+
)
|
|
7297
7715
|
|
|
7298
7716
|
evidence_paths = _resolve_config_paths(
|
|
7299
7717
|
evidence_cfg.get("paths", []) if isinstance(evidence_cfg, dict) else [],
|
|
@@ -7311,22 +7729,24 @@ def cmd_gate_run(args: argparse.Namespace) -> int:
|
|
|
7311
7729
|
else ""
|
|
7312
7730
|
)
|
|
7313
7731
|
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7732
|
+
result_row = {
|
|
7733
|
+
"gate_id": gate_id,
|
|
7734
|
+
"phase": gate.get("phase", "custom"),
|
|
7735
|
+
"command": cmd,
|
|
7736
|
+
"status": status,
|
|
7737
|
+
"exit_code": rc,
|
|
7738
|
+
"duration_ms": dur_ms,
|
|
7739
|
+
"stdout_path": str(stdout_path.relative_to(repo_root)),
|
|
7740
|
+
"stderr_path": str(stderr_path.relative_to(repo_root)),
|
|
7741
|
+
"rule_issues": issues,
|
|
7742
|
+
"evidence_paths": evidence_paths,
|
|
7743
|
+
"evidence_status": evidence_status,
|
|
7744
|
+
"evidence_note": evidence_note,
|
|
7745
|
+
}
|
|
7746
|
+
if kernel_validation is not None:
|
|
7747
|
+
result_row["kernel_validation"] = kernel_validation
|
|
7748
|
+
|
|
7749
|
+
run_results.append(result_row)
|
|
7330
7750
|
|
|
7331
7751
|
if not passed:
|
|
7332
7752
|
on_fail = str(gate.get("on_fail", "stop"))
|
|
@@ -7704,6 +8124,7 @@ def cmd_about(args: argparse.Namespace) -> int:
|
|
|
7704
8124
|
print(f"artifact.packet_json={payload['artifacts']['packet_json']}")
|
|
7705
8125
|
print(f"schema.config={payload['schemas']['config']}")
|
|
7706
8126
|
print(f"schema.packet={payload['schemas']['packet']}")
|
|
8127
|
+
print(f"schema.kernel={payload['schemas']['kernel']}")
|
|
7707
8128
|
print(f"schema.profile_pack={payload['schemas']['profile_pack']}")
|
|
7708
8129
|
print(f"packs.count={len(payload['packs'])}")
|
|
7709
8130
|
for pack in payload["packs"]:
|
|
@@ -12181,6 +12602,62 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
12181
12602
|
add_json_flag(s_cleanup)
|
|
12182
12603
|
s_cleanup.set_defaults(func=cmd_cleanup, json_output=False)
|
|
12183
12604
|
|
|
12605
|
+
s_kernel = sub.add_parser("kernel", help="Reasoning-kernel artifact operations")
|
|
12606
|
+
kernel_sub = s_kernel.add_subparsers(dest="kernel_cmd", required=True)
|
|
12607
|
+
|
|
12608
|
+
s_kernel_validate = kernel_sub.add_parser(
|
|
12609
|
+
"validate",
|
|
12610
|
+
help="Validate one kernel artifact against typed ORP structure rules",
|
|
12611
|
+
)
|
|
12612
|
+
s_kernel_validate.add_argument("artifact", help="Kernel artifact path (.yml, .yaml, or .json)")
|
|
12613
|
+
s_kernel_validate.add_argument(
|
|
12614
|
+
"--artifact-class",
|
|
12615
|
+
default="",
|
|
12616
|
+
help="Optional expected artifact class override",
|
|
12617
|
+
)
|
|
12618
|
+
s_kernel_validate.add_argument(
|
|
12619
|
+
"--required-field",
|
|
12620
|
+
action="append",
|
|
12621
|
+
default=[],
|
|
12622
|
+
help="Extra required field to enforce during validation (repeatable)",
|
|
12623
|
+
)
|
|
12624
|
+
add_json_flag(s_kernel_validate)
|
|
12625
|
+
s_kernel_validate.set_defaults(func=cmd_kernel_validate, json_output=False)
|
|
12626
|
+
|
|
12627
|
+
s_kernel_scaffold = kernel_sub.add_parser(
|
|
12628
|
+
"scaffold",
|
|
12629
|
+
help="Write a starter kernel artifact template for a typed ORP artifact class",
|
|
12630
|
+
)
|
|
12631
|
+
s_kernel_scaffold.add_argument(
|
|
12632
|
+
"--artifact-class",
|
|
12633
|
+
required=True,
|
|
12634
|
+
choices=sorted(KERNEL_ARTIFACT_CLASS_REQUIREMENTS.keys()),
|
|
12635
|
+
help="Typed kernel artifact class to scaffold",
|
|
12636
|
+
)
|
|
12637
|
+
s_kernel_scaffold.add_argument(
|
|
12638
|
+
"--out",
|
|
12639
|
+
required=True,
|
|
12640
|
+
help="Output artifact path (.yml/.yaml or .json)",
|
|
12641
|
+
)
|
|
12642
|
+
s_kernel_scaffold.add_argument(
|
|
12643
|
+
"--name",
|
|
12644
|
+
default="",
|
|
12645
|
+
help="Optional name hint used in scaffold placeholders",
|
|
12646
|
+
)
|
|
12647
|
+
s_kernel_scaffold.add_argument(
|
|
12648
|
+
"--format",
|
|
12649
|
+
default="",
|
|
12650
|
+
choices=["", "yaml", "json"],
|
|
12651
|
+
help="Optional explicit output format (default: infer from file extension)",
|
|
12652
|
+
)
|
|
12653
|
+
s_kernel_scaffold.add_argument(
|
|
12654
|
+
"--force",
|
|
12655
|
+
action="store_true",
|
|
12656
|
+
help="Overwrite an existing artifact at --out",
|
|
12657
|
+
)
|
|
12658
|
+
add_json_flag(s_kernel_scaffold)
|
|
12659
|
+
s_kernel_scaffold.set_defaults(func=cmd_kernel_scaffold, json_output=False)
|
|
12660
|
+
|
|
12184
12661
|
s_gate = sub.add_parser("gate", help="Gate operations")
|
|
12185
12662
|
gate_sub = s_gate.add_subparsers(dest="gate_cmd", required=True)
|
|
12186
12663
|
s_run = gate_sub.add_parser("run", help="Run configured gates for a profile")
|