From a048510ab70787af5b27b2c812e2a6860febcc50 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 21:56:47 +0300 Subject: [PATCH 01/15] Add pinned native SpecGraph runtime for product workspaces Spec-ID: SG-SPEC-0007 Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Package a fixed source snapshot and route existing product commands without changing supervisor authority. --- CONTRIBUTING.md | 6 + README.md | 20 ++ .../Documentation.docc/NativeRuntime.md | 27 +++ .../SpecGraph/Documentation.docc/SpecGraph.md | 1 + .../0231_native_specgraph_runtime.md | 17 ++ .../0231_native_specgraph_runtime.intent.yaml | 18 ++ .../0231_native_specgraph_runtime.md | 55 ++++++ docs/reviews/0231_native_runtime/intake.json | 9 + .../0231_native_runtime/source-notes.json | 37 ++++ docs/reviews/0231_native_runtime/specdraft.md | 11 ++ ...aph-native-runtime-20261027--79ab20f1.json | 81 ++++++++ .../nodes/proposal--0231--132f1b8b.json | 70 +++++++ tests/native_runtime_smoke.py | 178 ++++++++++++++++++ tests/test_native_runtime.py | 53 ++++++ tools/docc_sync_contract.json | 17 ++ tools/native_runtime.py | 169 +++++++++++++++++ tools/proposal_promotion_registry.json | 13 ++ tools/proposal_runtime_registry.json | 44 +++++ 18 files changed, 826 insertions(+) create mode 100644 Sources/SpecGraph/Documentation.docc/NativeRuntime.md create mode 100644 docs/archive/proposal_sources/0231_native_specgraph_runtime.md create mode 100644 docs/proposals/0231_native_specgraph_runtime.intent.yaml create mode 100644 docs/proposals/0231_native_specgraph_runtime.md create mode 100644 docs/reviews/0231_native_runtime/intake.json create mode 100644 docs/reviews/0231_native_runtime/source-notes.json create mode 100644 docs/reviews/0231_native_runtime/specdraft.md create mode 100644 intent_layer/nodes/user_intent--specgraph-native-runtime-20261027--79ab20f1.json create mode 100644 proposal_lane/nodes/proposal--0231--132f1b8b.json create mode 100644 tests/native_runtime_smoke.py create mode 100644 tests/test_native_runtime.py create mode 100644 tools/native_runtime.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 783100a6..107c083b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,12 @@ Use [AGENTS.md](AGENTS.md) for executable agent rules. Use this file for the hum ## Distribution verification +Proposal 0231 installs a full pinned source snapshot alongside a separate Python +environment. Exercise the installed launcher against distinct external workspace +roots; source-only tests cannot prove policy resources or tool paths are usable. +Record the selected commit and distinguish deterministic probes from real +executor runs and canonical approval. + Distribution changes must exercise the built artifact, not just imports from the source checkout. For portable SpecDraft (proposal 0230), build a wheel through its source distribution, install it into a separate pipx environment, then run diff --git a/README.md b/README.md index 9eef89a4..49febc25 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,26 @@ It turns product intent into a structured, governed, and execution-ready graph t SpecGraph makes it possible to move from intent to implementation through specifications that remain understandable to humans, verifiable by machines, and traceable across the full software lifecycle. +## Native SpecGraph runtime + +Proposal 0231 adds a separately installed snapshot of the full tracked SpecGraph +source and a Python environment. It uses a full Git SHA and a new destination: + +```sh +python3 tools/native_runtime.py install --source https://github.com/0al-spec/SpecGraph.git \ + --revision --destination +/specgraph-runtime --workspace \ + init -- --project-id +/specgraph-runtime --workspace \ + supervisor -- --target-spec --dry-run +``` + +The selected revision must include the installer. Python 3.10+, Git and network +access for Python dependencies are required. Existing project history, agent +executor setup and workspace initialization are separate from installation. +See [proposal 0231](docs/proposals/0231_native_specgraph_runtime.md) for +supported operations and limitations. + ## Installed SpecDraft Proposal 0230 provides a separate Python runtime for notes and local preview. diff --git a/Sources/SpecGraph/Documentation.docc/NativeRuntime.md b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md new file mode 100644 index 00000000..561def55 --- /dev/null +++ b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md @@ -0,0 +1,27 @@ +# Native SpecGraph runtime + +Proposal 0231 describes a pinned source snapshot for an agent running SpecGraph +from independent product workspaces. The installer ships tracked tools and +policy resources without the engine Git history; it creates a separate Python +environment and a launcher. Python 3.10+, Git and access to dependencies are +prerequisites. The local source commit is recorded in `runtime.json`. + +`tools/native_runtime.py install` takes an explicit Git source, full revision +and new destination. It never overwrites an existing destination. Failure leaves +an incomplete installation without the `runtime.json` ready marker. + +The launcher accepts `--workspace` and `init`, `next-move`, `supervisor` or +`draft`. `init` runs the existing initializer from the installed source root; +other operations run in the selected product workspace. Project-owned Git, +notes, specs and runs are not installed data. Each workspace must be explicitly +selected, and an existing workspace or Git history is not overwritten by setup. + +Current supervisor refinement requires its configured Codex executor. ASP is +future work, and the invoking agent is not automatically the child executor. +The existing initializer's `specs/nodes` and `.worktrees` limitations persist. +Unix platforms are the target; relocation, offline installation, Docker and a +full cross-repository evidence binding are not claimed by this slice. + +Focused tests cover snapshot safety and installer refusal. An installed-runtime +probe exercises a real snapshot and separate project roots; no canonical +acceptance or live provider inference follows from that observation. diff --git a/Sources/SpecGraph/Documentation.docc/SpecGraph.md b/Sources/SpecGraph/Documentation.docc/SpecGraph.md index a8bbd4df..6b00448f 100644 --- a/Sources/SpecGraph/Documentation.docc/SpecGraph.md +++ b/Sources/SpecGraph/Documentation.docc/SpecGraph.md @@ -110,6 +110,7 @@ The canonical source files remain in the repository: - - - +- ### Proposed composition comparison diff --git a/docs/archive/proposal_sources/0231_native_specgraph_runtime.md b/docs/archive/proposal_sources/0231_native_specgraph_runtime.md new file mode 100644 index 00000000..78095870 --- /dev/null +++ b/docs/archive/proposal_sources/0231_native_specgraph_runtime.md @@ -0,0 +1,17 @@ +# Source: native SpecGraph runtime + +The operator described the intended product explicitly: + +> Для executor у меня есть ASP - agent surface protocol, но он ещё в разработке. Поэтому меня бы устроил такой вариант, где каким-то образом (скриптом?) создается копия стабильного SpecGraph со всеми инструмениами, разворачивается окружение Python/git/etc и далее агент-разработчик работает с супервизором и тп над конкретным проектом… + +The operator subsequently requested: «Да, начинай по канону правок». +The SpecDraft note `specgraph_native_runtime` retains this quote at revision +`1b44588d2b6640de86bd56f219fd4735`; the original message ID and timestamp were +not independently attested. The supervisor intake helper created a pre-canonical +captured intent and an under-review proposal-lane link. This is not approval of +canonical graph publication or a claim of an executor/model run. + +Proposal 0227 describes the product workspace boundary; 0230 packages SpecDraft. +This proposal packages a pinned checkout snapshot of the existing engine and +does not introduce ASP or change either proposal's authority. Git, an appropriate +Python interpreter, and the executor remain externally selected prerequisites. diff --git a/docs/proposals/0231_native_specgraph_runtime.intent.yaml b/docs/proposals/0231_native_specgraph_runtime.intent.yaml new file mode 100644 index 00000000..727fb0a3 --- /dev/null +++ b/docs/proposals/0231_native_specgraph_runtime.intent.yaml @@ -0,0 +1,18 @@ +id: SG-RFC-0231 +kind: proposal +status: draft +proposal_ref: docs/proposals/0231_native_specgraph_runtime.md +provenance: + authored_by: agent + authority: proposal_draft + source_ref: docs/archive/proposal_sources/0231_native_specgraph_runtime.md +atoms: + intents: + - id: SG-NATIVE-SNAPSHOT + statement: An installation retains a full tracked SpecGraph snapshot identified by the exact source commit without retaining its Git history. + - id: SG-NATIVE-ENVIRONMENT + statement: A separately created Python environment installs the snapshot dependencies and a launcher without overwriting an existing installation. + - id: SG-NATIVE-WORKSPACES + statement: One installed runtime runs existing product commands against explicitly selected independent workspace roots. + - id: SG-NATIVE-AUTHORITY + statement: The installed supervisor retains existing executor and review authority without pretending to implement ASP or canonical acceptance. diff --git a/docs/proposals/0231_native_specgraph_runtime.md b/docs/proposals/0231_native_specgraph_runtime.md new file mode 100644 index 00000000..8030f008 --- /dev/null +++ b/docs/proposals/0231_native_specgraph_runtime.md @@ -0,0 +1,55 @@ +# Native SpecGraph runtime + +## Status and source + +Synchronous runtime slice for review. The [source](../archive/proposal_sources/0231_native_specgraph_runtime.md), [attributed note](../reviews/0231_native_runtime/source-notes.json), [intake](../reviews/0231_native_runtime/intake.json) and [draft obligations](0231_native_specgraph_runtime.intent.yaml) precede canonical acceptance. + +An agent needs a stable installed snapshot while the SpecGraph developer +checkout evolves independently. The existing Product Workspace model remains +the project boundary; source snapshots and Python environments are versioned +separately from project-owned graphs, runs and Git history. + +## Bounded contract + +`tools/native_runtime.py install --source --revision +--destination ` fetches that exact commit into temporary bare Git, +archives tracked files without history, creates a separate Python environment, +installs dependencies, and produces a launcher and a completed receipt. It fails +if the destination exists, preserves incomplete installations on error, and +never adds projects or their data to the installed runtime. Python >=3.10, Git, +network access to dependencies and Unix venv layout are prerequisites. + +The installed `specgraph-runtime --workspace init -- --project-id ` +invokes the existing product initializer from the installed engine root. +`next-move`, `supervisor` and `draft` forward arguments with the selected +workspace as current directory. This retains existing CLI output and authority +semantics. Existing source facades remain available. A workspace with existing +Git history keeps it; the installer does not create or overwrite project Git. + +The installed snapshot contains tracked source files, not `.git`, uncommitted +changes, credentials, existing worktrees, local notes or generated artifacts. +The launcher is colocated with its Python environment and is not relocatable; +reinstall at a new path. One installation can serve separate workspace roots. +Current supervisor refinement launches a configured `codex exec` child; ASP is +a future integration. An agent invoking the launcher is not itself the child +executor. No package-index release, native Windows support, offline installation, +automatic upgrades, broad integration bundle or Docker image is claimed. + +## Verification and limits + +Focused tests verify conflict preservation, unsafe archive rejection, full SHA +and incomplete installation rejection. A real isolated install and product +workspace probe must bind to the installed commit and report exactly what ran. +The existing initializer does not create `specs/nodes`, and after supervisor +creates `.worktrees` a repeated initializer may report it as unexpected; these +pre-existing limitations are not silently altered by this distribution slice. +Real provider calls, canonical publication, cross-repository code trace and +hosted evidence admission are outside the technical smoke's claims. + +## Pipeline + +Captured user intent → proposal lane → draft obligations → implementation → +tests/observations. SG-SPEC-0007 constrains the attribution boundary; references +are governance context, not a canonical Requirement binding. CLI forwarding, +archive copying and environment creation are I/O mechanics; they do not create +new domain Specifications or expand supervisor authority. diff --git a/docs/reviews/0231_native_runtime/intake.json b/docs/reviews/0231_native_runtime/intake.json new file mode 100644 index 00000000..33dc9155 --- /dev/null +++ b/docs/reviews/0231_native_runtime/intake.json @@ -0,0 +1,9 @@ +{ + "artifact_kind": "native_runtime_intake", + "schema_version": 1, + "source_notes_revision": "1b44588d2b6640de86bd56f219fd4735", + "source_node": "specgraph_native_runtime", + "source_notes_ref": "docs/reviews/0231_native_runtime/source-notes.json", + "user_instruction": "Да, начинай по канону правок", + "canonical": false +} diff --git a/docs/reviews/0231_native_runtime/source-notes.json b/docs/reviews/0231_native_runtime/source-notes.json new file mode 100644 index 00000000..61725205 --- /dev/null +++ b/docs/reviews/0231_native_runtime/source-notes.json @@ -0,0 +1,37 @@ +{ + "artifact_kind": "conversation_notes", + "schema_version": 1, + "revision": "1b44588d2b6640de86bd56f219fd4735", + "canonical": false, + "nodes": [ + { + "id": "specgraph_native_runtime", + "label": "Стабильный установленный SpecGraph", + "detail": "Снимок движка и отдельное Python-окружение; агент использует supervisor в workspace проекта. ASP — будущий executor.", + "parent_id": "specdraft_portable_runtime", + "sources": [ + { + "ref": "conversation:2026-10-27/native-runtime-request", + "quote": "Для executor у меня есть ASP - agent surface protocol, но он ещё в разработке. Поэтому меня бы устроил такой вариант, где каким-то образом (скриптом?) создается копия стабильного SpecGraph со всеми инструмениами, разворачивается окружение Python/git/etc и далее агент-разработчик работает с супервизором и тп над конкретным проектом…", + "speaker": "human" + } + ] + } + ], + "links": [], + "reviews": [], + "omitted_nodes": 31, + "history": [ + { + "revision": "1b44588d2b6640de86bd56f219fd4735", + "parent_revision": "2bf2408ebe0148f6a5c6a548f305e67a", + "captured_at": "2026-10-27T18:45:26.818269+00:00", + "source": { + "ref": "conversation:2026-10-27/native-runtime-request", + "quote": "Для executor у меня есть ASP - agent surface protocol, но он ещё в разработке. Поэтому меня бы устроил такой вариант, где каким-то образом (скриптом?) создается копия стабильного SpecGraph со всеми инструмениами, разворачивается окружение Python/git/etc и далее агент-разработчик работает с супервизором и тп над конкретным проектом…", + "speaker": "human" + } + } + ], + "snapshot_path": "conversation_memory/sketches/hypercode-conversation-poc/revisions/1b44588d2b6640de86bd56f219fd4735" +} diff --git a/docs/reviews/0231_native_runtime/specdraft.md b/docs/reviews/0231_native_runtime/specdraft.md new file mode 100644 index 00000000..7aa87f0c --- /dev/null +++ b/docs/reviews/0231_native_runtime/specdraft.md @@ -0,0 +1,11 @@ +# Draft: native SpecGraph runtime + +Review address `req.specgraph.native-runtime`; `review_pending`, `canonical: false`. +Source: [proposal 0231](../../proposals/0231_native_specgraph_runtime.md), +[intake](intake.json) and [source notes](source-notes.json). + +The four draft obligations map to `tools/native_runtime.py::install`, +`extract_snapshot`, and `run`; `tests/test_native_runtime.py` checks the +destructive/error boundaries. The installed-runtime smoke must additionally +exercise two independent workspaces, existing supervisor behavior and receipt +provenance. This draft is not a canonical Requirement or an admission decision. diff --git a/intent_layer/nodes/user_intent--specgraph-native-runtime-20261027--79ab20f1.json b/intent_layer/nodes/user_intent--specgraph-native-runtime-20261027--79ab20f1.json new file mode 100644 index 00000000..68944ca7 --- /dev/null +++ b/intent_layer/nodes/user_intent--specgraph-native-runtime-20261027--79ab20f1.json @@ -0,0 +1,81 @@ +{ + "artifact_kind": "intent_layer_node", + "schema_version": 1, + "title": "User intent: Install a fixed full SpecGraph runtime with Python environment for agents operating separate product workspaces.", + "created_at": "2026-10-27T18:47:54.979173+00:00", + "updated_at": "2026-10-27T18:47:54.979445+00:00", + "policy_reference": { + "artifact_path": "tools/intent_layer_policy.json", + "artifact_sha256": "f9df44dafea0fb1bc0e33007e304b749810ddefb6eda1fe31429390312e30171", + "version": 1 + }, + "vocabulary_reference": { + "artifact_path": "tools/specgraph_vocabulary.json", + "artifact_sha256": "68aa0c73461dbd4ec8db3c2af27eb7b51a601d86005fae54ab58206f5e5f9b9f", + "version": 1 + }, + "intent_repository_presence": { + "tracked_presence": "repository_tracked", + "path_class": "intent_layer_node_path", + "persistence_boundary": "pre_canonical_repository_separate_from_runtime", + "tracked_path": "intent_layer/nodes/user_intent--specgraph-native-runtime-20261027--79ab20f1.json" + }, + "intent_handle": { + "handle_value": "user_intent::specgraph-native-runtime-20261027", + "handle_status": "active" + }, + "intent_layer_kind": "user_intent", + "mediation_state": "captured", + "pre_spec_semantics": { + "policy_reference": { + "artifact_path": "tools/pre_spec_semantics_policy.json", + "artifact_sha256": "6cd49d100c8eae2ac9ea8512c7b7cbad108823064070a5de03487c3941447cb1", + "version": 1 + }, + "phase": "pre_spec", + "semantic_artifact_class": "user_intent", + "status": "captured", + "authority": "external_actor", + "required_axes": [ + "phase", + "status", + "authority", + "confidence", + "provenance", + "context_tags", + "supersedes" + ], + "implemented_axes": { + "phase": "explicit", + "status": "mapped_from_mediation_state", + "authority": "explicit", + "confidence": "not_yet_materialized", + "provenance": "explicit", + "context_tags": "not_yet_materialized", + "supersedes": "not_yet_materialized" + }, + "adjacent_primary_kinds": [ + "intent_fragment", + "intent_draft", + "intent" + ], + "canonical_boundary": "proposal_lane_or_reviewed_supervisor_candidate_required" + }, + "intent_capture": { + "source_kind": "chat_instruction", + "source_summary": "Install a fixed full SpecGraph runtime with Python environment for agents operating separate product workspaces.", + "selected_node_ref": "SG-SPEC-0007", + "unresolved_questions": [] + }, + "intent_lineage_link": [ + { + "lineage_role": "captured_from", + "source_kind": "runtime_artifact", + "source_reference": "docs/reviews/0231_native_runtime/intake.json" + } + ], + "runtime_bridge": { + "latest_operator_request_handle": "", + "latest_packet_reference": "docs/reviews/0231_native_runtime/intake.json" + } +} \ No newline at end of file diff --git a/proposal_lane/nodes/proposal--0231--132f1b8b.json b/proposal_lane/nodes/proposal--0231--132f1b8b.json new file mode 100644 index 00000000..800290ed --- /dev/null +++ b/proposal_lane/nodes/proposal--0231--132f1b8b.json @@ -0,0 +1,70 @@ +{ + "artifact_kind": "proposal_lane_node", + "schema_version": 1, + "title": "Implementation Handoff Proposal for unscoped target (native_specgraph_runtime)", + "created_at": "2026-10-27T18:47:54.980080+00:00", + "updated_at": "2026-10-27T18:47:54.980087+00:00", + "policy_reference": { + "artifact_path": "tools/proposal_lane_policy.json", + "artifact_sha256": "cf16a8601fee008b754a9852160bb249386fc6182e9500fad95e72adc585eabc", + "version": 1 + }, + "proposal_repository_presence": { + "tracked_presence": "repository_tracked", + "path_class": "proposal_node_path", + "persistence_boundary": "review_visible_repository_separate_from_runtime", + "tracked_path": "proposal_lane/nodes/proposal--0231--132f1b8b.json" + }, + "proposal_handle": { + "handle_value": "proposal::0231", + "handle_status": "active" + }, + "proposal_authority_state": "under_review", + "proposal_target_region": { + "target_kind": "draft_target_area", + "target_reference": "proposal::0231", + "change_scope": "native_specgraph_runtime" + }, + "proposal_lineage_link": [ + { + "lineage_role": "derived_from", + "source_kind": "runtime_artifact", + "source_reference": "docs/proposals/0231_native_specgraph_runtime.md" + }, + { + "lineage_role": "motivated_by", + "source_kind": "intent_layer_node", + "source_reference": "user_intent::specgraph-native-runtime-20261027" + } + ], + "proposal_artifact_link": [ + { + "artifact_role": "supporting_runtime_artifact", + "reference_kind": "runtime_artifact", + "reference_value": "docs/proposals/0231_native_specgraph_runtime.md" + } + ], + "proposal_payload": { + "proposal_type": "implementation_handoff_proposal", + "trigger": "captured_user_intent", + "recommended_action": "review_installed_runtime_evidence", + "signal": "native_specgraph_runtime", + "source_work_item_type": "user_intent", + "execution_policy": "emit_proposal", + "transition_profile": "", + "packet_type": "", + "target_artifact_class": "reviewable_proposal", + "user_intent_handle": "user_intent::specgraph-native-runtime-20261027", + "operator_request_handle": "" + }, + "runtime_bridge": { + "proposal_queue_status": "review_pending", + "supporting_run_ids": [], + "occurrence_count": null, + "threshold": null, + "proposal_artifact_path": "docs/proposals/0231_native_specgraph_runtime.md", + "applied_run_id": "", + "applied_at": "", + "operator_request_handle": "" + } +} \ No newline at end of file diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py new file mode 100644 index 00000000..eafcf5fd --- /dev/null +++ b/tests/native_runtime_smoke.py @@ -0,0 +1,178 @@ +"""Exercise a real installed snapshot from two isolated synthetic workspaces.""" + +import argparse +import hashlib +import json +import os +import subprocess +import sys +from pathlib import Path + +import yaml + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", required=True, type=Path) + parser.add_argument("--revision", required=True) + parser.add_argument("--root", required=True, type=Path) + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args() + root = args.root.resolve() + root.mkdir(parents=True, exist_ok=True) + install = root / "runtime with spaces" + project_a = root / "project-a" + project_b = root / "project-b" + for project in (project_a, project_b): + project.mkdir() + environment = os.environ.copy() + environment.pop("PYTHONPATH", None) + environment.pop("PYTHONHOME", None) + environment["PYTHONDONTWRITEBYTECODE"] = "1" + + def call(command, *, cwd=root): + result = subprocess.run(command, cwd=cwd, env=environment, text=True, capture_output=True) + if result.returncode: + raise AssertionError((command, result.returncode, result.stdout, result.stderr)) + return result.stdout + + setup = call( + [ + sys.executable, + str(args.repo / "tools/native_runtime.py"), + "install", + "--source", + str(args.repo), + "--revision", + args.revision, + "--destination", + str(install), + ] + ) + receipt = json.loads(setup) + assert receipt["source_commit"] == args.revision + assert not (install / "source/.git").exists() + launcher = install / "specgraph-runtime" + fixture = json.loads( + (install / "source/docs/evidence/0227_local_workspace_path_reuse.json").read_text() + )["fixture"] + for index, project in enumerate((project_a, project_b), 1): + workspace = project / "knowledge" + call( + [ + str(launcher), + "--workspace", + str(workspace), + "init", + "--", + "--project-id", + f"native-{index}", + "--root-intent", + f"Synthetic native project {index}", + "--output-mode", + "summary", + ] + ) + nodes = workspace / "specs/nodes" + nodes.mkdir(parents=True) + (nodes / f"{fixture['id']}.yaml").write_text(yaml.safe_dump(fixture, sort_keys=False)) + call(["git", "init", "--quiet", str(workspace)]) + call( + [ + "git", + "-C", + str(workspace), + "add", + "specgraph.project.yaml", + "specs", + ".specgraph/root_intent.md", + ] + ) + call( + [ + "git", + "-C", + str(workspace), + "-c", + "user.name=Fixture", + "-c", + "user.email=fixture@example.invalid", + "commit", + "--quiet", + "-m", + "Initial synthetic graph", + ] + ) + call( + [ + str(launcher), + "--workspace", + str(workspace), + "next-move", + "--", + "--target-spec", + fixture["id"], + ] + ) + call( + [ + str(launcher), + "--workspace", + str(workspace), + "supervisor", + "--", + "--target-spec", + fixture["id"], + "--dry-run", + "--output-mode", + "summary", + ] + ) + assert (workspace / "runs/product_workspace_next_moves.json").is_file() + notes = json.loads( + call( + [ + str(launcher), + "--workspace", + str(workspace), + "draft", + "--", + "notes", + "read_notes", + "--workspace", + str(workspace / "sketch"), + ] + ) + ) + assert notes["canonical"] is False and notes["revision"] is None + assert (project_a / "knowledge/specgraph.project.yaml").read_text() != ( + project_b / "knowledge/specgraph.project.yaml" + ).read_text() + report = { + "artifact_kind": "native_runtime_observation", + "schema_version": 1, + "source_commit": args.revision, + "status": "passed", + "synthetic_workspaces": 2, + "checks": [ + "exact_snapshot_without_git", + "isolated_venv", + "path_with_spaces", + "init", + "product_advisor", + "targeted_supervisor_dry_run", + "specdraft_read", + ], + "executor": "not_exercised", + "canonical_publication": "not_evaluated", + "installer_sha256": hashlib.sha256( + (install / "source/tools/native_runtime.py").read_bytes() + ).hexdigest(), + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n") + print(json.dumps(report, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/tests/test_native_runtime.py b/tests/test_native_runtime.py new file mode 100644 index 00000000..d07521fe --- /dev/null +++ b/tests/test_native_runtime.py @@ -0,0 +1,53 @@ +"""Safety and path boundaries of the native runtime installer.""" + +import importlib.util +import io +import json +import tarfile +from pathlib import Path + +import pytest + +SPEC = importlib.util.spec_from_file_location( + "native_runtime", Path(__file__).resolve().parents[1] / "tools" / "native_runtime.py" +) +runtime = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(runtime) + + +def test_existing_installation_is_not_overwritten(tmp_path): + destination = tmp_path / "existing" + destination.mkdir() + marker = destination / "data" + marker.write_text("keep") + with pytest.raises(ValueError, match="already exists"): + runtime.install("unused", "a" * 40, destination) + assert marker.read_text() == "keep" + + +def test_archive_rejects_traversal_before_extracting(tmp_path): + archive = tmp_path / "bad.tar" + with tarfile.open(archive, "w") as bundle: + entry = tarfile.TarInfo("../escaped") + entry.size = 1 + bundle.addfile(entry, io.BytesIO(b"x")) + destination = tmp_path / "snapshot" + destination.mkdir() + with pytest.raises(ValueError, match="Unsupported archive"): + runtime.extract_snapshot(archive, destination) + assert not (tmp_path / "escaped").exists() + + +def test_incomplete_installation_is_not_usable(tmp_path): + root = tmp_path / "runtime" + root.mkdir() + (root / "runtime.json").write_text(json.dumps({"status": "installing"})) + with pytest.raises(ValueError, match="not ready"): + runtime.run(root, tmp_path / "workspace", "supervisor", ["--dry-run"]) + + +def test_non_full_sha_rejected_without_creating_destination(tmp_path): + target = tmp_path / "runtime" + with pytest.raises(ValueError, match="full lowercase"): + runtime.install("unused", "deadbeef", target) + assert not target.exists() diff --git a/tools/docc_sync_contract.json b/tools/docc_sync_contract.json index e3298357..8c677b88 100644 --- a/tools/docc_sync_contract.json +++ b/tools/docc_sync_contract.json @@ -1,5 +1,22 @@ { "groups": [ + { + "id": "native-specgraph-runtime", + "description": "Pinned engine snapshot and project-owned workspace runtime boundaries.", + "docs": [ + "docs/proposals/0231_native_specgraph_runtime.md", + "README.md", + "CONTRIBUTING.md" + ], + "docc": [ + "Sources/SpecGraph/Documentation.docc/NativeRuntime.md" + ], + "required_terms": [ + "0231", + "SpecGraph", + "workspace" + ] + }, { "id": "specdraft-distribution", "description": "Independently installed SpecDraft preserves source fa\u00e7ades, consumer data and source-bound verification.", diff --git a/tools/native_runtime.py b/tools/native_runtime.py new file mode 100644 index 00000000..b9b2d139 --- /dev/null +++ b/tools/native_runtime.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Install a pinned SpecGraph source snapshot and run it in a product workspace.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shlex +import shutil +import subprocess +import sys +import tarfile +import tempfile +from pathlib import Path, PurePosixPath + +COMMIT = re.compile(r"^[0-9a-f]{40}$") + + +def execute(arguments: list[str], *, cwd: Path | None = None) -> None: + subprocess.run(arguments, cwd=cwd, stdout=sys.stderr, check=True) + + +def extract_snapshot(archive: Path, destination: Path) -> None: + with tarfile.open(archive, "r") as bundle: + for item in bundle.getmembers(): + path = PurePosixPath(item.name) + if path.is_absolute() or ".." in path.parts or not (item.isfile() or item.isdir()): + raise ValueError(f"Unsupported archive member: {item.name}") + bundle.extractall(destination) # All member paths and types were checked above. + + +def install(source: str, revision: str, destination: Path) -> dict[str, str]: + if not COMMIT.fullmatch(revision): + raise ValueError("Revision must be a full lowercase Git commit SHA") + if shutil.which("git") is None: + raise ValueError("Git is required for installation") + target = destination.expanduser().absolute() + if target.is_symlink() or target.exists(): + raise ValueError("Installation destination already exists") + if not target.parent.is_dir(): + raise ValueError("Installation destination parent must already exist") + target.mkdir() + snapshot = target / "source" + snapshot.mkdir() + # Temporary Git objects are discarded; the installed snapshot has no .git. + # An interrupted or failed installation is left in place for inspection and + # cannot be mistaken for a ready installation without runtime.json. + with tempfile.TemporaryDirectory(prefix="specgraph-install-") as temporary: + temp = Path(temporary) + git = temp / "source.git" + execute(["git", "init", "--bare", "--quiet", str(git)]) + execute(["git", "-C", str(git), "fetch", "--quiet", "--depth=1", "--", source, revision]) + selected = subprocess.check_output( + ["git", "-C", str(git), "rev-parse", "FETCH_HEAD^{commit}"], text=True + ).strip() + if selected != revision: + raise ValueError("Fetched commit does not match requested revision") + archive = temp / "snapshot.tar" + execute(["git", "-C", str(git), "archive", "--format=tar", f"--output={archive}", revision]) + extract_snapshot(archive, snapshot) + if not (snapshot / "tools" / "native_runtime.py").is_file(): + raise ValueError("Selected revision does not contain the native runtime launcher") + execute([sys.executable, "-m", "venv", str(target / "venv")]) + python = target / "venv" / "bin" / "python" + # Build from a temporary copy so package-manager build products never + # modify the runtime snapshot, which is also the supervisor's tool root. + build_source = temp / "build-source" + shutil.copytree(snapshot, build_source) + execute([str(python), "-m", "pip", "install", str(build_source)]) + launcher = target / "specgraph-runtime" + command = [ + str(python), + str(snapshot / "tools" / "native_runtime.py"), + "run", + "--installation", + str(target), + ] + launcher.write_text("#!/bin/sh\nexec " + " ".join(map(shlex.quote, command)) + ' "$@"\n') + launcher.chmod(0o755) + receipt = { + "artifact_kind": "specgraph_native_runtime", + "schema_version": 1, + "status": "installed", + "source_commit": selected, + "python_version": subprocess.check_output( + [str(python), "-c", "import platform; print(platform.python_version())"], text=True + ).strip(), + } + (target / "runtime.json").write_text(json.dumps(receipt, indent=2) + "\n") + return {**receipt, "launcher": str(launcher)} + + +def run(installation: Path, workspace: Path, operation: str, arguments: list[str]) -> int: + root = installation.expanduser().resolve(strict=True) + receipt = json.loads((root / "runtime.json").read_text()) + if ( + receipt.get("artifact_kind") != "specgraph_native_runtime" + or receipt.get("status") != "installed" + ): + raise ValueError("Installation is not ready") + project = workspace.expanduser().resolve() + if project == root or root in project.parents: + raise ValueError("Workspace must be outside the installed runtime") + source = root / "source" + python = root / "venv" / "bin" / "python" + if operation == "init": + command = [ + str(python), + str(source / "tools" / "supervisor.py"), + "--init-product-workspace", + "--workspace-root", + str(project), + *arguments, + ] + cwd = source + elif operation == "next-move": + command = [ + str(python), + str(source / "tools" / "product_workspace_next_moves.py"), + "--workspace-root", + str(project), + *arguments, + ] + cwd = project + elif operation == "supervisor": + command = [str(python), str(source / "tools" / "supervisor.py"), *arguments] + cwd = project + else: + command = [str(python), "-m", "specgraph", *arguments] + cwd = project + if operation != "init" and not project.is_dir(): + raise ValueError("Workspace must be an existing directory") + environment = os.environ.copy() + environment["PYTHONDONTWRITEBYTECODE"] = "1" + environment.pop("PYTHONPATH", None) + environment.pop("PYTHONHOME", None) + return subprocess.run(command, cwd=cwd, env=environment, check=False).returncode + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + setup = commands.add_parser("install") + setup.add_argument("--source", required=True, help="Git repository path or URL") + setup.add_argument("--revision", required=True, help="Full commit SHA") + setup.add_argument("--destination", required=True, type=Path) + launch = commands.add_parser("run") + launch.add_argument("--installation", required=True, type=Path) + launch.add_argument("--workspace", required=True, type=Path) + launch.add_argument("operation", choices=("init", "next-move", "supervisor", "draft")) + args, extra = parser.parse_known_args(argv) + if extra and extra[0] == "--": + extra.pop(0) + try: + if args.command == "install": + if extra: + parser.error("install does not accept extra arguments") + print(json.dumps(install(args.source, args.revision, args.destination), indent=2)) + return 0 + return run(args.installation, args.workspace, args.operation, extra) + except (OSError, ValueError, subprocess.CalledProcessError) as error: + print(json.dumps({"error": str(error)}), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/proposal_promotion_registry.json b/tools/proposal_promotion_registry.json index 79710fab..632c0c10 100644 --- a/tools/proposal_promotion_registry.json +++ b/tools/proposal_promotion_registry.json @@ -2975,5 +2975,18 @@ "required_provenance_links": [ "source_draft_ref" ] + }, + { + "proposal_id": "0231", + "source_artifact_class": "working_draft", + "source_refs": [ + "docs/archive/proposal_sources/0231_native_specgraph_runtime.md" + ], + "motivating_concern": "Use a stable complete SpecGraph snapshot for independent projects while the developer checkout changes.", + "normalized_title": "Native SpecGraph runtime", + "bounded_scope": "Native source-snapshot installer, isolated Python environment and existing product-workspace command forwarding with explicit provenance. ASP, Docker, full product evidence binding and canonical publication remain separate.", + "required_provenance_links": [ + "source_draft_ref" + ] } ] diff --git a/tools/proposal_runtime_registry.json b/tools/proposal_runtime_registry.json index e5e4a4cd..cc4fbc2a 100644 --- a/tools/proposal_runtime_registry.json +++ b/tools/proposal_runtime_registry.json @@ -13500,5 +13500,49 @@ } ], "notes": "Bounded portable notes/preview distribution with matched resources and skill; see source-bound installed verification. Full supervisor, Docker, native Windows, browser rendering and canonical adoption remain outside this slice." + }, + { + "proposal_id": "0231", + "posture": "synchronous_runtime_slice", + "runtime_surfaces": [ + "docs/proposals/0231_native_specgraph_runtime.md", + "docs/proposals/0231_native_specgraph_runtime.intent.yaml", + "docs/archive/proposal_sources/0231_native_specgraph_runtime.md", + "docs/reviews/0231_native_runtime/intake.json", + "docs/reviews/0231_native_runtime/source-notes.json", + "docs/reviews/0231_native_runtime/specdraft.md", + "docs/reviews/0231_native_runtime/verification.json", + "tools/native_runtime.py", + "tests/test_native_runtime.py", + "tests/native_runtime_smoke.py", + "Sources/SpecGraph/Documentation.docc/NativeRuntime.md" + ], + "runtime_markers": [ + { + "path": "tools/native_runtime.py", + "pattern": "def install(" + }, + { + "path": "tools/native_runtime.py", + "pattern": "def run(" + } + ], + "validation_markers": [ + { + "path": "tests/test_native_runtime.py", + "pattern": "def test_archive_rejects_traversal_before_extracting(" + }, + { + "path": "tests/native_runtime_smoke.py", + "pattern": "def main(" + } + ], + "observation_markers": [ + { + "path": "docs/reviews/0231_native_runtime/verification.json", + "pattern": "\"artifact_kind\": \"native_runtime_observation\"" + } + ], + "notes": "Pinned native engine snapshot and explicit product workspace launcher; local observations and unresolved executor/authority boundaries remain explicit." } ] From 9a30c8474e29954d8f812259d1b2d5f77368c3c4 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:03:42 +0300 Subject: [PATCH 02/15] Exercise installed runtime with isolated workspaces in CI Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Verify snapshot installation, product isolation and supervisor worktree/review behavior using a deterministic executor. --- .github/workflows/native-runtime.yml | 40 ++++++++++++++++++++++++++++ tests/native_runtime_smoke.py | 40 +++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/native-runtime.yml diff --git a/.github/workflows/native-runtime.yml b/.github/workflows/native-runtime.yml new file mode 100644 index 00000000..247938af --- /dev/null +++ b/.github/workflows/native-runtime.yml @@ -0,0 +1,40 @@ +name: Native SpecGraph runtime + +on: + pull_request: + push: + branches: [main, master] + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + native-install: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.14"] + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install test prerequisites + run: python -m pip install -e '.[dev]' + - name: Verify installed runtime in separate workspaces + run: >- + python tests/native_runtime_smoke.py + --repo "$GITHUB_WORKSPACE" + --revision "$GITHUB_SHA" + --root "$RUNNER_TEMP/native-runtime-smoke" + --output "$RUNNER_TEMP/native-runtime-observation.json" + - uses: actions/upload-artifact@v4 + with: + name: native-runtime-observation-${{ matrix.python-version }} + path: ${{ runner.temp }}/native-runtime-observation.json diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py index eafcf5fd..7df70de3 100644 --- a/tests/native_runtime_smoke.py +++ b/tests/native_runtime_smoke.py @@ -148,6 +148,39 @@ def call(command, *, cwd=root): assert (project_a / "knowledge/specgraph.project.yaml").read_text() != ( project_b / "knowledge/specgraph.project.yaml" ).read_text() + # Exercise the installed supervisor's real Git worktree/review path with a + # deterministic executor. No agent, model or hosted inference is invoked. + code = """ +import subprocess, sys +from pathlib import Path +sys.path.insert(0, str(Path(sys.argv[1]) / "tools")) +import supervisor +import yaml +def executor(node, worktree): + path = worktree / "specs/nodes" / (node.id + ".yaml") + value = yaml.safe_load(path.read_text()) + value["prompt"] += " Keep this bounded to the selected project." + supervisor.write_spec_yaml(path, value) + return subprocess.CompletedProcess(["fixture"], 0, "RUN_OUTCOME: done\\nBLOCKER: none\\n", "") +raise SystemExit(supervisor.main( + target_spec="PROBE-SPEC-0001", executor=executor, auto_approve=False, output_mode="summary" +)) +""" + before_a = (project_a / "knowledge/specs/nodes/PROBE-SPEC-0001.yaml").read_bytes() + call( + [str(install / "venv/bin/python"), "-c", code, str(install / "source")], + cwd=project_b / "knowledge", + ) + logs = [] + for path in (project_b / "knowledge/runs").glob("*.json"): + value = json.loads(path.read_text()) + if value.get("spec_id") == "PROBE-SPEC-0001" and "completion_status" in value: + logs.append(value) + assert len(logs) == 1 + assert logs[0]["completion_status"] == "ok" and logs[0]["outcome"] == "done" + assert logs[0]["gate_state"] == "review_pending" and not logs[0]["validation_errors"] + assert logs[0]["isolation_mode"] == "git_worktree" + assert (project_a / "knowledge/specs/nodes/PROBE-SPEC-0001.yaml").read_bytes() == before_a report = { "artifact_kind": "native_runtime_observation", "schema_version": 1, @@ -163,7 +196,12 @@ def call(command, *, cwd=root): "targeted_supervisor_dry_run", "specdraft_read", ], - "executor": "not_exercised", + "executor": "deterministic_fixture_not_a_real_agent", + "refinement": { + "outcome": "done", + "gate_state": "review_pending", + "isolation_mode": "git_worktree", + }, "canonical_publication": "not_evaluated", "installer_sha256": hashlib.sha256( (install / "source/tools/native_runtime.py").read_bytes() From 11e6edffe8af0c9104f3fcfee5c82f3b637e799a Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:04:55 +0300 Subject: [PATCH 03/15] Select supervisor run logs by artifact shape Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Distinguish run records from other JSON artifacts in the installed-runtime probe. --- tests/native_runtime_smoke.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py index 7df70de3..6a7ab5b7 100644 --- a/tests/native_runtime_smoke.py +++ b/tests/native_runtime_smoke.py @@ -174,7 +174,11 @@ def executor(node, worktree): logs = [] for path in (project_b / "knowledge/runs").glob("*.json"): value = json.loads(path.read_text()) - if value.get("spec_id") == "PROBE-SPEC-0001" and "completion_status" in value: + if ( + isinstance(value, dict) + and value.get("spec_id") == "PROBE-SPEC-0001" + and "completion_status" in value + ): logs.append(value) assert len(logs) == 1 assert logs[0]["completion_status"] == "ok" and logs[0]["outcome"] == "done" From b4e5cb9ff0c0aa6138c03a9c9edcfd809d6c5f63 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:07:12 +0300 Subject: [PATCH 04/15] Compare installed engine files with pinned Git objects Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Verify the snapshot contents against the selected source commit. --- tests/native_runtime_smoke.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py index 6a7ab5b7..df3e8476 100644 --- a/tests/native_runtime_smoke.py +++ b/tests/native_runtime_smoke.py @@ -52,6 +52,11 @@ def call(command, *, cwd=root): receipt = json.loads(setup) assert receipt["source_commit"] == args.revision assert not (install / "source/.git").exists() + for name in ("tools/supervisor.py", "tools/native_runtime.py", "tools/supervisor_policy.json"): + original = subprocess.check_output( + ["git", "-C", str(args.repo), "show", f"{args.revision}:{name}"] + ) + assert (install / "source" / name).read_bytes() == original launcher = install / "specgraph-runtime" fixture = json.loads( (install / "source/docs/evidence/0227_local_workspace_path_reuse.json").read_text() From 654e1522acfcecd977165a56ca0fd2670b76f0b9 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:13:51 +0300 Subject: [PATCH 05/15] Isolate installed runtime imports from consumer projects Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Prevent consumer modules and Python environment variables from shadowing the installed runtime. --- README.md | 9 +++++++-- Sources/SpecGraph/Documentation.docc/NativeRuntime.md | 3 +++ docs/proposals/0231_native_specgraph_runtime.md | 7 +++++++ tests/native_runtime_smoke.py | 3 +++ tools/native_runtime.py | 11 ++++++++--- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 49febc25..86e12813 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,13 @@ Proposal 0231 adds a separately installed snapshot of the full tracked SpecGraph source and a Python environment. It uses a full Git SHA and a new destination: ```sh -python3 tools/native_runtime.py install --source https://github.com/0al-spec/SpecGraph.git \ - --revision --destination +# Select a trusted full commit SHA that includes tools/native_runtime.py. +REVISION= +curl -fsSL "https://raw.githubusercontent.com/0al-spec/SpecGraph/$REVISION/tools/native_runtime.py" \ + -o /tmp/specgraph-native-install.py +python3 /tmp/specgraph-native-install.py install \ + --source https://github.com/0al-spec/SpecGraph.git \ + --revision "$REVISION" --destination /specgraph-runtime --workspace \ init -- --project-id /specgraph-runtime --workspace \ diff --git a/Sources/SpecGraph/Documentation.docc/NativeRuntime.md b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md index 561def55..15419a99 100644 --- a/Sources/SpecGraph/Documentation.docc/NativeRuntime.md +++ b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md @@ -9,6 +9,9 @@ prerequisites. The local source commit is recorded in `runtime.json`. `tools/native_runtime.py install` takes an explicit Git source, full revision and new destination. It never overwrites an existing destination. Failure leaves an incomplete installation without the `runtime.json` ready marker. +To bootstrap without a developer checkout, download that script from the same +trusted full commit on `raw.githubusercontent.com` before invoking it; the +repository README gives the command. The chosen revision must contain the script. The launcher accepts `--workspace` and `init`, `next-move`, `supervisor` or `draft`. `init` runs the existing initializer from the installed source root; diff --git a/docs/proposals/0231_native_specgraph_runtime.md b/docs/proposals/0231_native_specgraph_runtime.md index 8030f008..2ed59b34 100644 --- a/docs/proposals/0231_native_specgraph_runtime.md +++ b/docs/proposals/0231_native_specgraph_runtime.md @@ -4,6 +4,9 @@ Synchronous runtime slice for review. The [source](../archive/proposal_sources/0231_native_specgraph_runtime.md), [attributed note](../reviews/0231_native_runtime/source-notes.json), [intake](../reviews/0231_native_runtime/intake.json) and [draft obligations](0231_native_specgraph_runtime.intent.yaml) precede canonical acceptance. +See the [installed-runtime observation](../reviews/0231_native_runtime/verification.json) +and [pipeline observation](../reviews/0231_native_runtime/pipeline.json). + An agent needs a stable installed snapshot while the SpecGraph developer checkout evolves independently. The existing Product Workspace model remains the project boundary; source snapshots and Python environments are versioned @@ -18,6 +21,8 @@ installs dependencies, and produces a launcher and a completed receipt. It fails if the destination exists, preserves incomplete installations on error, and never adds projects or their data to the installed runtime. Python >=3.10, Git, network access to dependencies and Unix venv layout are prerequisites. +The bootstrap script can be downloaded from the same chosen Git commit; the +consumer does not need an existing developer checkout. The installed `specgraph-runtime --workspace init -- --project-id ` invokes the existing product initializer from the installed engine root. @@ -45,6 +50,8 @@ creates `.worktrees` a repeated initializer may report it as unexpected; these pre-existing limitations are not silently altered by this distribution slice. Real provider calls, canonical publication, cross-repository code trace and hosted evidence admission are outside the technical smoke's claims. +The actual repeat-init probe failed on `.worktrees` after the synthetic +refinement, as expected from proposal 0227; this is an open usability defect. ## Pipeline diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py index df3e8476..1d25a63b 100644 --- a/tests/native_runtime_smoke.py +++ b/tests/native_runtime_smoke.py @@ -81,6 +81,8 @@ def call(command, *, cwd=root): nodes = workspace / "specs/nodes" nodes.mkdir(parents=True) (nodes / f"{fixture['id']}.yaml").write_text(yaml.safe_dump(fixture, sort_keys=False)) + # A project-local module must not shadow the installed SpecDraft package. + (workspace / "specgraph.py").write_text("raise RuntimeError('unexpected project import')\n") call(["git", "init", "--quiet", str(workspace)]) call( [ @@ -91,6 +93,7 @@ def call(command, *, cwd=root): "specgraph.project.yaml", "specs", ".specgraph/root_intent.md", + "specgraph.py", ] ) call( diff --git a/tools/native_runtime.py b/tools/native_runtime.py index b9b2d139..6573baa1 100644 --- a/tools/native_runtime.py +++ b/tools/native_runtime.py @@ -19,7 +19,11 @@ def execute(arguments: list[str], *, cwd: Path | None = None) -> None: - subprocess.run(arguments, cwd=cwd, stdout=sys.stderr, check=True) + environment = os.environ.copy() + environment.pop("PYTHONPATH", None) + environment.pop("PYTHONHOME", None) + environment["PYTHONDONTWRITEBYTECODE"] = "1" + subprocess.run(arguments, cwd=cwd, env=environment, stdout=sys.stderr, check=True) def extract_snapshot(archive: Path, destination: Path) -> None: @@ -85,7 +89,8 @@ def install(source: str, revision: str, destination: Path) -> dict[str, str]: "status": "installed", "source_commit": selected, "python_version": subprocess.check_output( - [str(python), "-c", "import platform; print(platform.python_version())"], text=True + [str(python), "-I", "-c", "import platform; print(platform.python_version())"], + text=True, ).strip(), } (target / "runtime.json").write_text(json.dumps(receipt, indent=2) + "\n") @@ -128,7 +133,7 @@ def run(installation: Path, workspace: Path, operation: str, arguments: list[str command = [str(python), str(source / "tools" / "supervisor.py"), *arguments] cwd = project else: - command = [str(python), "-m", "specgraph", *arguments] + command = [str(python), "-I", "-m", "specgraph", *arguments] cwd = project if operation != "init" and not project.is_dir(): raise ValueError("Workspace must be an existing directory") From 4e5f6444563654b8cd788f20546a5e14e3527895 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:15:28 +0300 Subject: [PATCH 06/15] Record installed runtime and proposal lineage observations Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Preserve installed source evidence, deterministic refinement outcome and the repeat-initialization blocker. --- .../reviews/0231_native_runtime/pipeline.json | 12 ++++++ .../0231_native_runtime/verification.json | 39 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 docs/reviews/0231_native_runtime/pipeline.json create mode 100644 docs/reviews/0231_native_runtime/verification.json diff --git a/docs/reviews/0231_native_runtime/pipeline.json b/docs/reviews/0231_native_runtime/pipeline.json new file mode 100644 index 00000000..bad98414 --- /dev/null +++ b/docs/reviews/0231_native_runtime/pipeline.json @@ -0,0 +1,12 @@ +{ + "artifact_kind": "native_runtime_pipeline_observation", + "canonical_publication": "not_performed", + "canonical_requirement_binding": "not_created", + "intent_handle": "user_intent::specgraph-native-runtime-20261027", + "intent_status": "captured", + "model_execution": "not_performed", + "proposal_handle": "proposal::0231", + "proposal_status": "under_review", + "schema_version": 1, + "source_notes_revision": "1b44588d2b6640de86bd56f219fd4735" +} diff --git a/docs/reviews/0231_native_runtime/verification.json b/docs/reviews/0231_native_runtime/verification.json new file mode 100644 index 00000000..81bfb70c --- /dev/null +++ b/docs/reviews/0231_native_runtime/verification.json @@ -0,0 +1,39 @@ +{ + "artifact_kind": "native_runtime_observation", + "canonical_publication": "not_evaluated", + "checks": [ + "exact_snapshot_without_git", + "isolated_venv", + "path_with_spaces", + "init", + "product_advisor", + "targeted_supervisor_dry_run", + "specdraft_read" + ], + "executor": "deterministic_fixture_not_a_real_agent", + "installer_sha256": "4da7b216760d2fc54c6f2f95ea75707c873e1c02e735ff3734077fadef3e5185", + "platform": "macOS local; external TB5 synthetic workspaces", + "python_version": "3.12.11", + "refinement": { + "gate_state": "review_pending", + "isolation_mode": "git_worktree", + "outcome": "done" + }, + "repeat_init_after_refinement": { + "classification": "existing_initializer_limitation", + "entries": [ + ".worktrees" + ], + "exit_code": 1, + "finding_id": "unexpected_top_level_entries", + "status": "blocked" + }, + "schema_version": 1, + "source_commit": "b4e5cb9ff0c0aa6138c03a9c9edcfd809d6c5f63", + "source_files_sha256": { + "tests/native_runtime_smoke.py": "51ae2c81215aa43e998aab7101ec4e02eda082473f61ef2fb5be479735196428", + "tools/native_runtime.py": "4da7b216760d2fc54c6f2f95ea75707c873e1c02e735ff3734077fadef3e5185" + }, + "status": "passed", + "synthetic_workspaces": 2 +} From 451d0220e16bcb58e35077c7b21b5b4a687c3806 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:17:28 +0300 Subject: [PATCH 07/15] Bind observations to the final installed runtime source Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Record the exact source revision and distinguish workspace fixture entries in repeat-init diagnostics. --- .../0231_native_runtime/verification.json | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/reviews/0231_native_runtime/verification.json b/docs/reviews/0231_native_runtime/verification.json index 81bfb70c..8b156766 100644 --- a/docs/reviews/0231_native_runtime/verification.json +++ b/docs/reviews/0231_native_runtime/verification.json @@ -11,28 +11,39 @@ "specdraft_read" ], "executor": "deterministic_fixture_not_a_real_agent", - "installer_sha256": "4da7b216760d2fc54c6f2f95ea75707c873e1c02e735ff3734077fadef3e5185", - "platform": "macOS local; external TB5 synthetic workspaces", + "installer_sha256": "463b7bcb270d94e87fb487bc0c2eecf4df4090ea63d9d4860aa1bf1118cef449", + "platform": "macOS; synthetic external workspaces", "python_version": "3.12.11", "refinement": { "gate_state": "review_pending", "isolation_mode": "git_worktree", "outcome": "done" }, - "repeat_init_after_refinement": { - "classification": "existing_initializer_limitation", - "entries": [ - ".worktrees" - ], - "exit_code": 1, - "finding_id": "unexpected_top_level_entries", - "status": "blocked" - }, + "repeat_init_after_refinement": [ + { + "entries": [ + ".worktrees" + ], + "finding_id": "unexpected_top_level_entries", + "source_commit": "b4e5cb9ff0c0aa6138c03a9c9edcfd809d6c5f63", + "status": "blocked" + }, + { + "entries": [ + ".worktrees", + "specgraph.py" + ], + "finding_id": "unexpected_top_level_entries", + "note": "The second extra entry is the deliberate import-shadowing fixture.", + "source_commit": "654e1522acfcecd977165a56ca0fd2670b76f0b9", + "status": "blocked" + } + ], "schema_version": 1, - "source_commit": "b4e5cb9ff0c0aa6138c03a9c9edcfd809d6c5f63", + "source_commit": "654e1522acfcecd977165a56ca0fd2670b76f0b9", "source_files_sha256": { - "tests/native_runtime_smoke.py": "51ae2c81215aa43e998aab7101ec4e02eda082473f61ef2fb5be479735196428", - "tools/native_runtime.py": "4da7b216760d2fc54c6f2f95ea75707c873e1c02e735ff3734077fadef3e5185" + "tests/native_runtime_smoke.py": "d46037759f115809c6a807d223d1bf327ed2efd9b3143ae0a84363e68e438bec", + "tools/native_runtime.py": "463b7bcb270d94e87fb487bc0c2eecf4df4090ea63d9d4860aa1bf1118cef449" }, "status": "passed", "synthetic_workspaces": 2 From ffcdcd9eb3a8465692e19aa4d9a12e7f322bc0bd Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:21:02 +0300 Subject: [PATCH 08/15] Express native runtime readiness as a named specification Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Require a versioned completed installation receipt and full source commit before launching an installed runtime. --- .../proposals/0231_native_specgraph_runtime.md | 7 ++++--- docs/reviews/0231_native_runtime/specdraft.md | 2 ++ tests/test_native_runtime.py | 16 ++++++++++++++++ tools/native_runtime.py | 18 ++++++++++++++---- tools/native_runtime_context.py | 11 +++++++++++ tools/native_runtime_ready_spec.py | 17 +++++++++++++++++ tools/proposal_runtime_registry.json | 2 ++ 7 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tools/native_runtime_context.py create mode 100644 tools/native_runtime_ready_spec.py diff --git a/docs/proposals/0231_native_specgraph_runtime.md b/docs/proposals/0231_native_specgraph_runtime.md index 2ed59b34..7dbbfd18 100644 --- a/docs/proposals/0231_native_specgraph_runtime.md +++ b/docs/proposals/0231_native_specgraph_runtime.md @@ -57,6 +57,7 @@ refinement, as expected from proposal 0227; this is an open usability defect. Captured user intent → proposal lane → draft obligations → implementation → tests/observations. SG-SPEC-0007 constrains the attribution boundary; references -are governance context, not a canonical Requirement binding. CLI forwarding, -archive copying and environment creation are I/O mechanics; they do not create -new domain Specifications or expand supervisor authority. +are governance context, not a canonical Requirement binding. The named +`RUNTIME_READY_SPEC` checks that a versioned installation receipt identifies a +complete snapshot. CLI forwarding, archive copying and environment creation are +I/O mechanics and do not expand supervisor authority. diff --git a/docs/reviews/0231_native_runtime/specdraft.md b/docs/reviews/0231_native_runtime/specdraft.md index 7aa87f0c..2652eaad 100644 --- a/docs/reviews/0231_native_runtime/specdraft.md +++ b/docs/reviews/0231_native_runtime/specdraft.md @@ -9,3 +9,5 @@ The four draft obligations map to `tools/native_runtime.py::install`, destructive/error boundaries. The installed-runtime smoke must additionally exercise two independent workspaces, existing supervisor behavior and receipt provenance. This draft is not a canonical Requirement or an admission decision. +`RUNTIME_READY_SPEC` in `tools/native_runtime_ready_spec.py` expresses the +versioned installation readiness decision using `RuntimeReadyFacts`. diff --git a/tests/test_native_runtime.py b/tests/test_native_runtime.py index d07521fe..fceeed72 100644 --- a/tests/test_native_runtime.py +++ b/tests/test_native_runtime.py @@ -3,11 +3,13 @@ import importlib.util import io import json +import sys import tarfile from pathlib import Path import pytest +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools")) SPEC = importlib.util.spec_from_file_location( "native_runtime", Path(__file__).resolve().parents[1] / "tools" / "native_runtime.py" ) @@ -51,3 +53,17 @@ def test_non_full_sha_rejected_without_creating_destination(tmp_path): with pytest.raises(ValueError, match="full lowercase"): runtime.install("unused", "deadbeef", target) assert not target.exists() + + +def test_ready_spec_requires_a_complete_versioned_receipt(): + from native_runtime_context import RuntimeReadyFacts + from native_runtime_ready_spec import RUNTIME_READY_SPEC + + ready = RuntimeReadyFacts("specgraph_native_runtime", "installed", 1, "a" * 40) + assert RUNTIME_READY_SPEC.is_satisfied_by(ready) + assert not RUNTIME_READY_SPEC.is_satisfied_by( + RuntimeReadyFacts(ready.artifact_kind, ready.status, True, ready.source_commit) + ) + assert not RUNTIME_READY_SPEC.is_satisfied_by( + RuntimeReadyFacts(ready.artifact_kind, ready.status, 1, "incomplete") + ) diff --git a/tools/native_runtime.py b/tools/native_runtime.py index 6573baa1..8a5ff2fd 100644 --- a/tools/native_runtime.py +++ b/tools/native_runtime.py @@ -98,12 +98,22 @@ def install(source: str, revision: str, destination: Path) -> dict[str, str]: def run(installation: Path, workspace: Path, operation: str, arguments: list[str]) -> int: + # Imported only after installation: the standalone bootstrap has no + # SpecificationCore dependency until it has created its own environment. + from native_runtime_context import RuntimeReadyFacts + from native_runtime_ready_spec import RUNTIME_READY_SPEC + root = installation.expanduser().resolve(strict=True) receipt = json.loads((root / "runtime.json").read_text()) - if ( - receipt.get("artifact_kind") != "specgraph_native_runtime" - or receipt.get("status") != "installed" - ): + if not isinstance(receipt, dict): + raise ValueError("Installation receipt must be an object") + facts = RuntimeReadyFacts( + artifact_kind=str(receipt.get("artifact_kind", "")), + status=str(receipt.get("status", "")), + schema_version=receipt.get("schema_version"), + source_commit=str(receipt.get("source_commit", "")), + ) + if not RUNTIME_READY_SPEC.is_satisfied_by(facts): raise ValueError("Installation is not ready") project = workspace.expanduser().resolve() if project == root or root in project.parents: diff --git a/tools/native_runtime_context.py b/tools/native_runtime_context.py new file mode 100644 index 00000000..fd82f775 --- /dev/null +++ b/tools/native_runtime_context.py @@ -0,0 +1,11 @@ +"""Immutable facts used to decide whether an installation can run.""" + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class RuntimeReadyFacts: + artifact_kind: str + status: str + schema_version: object + source_commit: str diff --git a/tools/native_runtime_ready_spec.py b/tools/native_runtime_ready_spec.py new file mode 100644 index 00000000..d2ba2b19 --- /dev/null +++ b/tools/native_runtime_ready_spec.py @@ -0,0 +1,17 @@ +"""A complete runtime receipt must identify a fixed source commit.""" + +from specification_core import PredicateSpec + +from native_runtime_context import RuntimeReadyFacts + +RUNTIME_READY_SPEC: PredicateSpec[RuntimeReadyFacts] = PredicateSpec( + lambda facts: ( + facts.artifact_kind == "specgraph_native_runtime" + and facts.status == "installed" + and type(facts.schema_version) is int + and facts.schema_version == 1 + and len(facts.source_commit) == 40 + and all(character in "0123456789abcdef" for character in facts.source_commit) + ), + name="native_runtime.receipt_ready", +) diff --git a/tools/proposal_runtime_registry.json b/tools/proposal_runtime_registry.json index cc4fbc2a..cf2a44aa 100644 --- a/tools/proposal_runtime_registry.json +++ b/tools/proposal_runtime_registry.json @@ -13513,6 +13513,8 @@ "docs/reviews/0231_native_runtime/specdraft.md", "docs/reviews/0231_native_runtime/verification.json", "tools/native_runtime.py", + "tools/native_runtime_context.py", + "tools/native_runtime_ready_spec.py", "tests/test_native_runtime.py", "tests/native_runtime_smoke.py", "Sources/SpecGraph/Documentation.docc/NativeRuntime.md" From 26b2dd973ba3510dad71478b33570959194e653b Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:21:27 +0300 Subject: [PATCH 09/15] Clarify native runtime receipt readiness boundary Proposal-ID: 0231 Spec-Impact: docs-only Spec-Rationale: Distinguish receipt readiness from verification of installed tree integrity. --- docs/proposals/0231_native_specgraph_runtime.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/proposals/0231_native_specgraph_runtime.md b/docs/proposals/0231_native_specgraph_runtime.md index 7dbbfd18..47e69351 100644 --- a/docs/proposals/0231_native_specgraph_runtime.md +++ b/docs/proposals/0231_native_specgraph_runtime.md @@ -58,6 +58,7 @@ refinement, as expected from proposal 0227; this is an open usability defect. Captured user intent → proposal lane → draft obligations → implementation → tests/observations. SG-SPEC-0007 constrains the attribution boundary; references are governance context, not a canonical Requirement binding. The named -`RUNTIME_READY_SPEC` checks that a versioned installation receipt identifies a -complete snapshot. CLI forwarding, archive copying and environment creation are -I/O mechanics and do not expand supervisor authority. +`RUNTIME_READY_SPEC` checks that a versioned receipt records a completed install +and identifies a full source commit. It does not verify the current bytes of the +installed tree. CLI forwarding, archive copying and environment creation are I/O +mechanics and do not expand supervisor authority. From 33109de2317c479844c5b818a70dcad217f8c805 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:21:41 +0300 Subject: [PATCH 10/15] Record readiness specification installation verification Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Bind the installed runtime observation to the source containing its readiness policy. --- .../0231_native_runtime/verification.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/reviews/0231_native_runtime/verification.json b/docs/reviews/0231_native_runtime/verification.json index 8b156766..f755a03e 100644 --- a/docs/reviews/0231_native_runtime/verification.json +++ b/docs/reviews/0231_native_runtime/verification.json @@ -11,7 +11,7 @@ "specdraft_read" ], "executor": "deterministic_fixture_not_a_real_agent", - "installer_sha256": "463b7bcb270d94e87fb487bc0c2eecf4df4090ea63d9d4860aa1bf1118cef449", + "installer_sha256": "d7a220b7e0ddbd419e1b3e114adacd539675aa4292c897453ca8b02bea2e0536", "platform": "macOS; synthetic external workspaces", "python_version": "3.12.11", "refinement": { @@ -37,13 +37,25 @@ "note": "The second extra entry is the deliberate import-shadowing fixture.", "source_commit": "654e1522acfcecd977165a56ca0fd2670b76f0b9", "status": "blocked" + }, + { + "entries": [ + ".worktrees", + "specgraph.py" + ], + "finding_id": "unexpected_top_level_entries", + "note": "specgraph.py is a deliberate import-shadowing fixture.", + "source_commit": "ffcdcd9eb3a8465692e19aa4d9a12e7f322bc0bd", + "status": "blocked" } ], "schema_version": 1, - "source_commit": "654e1522acfcecd977165a56ca0fd2670b76f0b9", + "source_commit": "ffcdcd9eb3a8465692e19aa4d9a12e7f322bc0bd", "source_files_sha256": { "tests/native_runtime_smoke.py": "d46037759f115809c6a807d223d1bf327ed2efd9b3143ae0a84363e68e438bec", - "tools/native_runtime.py": "463b7bcb270d94e87fb487bc0c2eecf4df4090ea63d9d4860aa1bf1118cef449" + "tools/native_runtime.py": "d7a220b7e0ddbd419e1b3e114adacd539675aa4292c897453ca8b02bea2e0536", + "tools/native_runtime_context.py": "24eb0410940f29694cadb23602dc5659500cd345595d2b9a10534d312dcf9f5c", + "tools/native_runtime_ready_spec.py": "30d343160f4b62b60d71765a58e86e824e51ae851ac482f5504640fae259225b" }, "status": "passed", "synthetic_workspaces": 2 From a76eab7643130342c3ecd14137bf2912fdc0a9bd Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:22:24 +0300 Subject: [PATCH 11/15] Bind native runtime draft obligations to source decisions Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Record individually reviewed implementation dispositions including the named readiness specification. --- .../0231_native_runtime.json | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tools/intent_decision_reviews/0231_native_runtime.json diff --git a/tools/intent_decision_reviews/0231_native_runtime.json b/tools/intent_decision_reviews/0231_native_runtime.json new file mode 100644 index 00000000..d1020d57 --- /dev/null +++ b/tools/intent_decision_reviews/0231_native_runtime.json @@ -0,0 +1,75 @@ +{ + "artifact_kind": "specgraph.intent-decision-review/v1", + "base_sha": "26886fc22bf04d115429836dfce13f1e7efbfb29", + "decisions": [ + { + "change_sha256": "473942eb2712e5d7db9f220c469cd7abf3f8640f8a610937900e3afc2f7894d0", + "disposition": "mechanics", + "implementation_refs": [ + { + "path": "tools/native_runtime.py", + "symbol": "install" + }, + { + "path": "tools/native_runtime.py", + "symbol": "extract_snapshot" + } + ], + "rationale": "The fetched full commit is compared to the request; Git archive and validated extraction copy tracked files. This is source and filesystem I/O, not a domain policy or canonical authority decision.", + "specification_refs": [] + }, + { + "change_sha256": "c48fd8ccf26583fd3c8e3463224a10e94f1c34d0636b22bf708a9c691f5dfc2e", + "disposition": "new_specification", + "implementation_refs": [ + { + "path": "tools/native_runtime.py", + "symbol": "install" + }, + { + "path": "tools/native_runtime.py", + "symbol": "run" + } + ], + "rationale": "Environment creation and exclusive destination creation are I/O mechanics; the named receipt readiness Specification checks artifact kind, completed status, schema and source commit before any installed run. It does not claim tree integrity.", + "specification_refs": [ + { + "path": "tools/native_runtime_ready_spec.py", + "symbol": "RUNTIME_READY_SPEC" + } + ] + }, + { + "change_sha256": "08264b00af227e76f323fa34ac86cb4d28e1783dd66b3e8bae35df1b687d6dfe", + "disposition": "mechanics", + "implementation_refs": [ + { + "path": "tools/native_runtime.py", + "symbol": "run" + } + ], + "rationale": "The explicit workspace selects the current directory or existing workspace-root parameter for each existing CLI. This adapter routing does not infer business semantics; two independent synthetic workspaces and their Git repositories were exercised.", + "specification_refs": [] + }, + { + "change_sha256": "3ff0f66fab63b0c90c94ce1cb5f7ce96e6d574a5036d22754c266d448596913c", + "disposition": "mechanics", + "implementation_refs": [ + { + "path": "tools/native_runtime.py", + "symbol": "run" + }, + { + "path": "tools/supervisor.py", + "symbol": "main" + } + ], + "rationale": "The launcher only forwards to the unchanged supervisor and executor boundary; it grants no additional authority and does not implement ASP. Existing supervisor governance still governs refinement; the deterministic fixture ended review_pending, without a real agent or canonical approval.", + "specification_refs": [] + } + ], + "intent_delta_sha256": "0fa11a2d53468812916089645ed38be2ec5dd65382f55645ae24a99881081046", + "pr_body_sha256": "28bb5c95e384ac19e6ac05462e6337ad2e0ac3236294c8c8a46631f7fa40c500", + "schema_version": 1, + "source_scope_sha256": "0ce4e2c99fddca82dc84d55fafe6bf81b50d999dd5b1ab8a2620067ddbb5fafb" +} From baa871d7d4670e052c20dd3e095e1bf134a9920f Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:22:39 +0300 Subject: [PATCH 12/15] Use bounded source reference for supervisor forwarding Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Keep decision evidence within the source reference size limit. --- tools/intent_decision_reviews/0231_native_runtime.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/intent_decision_reviews/0231_native_runtime.json b/tools/intent_decision_reviews/0231_native_runtime.json index d1020d57..c16ea717 100644 --- a/tools/intent_decision_reviews/0231_native_runtime.json +++ b/tools/intent_decision_reviews/0231_native_runtime.json @@ -58,10 +58,6 @@ { "path": "tools/native_runtime.py", "symbol": "run" - }, - { - "path": "tools/supervisor.py", - "symbol": "main" } ], "rationale": "The launcher only forwards to the unchanged supervisor and executor boundary; it grants no additional authority and does not implement ASP. Existing supervisor governance still governs refinement; the deterministic fixture ended review_pending, without a real agent or canonical approval.", From df773af49d92121b9261dd5369fe0868086e5569 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:57:32 +0300 Subject: [PATCH 13/15] Resolve local Git sources from the caller directory Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Preserve relative local source semantics when fetching from an isolated temporary Git repository. --- CONTRIBUTING.md | 3 +++ .../SpecGraph/Documentation.docc/NativeRuntime.md | 3 +++ docs/proposals/0231_native_specgraph_runtime.md | 2 ++ tests/native_runtime_smoke.py | 5 +++-- tests/test_native_runtime.py | 15 +++++++++++++++ tools/native_runtime.py | 11 +++++++++++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 107c083b..341d90eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,9 @@ environment. Exercise the installed launcher against distinct external workspace roots; source-only tests cannot prove policy resources or tool paths are usable. Record the selected commit and distinguish deterministic probes from real executor runs and canonical approval. +Resolve user-supplied local Git sources against the caller's directory before +changing the Git subprocess directory. Exercise a relative source in the actual +installation smoke, and preserve Git URL and SSH remote syntax. Distribution changes must exercise the built artifact, not just imports from the source checkout. For portable SpecDraft (proposal 0230), build a wheel through diff --git a/Sources/SpecGraph/Documentation.docc/NativeRuntime.md b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md index 15419a99..2ccccf78 100644 --- a/Sources/SpecGraph/Documentation.docc/NativeRuntime.md +++ b/Sources/SpecGraph/Documentation.docc/NativeRuntime.md @@ -12,6 +12,9 @@ an incomplete installation without the `runtime.json` ready marker. To bootstrap without a developer checkout, download that script from the same trusted full commit on `raw.githubusercontent.com` before invoking it; the repository README gives the command. The chosen revision must contain the script. +Local Git source paths are resolved against the caller's directory before Git +enters its temporary repository; Git URL and SSH remote syntax stays unchanged. +The installed-runtime smoke uses a relative source to preserve this behavior. The launcher accepts `--workspace` and `init`, `next-move`, `supervisor` or `draft`. `init` runs the existing initializer from the installed source root; diff --git a/docs/proposals/0231_native_specgraph_runtime.md b/docs/proposals/0231_native_specgraph_runtime.md index 47e69351..ab98701c 100644 --- a/docs/proposals/0231_native_specgraph_runtime.md +++ b/docs/proposals/0231_native_specgraph_runtime.md @@ -23,6 +23,8 @@ never adds projects or their data to the installed runtime. Python >=3.10, Git, network access to dependencies and Unix venv layout are prerequisites. The bootstrap script can be downloaded from the same chosen Git commit; the consumer does not need an existing developer checkout. +Relative Git source paths are resolved from the caller's directory before +entering the temporary Git repository; URL and SSH remote forms are preserved. The installed `specgraph-runtime --workspace init -- --project-id ` invokes the existing product initializer from the installed engine root. diff --git a/tests/native_runtime_smoke.py b/tests/native_runtime_smoke.py index 1d25a63b..8c8c2c3f 100644 --- a/tests/native_runtime_smoke.py +++ b/tests/native_runtime_smoke.py @@ -42,12 +42,13 @@ def call(command, *, cwd=root): str(args.repo / "tools/native_runtime.py"), "install", "--source", - str(args.repo), + ".", "--revision", args.revision, "--destination", str(install), - ] + ], + cwd=args.repo, ) receipt = json.loads(setup) assert receipt["source_commit"] == args.revision diff --git a/tests/test_native_runtime.py b/tests/test_native_runtime.py index fceeed72..efdd4ba6 100644 --- a/tests/test_native_runtime.py +++ b/tests/test_native_runtime.py @@ -55,6 +55,21 @@ def test_non_full_sha_rejected_without_creating_destination(tmp_path): assert not target.exists() +def test_local_git_sources_use_caller_root_and_remote_syntax_stays_unchanged(tmp_path): + assert runtime.resolve_git_source(".", tmp_path) == str(tmp_path.resolve()) + assert runtime.resolve_git_source("../repository with spaces", tmp_path) == str( + (tmp_path / "../repository with spaces").resolve() + ) + for source in ( + "https://github.com/0al-spec/SpecGraph.git", + "ssh://git@example.com/repository", + "git@example.com:owner/repository.git", + "example.com:owner/repository.git", + "file:///tmp/source.git", + ): + assert runtime.resolve_git_source(source, tmp_path) == source + + def test_ready_spec_requires_a_complete_versioned_receipt(): from native_runtime_context import RuntimeReadyFacts from native_runtime_ready_spec import RUNTIME_READY_SPEC diff --git a/tools/native_runtime.py b/tools/native_runtime.py index 8a5ff2fd..cce2760b 100644 --- a/tools/native_runtime.py +++ b/tools/native_runtime.py @@ -16,6 +16,16 @@ from pathlib import Path, PurePosixPath COMMIT = re.compile(r"^[0-9a-f]{40}$") +REMOTE_SOURCE = re.compile(r"^(?:[A-Za-z][A-Za-z0-9+.-]*://|[^/]+:)") + + +def resolve_git_source(source: str, caller_root: Path) -> str: + """Resolve local paths before Git changes cwd, preserving remote syntax.""" + if not source: + raise ValueError("Git source must not be empty") + if REMOTE_SOURCE.match(source): + return source + return str((caller_root / Path(source).expanduser()).resolve()) def execute(arguments: list[str], *, cwd: Path | None = None) -> None: @@ -40,6 +50,7 @@ def install(source: str, revision: str, destination: Path) -> dict[str, str]: raise ValueError("Revision must be a full lowercase Git commit SHA") if shutil.which("git") is None: raise ValueError("Git is required for installation") + source = resolve_git_source(source, Path.cwd()) target = destination.expanduser().absolute() if target.is_symlink() or target.exists(): raise ValueError("Installation destination already exists") From f51a4a99904279179c7141292b1935672560bb79 Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 22:59:39 +0300 Subject: [PATCH 14/15] Record relative source review regression evidence Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Retain the review root cause and source-bound installation verification. --- .../relative-source-verification.json | 33 +++++++++++++++++++ tools/review_feedback_records.json | 17 ++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/reviews/0231_native_runtime/relative-source-verification.json diff --git a/docs/reviews/0231_native_runtime/relative-source-verification.json b/docs/reviews/0231_native_runtime/relative-source-verification.json new file mode 100644 index 00000000..fbb6347f --- /dev/null +++ b/docs/reviews/0231_native_runtime/relative-source-verification.json @@ -0,0 +1,33 @@ +{ + "artifact_kind": "native_runtime_observation", + "canonical_publication": "not_evaluated", + "checks": [ + "exact_snapshot_without_git", + "isolated_venv", + "path_with_spaces", + "init", + "product_advisor", + "targeted_supervisor_dry_run", + "specdraft_read" + ], + "executor": "deterministic_fixture_not_a_real_agent", + "installer_sha256": "3024b78faab17f4b920e02b1a3dff30961fae9ab4d48139b3f381aa6bd6aa8c0", + "python_version": "3.12.11", + "refinement": { + "gate_state": "review_pending", + "isolation_mode": "git_worktree", + "outcome": "done" + }, + "review_thread": "https://github.com/0al-spec/SpecGraph/pull/813#discussion_r4393159271", + "schema_version": 1, + "source_argument": ".", + "source_commit": "df773af49d92121b9261dd5369fe0868086e5569", + "source_files_sha256": { + "tests/native_runtime_smoke.py": "744eff5a57fabeafc7f4926d0a5c7791a73d9a42d8dccab5ef984c982fba1bb3", + "tests/test_native_runtime.py": "1e76f5f3dfd5ae94df860ee873c8a0063fd5a201121cd9956e38d3c2a7cc7596", + "tools/native_runtime.py": "3024b78faab17f4b920e02b1a3dff30961fae9ab4d48139b3f381aa6bd6aa8c0" + }, + "source_resolution": "relative to the caller checkout before temporary git -C", + "status": "passed", + "synthetic_workspaces": 2 +} diff --git a/tools/review_feedback_records.json b/tools/review_feedback_records.json index 9c3b37c8..6be18870 100644 --- a/tools/review_feedback_records.json +++ b/tools/review_feedback_records.json @@ -1343,5 +1343,22 @@ "residual_risk": "The Node DOM-double regression checks shipped app text and click-handler behavior; it is not a real-browser layout or accessibility audit. Earlier browser observations remain historical.", "recorded_at": "2026-10-26T16:59:24.779875+00:00", "prevention_path": "tests/specdraft_preview_inspector.cjs exercises both selected endpoints, both outside-branch directions, a self-link and literal text. Four tests failed before and passed after; .github/workflows/python-ci.yml runs them under Node 24." + }, + { + "feedback_id": "pr-813-relative-git-source", + "source_pr": 813, + "source_thread_url": "https://github.com/0al-spec/SpecGraph/pull/813#discussion_r4393159271", + "reviewer": "chatgpt-codex-connector", + "review_comment_summary": "Relative --source was resolved inside the temporary bare Git repository, causing fetch to fail.", + "fix_summary": "Resolve local sources against the caller directory before changing Git cwd; preserve URL and scp-style remotes.", + "root_cause_class": "test_coverage_gap", + "prevention_action": "regression_test_added", + "verification": [ + "targeted_test", + "lint_or_format_check" + ], + "residual_risk": "Remote URL and scp forms are checked as preserved arguments; this review smoke uses an actual local repository rather than a live remote.", + "recorded_at": "2026-10-27T19:59:39.779207+00:00", + "prevention_path": "tests/test_native_runtime.py checks relative paths and remote forms; tests/native_runtime_smoke.py installs using --source . from the caller checkout and exercises two workspaces." } ] From 12eada48424c617560e0c08cd31203b8f0339f0f Mon Sep 17 00:00:00 2001 From: Egor Merkushev Date: Tue, 27 Oct 2026 23:02:33 +0300 Subject: [PATCH 15/15] Refresh native runtime intent review after feedback Proposal-ID: 0231 Spec-Impact: tooling Spec-Rationale: Bind the reviewed source changes and updated PR description to existing individual dispositions. --- tools/intent_decision_reviews/0231_native_runtime.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/intent_decision_reviews/0231_native_runtime.json b/tools/intent_decision_reviews/0231_native_runtime.json index c16ea717..b6f23894 100644 --- a/tools/intent_decision_reviews/0231_native_runtime.json +++ b/tools/intent_decision_reviews/0231_native_runtime.json @@ -65,7 +65,7 @@ } ], "intent_delta_sha256": "0fa11a2d53468812916089645ed38be2ec5dd65382f55645ae24a99881081046", - "pr_body_sha256": "28bb5c95e384ac19e6ac05462e6337ad2e0ac3236294c8c8a46631f7fa40c500", + "pr_body_sha256": "9a127e2adc83429acc03b9b302d7db1132ba0e41668b35cf1507bc6b1dfd9abf", "schema_version": 1, - "source_scope_sha256": "0ce4e2c99fddca82dc84d55fafe6bf81b50d999dd5b1ab8a2620067ddbb5fafb" + "source_scope_sha256": "068c77b2c6b77fb8cde2a33447d9b1f345f8cd882ee90b3020fcf81f85298ac8" }