diff --git a/Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md b/Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md index 1affa6e1..f963e4dd 100644 --- a/Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md +++ b/Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md @@ -959,3 +959,10 @@ The full planning contracts remain in repository Markdown: - `docs/product_workspace_graph_versioning_roadmap.md` - `docs/product_workspace_stable_mode_guide.md` +## Durable workspace binding evidence + +Proposal `0211` adds a public-safe `workspace_binding_evidence` descriptor to +the SpecGraph product workspace initialization receipt. The descriptor pins +workspace identity, relative layout roots, project-config digest, and +repository-role hints without exposing absolute local paths or granting +Platform, Git, spec, or Ontology mutation authority. diff --git a/docs/archive/proposal_sources/0211_durable_product_workspace_binding_evidence.md b/docs/archive/proposal_sources/0211_durable_product_workspace_binding_evidence.md new file mode 100644 index 00000000..ccb06dc8 --- /dev/null +++ b/docs/archive/proposal_sources/0211_durable_product_workspace_binding_evidence.md @@ -0,0 +1,58 @@ +# 0211 Durable Product Workspace Binding Evidence + +## Status + +Draft / bounded product workspace initialization evidence slice. + +## Summary + +Platform already records routing and execution fields for an initialized product +workspace, but downstream operations can still derive run directories, state +namespaces, or artifact routes from a workspace slug or shared local defaults. +SpecGraph's initialization receipt proves that a workspace layout is valid, but +it does not yet expose a compact digest-bound descriptor that Platform can pin +when it creates the durable cross-repository binding. + +This slice adds that SpecGraph-owned evidence without making SpecGraph the owner +of Platform paths, SpecSpace state, deployment URLs, or Git execution. + +## Decision + +`runs/product_workspace_initialization.json` carries +`workspace_binding_evidence` with: + +- stable workspace identity and product-workspace governance; +- workspace-relative layout refs for config, specs, proposals, runs, and + supervisor state; +- the `specgraph.project.yaml` digest; +- repository/worktree identity hints that do not create a worktree; +- a stable evidence digest over the public-safe descriptor; +- closed privacy and authority boundaries. + +Platform may validate and pin the whole initialization report plus this evidence +when it creates its durable workspace binding. SpecSpace may render a sanitized +projection but must not infer local paths from the route. + +## Authority Boundary + +This slice does not: + +- select Platform artifact base URLs or local execution roots; +- allocate SpecSpace mutable-state storage; +- execute Platform, SpecGraph managed flows, or Git Service; +- create a repository, worktree, branch, commit, or pull request; +- mutate canonical specs; +- write Ontology packages or accept Ontology terms; +- publish raw root intent or local absolute paths. + +## Acceptance Criteria + +- Ready initialization reports include versioned workspace binding evidence. +- Evidence uses workspace-relative refs and a stable content digest. +- The project config digest is available on first initialization and idempotent + reruns. +- Blocked initialization reports mark binding evidence blocked. +- No absolute input path or raw root intent appears in the evidence. +- Platform can pin the initialization report and evidence without re-deriving + SpecGraph layout semantics. + diff --git a/docs/product_workspace_graph_versioning_roadmap.md b/docs/product_workspace_graph_versioning_roadmap.md index 35922f2b..ef64382f 100644 --- a/docs/product_workspace_graph_versioning_roadmap.md +++ b/docs/product_workspace_graph_versioning_roadmap.md @@ -1885,6 +1885,14 @@ That directory has its own `artifact_manifest.json` and `checksums.sha256`. SpecSpace production routes should read `/team-decision-log` from this workspace-specific artifact base instead of the bootstrap root bundle. +## Durable Product Workspace Binding Evidence + +Implemented producer-side in proposal `0211`. Product workspace initialization +now emits digest-bound, workspace-relative binding evidence. Platform owns the +external binding across artifact routing, SpecSpace state namespace, execution +run directory, and repository resolution; SpecGraph owns only initialized +workspace identity, layout semantics, and project-config evidence. + ## Related Documents - `docs/product_workspace_stable_mode_guide.md` diff --git a/docs/product_workspace_initialization_viewer_contract.md b/docs/product_workspace_initialization_viewer_contract.md index 6457c1dc..5fa3529a 100644 --- a/docs/product_workspace_initialization_viewer_contract.md +++ b/docs/product_workspace_initialization_viewer_contract.md @@ -79,6 +79,13 @@ python3 tools/supervisor.py \ } ``` +Proposal `0211` adds `workspace_binding_evidence` to the same receipt. The +evidence carries workspace identity, workspace-relative layout refs, the +`specgraph.project.yaml` digest, repository/worktree identity hints, and a +stable evidence digest. Platform may pin it into a durable workspace binding; +SpecSpace should consume only a public-safe projection and must not infer local +paths from the route. + ## UI Guidance - Show `summary.status` as the primary status. diff --git a/docs/proposals/0211_durable_product_workspace_binding_evidence.md b/docs/proposals/0211_durable_product_workspace_binding_evidence.md new file mode 100644 index 00000000..ccb06dc8 --- /dev/null +++ b/docs/proposals/0211_durable_product_workspace_binding_evidence.md @@ -0,0 +1,58 @@ +# 0211 Durable Product Workspace Binding Evidence + +## Status + +Draft / bounded product workspace initialization evidence slice. + +## Summary + +Platform already records routing and execution fields for an initialized product +workspace, but downstream operations can still derive run directories, state +namespaces, or artifact routes from a workspace slug or shared local defaults. +SpecGraph's initialization receipt proves that a workspace layout is valid, but +it does not yet expose a compact digest-bound descriptor that Platform can pin +when it creates the durable cross-repository binding. + +This slice adds that SpecGraph-owned evidence without making SpecGraph the owner +of Platform paths, SpecSpace state, deployment URLs, or Git execution. + +## Decision + +`runs/product_workspace_initialization.json` carries +`workspace_binding_evidence` with: + +- stable workspace identity and product-workspace governance; +- workspace-relative layout refs for config, specs, proposals, runs, and + supervisor state; +- the `specgraph.project.yaml` digest; +- repository/worktree identity hints that do not create a worktree; +- a stable evidence digest over the public-safe descriptor; +- closed privacy and authority boundaries. + +Platform may validate and pin the whole initialization report plus this evidence +when it creates its durable workspace binding. SpecSpace may render a sanitized +projection but must not infer local paths from the route. + +## Authority Boundary + +This slice does not: + +- select Platform artifact base URLs or local execution roots; +- allocate SpecSpace mutable-state storage; +- execute Platform, SpecGraph managed flows, or Git Service; +- create a repository, worktree, branch, commit, or pull request; +- mutate canonical specs; +- write Ontology packages or accept Ontology terms; +- publish raw root intent or local absolute paths. + +## Acceptance Criteria + +- Ready initialization reports include versioned workspace binding evidence. +- Evidence uses workspace-relative refs and a stable content digest. +- The project config digest is available on first initialization and idempotent + reruns. +- Blocked initialization reports mark binding evidence blocked. +- No absolute input path or raw root intent appears in the evidence. +- Platform can pin the initialization report and evidence without re-deriving + SpecGraph layout semantics. + diff --git a/tests/test_supervisor.py b/tests/test_supervisor.py index e1c3a005..308152e4 100644 --- a/tests/test_supervisor.py +++ b/tests/test_supervisor.py @@ -25015,6 +25015,33 @@ def test_initialize_product_workspace_creates_minimal_layout( ) assert artifact["workspace"]["root"] == "." assert artifact["workspace"]["local_input_path_persisted"] is False + binding = artifact["workspace_binding_evidence"] + assert binding["contract_ref"] == ("specgraph.product-workspace.binding-evidence.v0.1") + assert binding["proposal_id"] == "0211" + assert binding["status"] == "ready" + assert binding["identity"] == { + "workspace_id": "swiftui-calculator", + "display_name": "SwiftUI Calculator", + "governance_profile": "product_workspace", + "repository_role": "product_spec_workspace", + } + assert binding["layout"]["runs_root_ref"] == "runs" + assert binding["project_config"]["source_ref"] == "specgraph.project.yaml" + assert len(binding["project_config"]["source_sha256"]) == 64 + binding_without_digest = copy.deepcopy(binding) + evidence_sha256 = binding_without_digest.pop("evidence_sha256") + assert ( + evidence_sha256 + == supervisor_module.hashlib.sha256( + json.dumps( + binding_without_digest, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).hexdigest() + ) + assert binding["privacy_boundary"]["workspace_relative_refs_only"] is True + assert binding["authority_boundary"]["may_create_git_commit"] is False assert artifact["root_intent"]["status"] == "captured" assert "Build a SwiftUI calculator" not in json.dumps(artifact) @@ -25044,6 +25071,14 @@ def test_initialize_product_workspace_is_idempotent( assert second["workspace"]["created_paths"] == [] assert "specgraph.project.yaml" in second["workspace"]["existing_paths"] assert ".specgraph/root_intent.md" in second["workspace"]["existing_paths"] + assert ( + first["workspace_binding_evidence"]["project_config"]["source_sha256"] + == second["workspace_binding_evidence"]["project_config"]["source_sha256"] + ) + assert ( + first["workspace_binding_evidence"]["evidence_sha256"] + == second["workspace_binding_evidence"]["evidence_sha256"] + ) def test_initialize_product_workspace_hashes_existing_root_intent( @@ -25216,6 +25251,7 @@ def test_initialize_product_workspace_blocks_conflicting_config_without_overwrit ) assert artifact["summary"]["status"] == "blocked" assert artifact["next_gap"] == "repair_product_workspace_initialization" + assert artifact["workspace_binding_evidence"]["status"] == "blocked" def test_initialize_product_workspace_refuses_core_repo_paths( diff --git a/tools/README.md b/tools/README.md index 6852bd87..37761505 100644 --- a/tools/README.md +++ b/tools/README.md @@ -2030,3 +2030,9 @@ python3 tools/search_pageindex.py "agent orchestration" --top-k 10 --context The script expects the PageIndex API to be running on `http://localhost:8765` and uses the `~/Development/GitHub/PageIndexInstance/results/chatgpt_dialogs/catalog.json` catalog by default. +## Durable product workspace binding evidence + +Proposal 0211 adds digest-bound `workspace_binding_evidence` to +`runs/product_workspace_initialization.json`. Platform can use it as +producer-owned initialization evidence while retaining ownership of artifact +routing, state namespaces, execution roots, and repository operations. diff --git a/tools/proposal_promotion_registry.json b/tools/proposal_promotion_registry.json index 43b05398..206d5eea 100644 --- a/tools/proposal_promotion_registry.json +++ b/tools/proposal_promotion_registry.json @@ -2715,5 +2715,18 @@ "required_provenance_links": [ "source_draft_ref" ] + }, + { + "proposal_id": "0211", + "source_artifact_class": "working_draft", + "source_refs": [ + "docs/archive/proposal_sources/0211_durable_product_workspace_binding_evidence.md" + ], + "motivating_concern": "Platform and SpecSpace need durable product workspace routing and execution bindings, but SpecGraph initialization currently lacks a compact digest-bound descriptor of the workspace identity and relative layout it owns.", + "normalized_title": "Durable Product Workspace Binding Evidence", + "bounded_scope": "Emit digest-bound workspace-relative initialization evidence for Platform and SpecSpace consumers without selecting deployment URLs or state storage, executing managed operations, mutating canonical specs, writing Ontology packages, accepting terms, creating Git artifacts, or publishing raw intent and local paths.", + "required_provenance_links": [ + "source_draft_ref" + ] } ] diff --git a/tools/proposal_runtime_registry.json b/tools/proposal_runtime_registry.json index 73316bf3..ff9bc383 100644 --- a/tools/proposal_runtime_registry.json +++ b/tools/proposal_runtime_registry.json @@ -12041,5 +12041,54 @@ } ], "notes": "SpecGraph now emits workspace-bound fallback-free real-intake clarification outcomes and browser-answerable templates, including mandatory policy context, without publishing raw ideas or expanding execution authority." + }, + { + "proposal_id": "0211", + "posture": "bounded_runtime_followup", + "runtime_surfaces": [ + "docs/proposals/0211_durable_product_workspace_binding_evidence.md", + "docs/archive/proposal_sources/0211_durable_product_workspace_binding_evidence.md", + "tools/supervisor.py", + "tests/test_supervisor.py", + "docs/product_workspace_initialization_viewer_contract.md", + "docs/product_workspace_graph_versioning_roadmap.md", + "Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md", + "tools/README.md" + ], + "runtime_markers": [ + { + "path": "tools/supervisor.py", + "pattern": "PRODUCT_WORKSPACE_BINDING_EVIDENCE_PROPOSAL_ID = \"0211\"" + }, + { + "path": "tools/supervisor.py", + "pattern": "\"workspace_binding_evidence\": binding_evidence" + } + ], + "validation_markers": [ + { + "path": "tests/test_supervisor.py", + "pattern": "specgraph.product-workspace.binding-evidence.v0.1" + }, + { + "path": "tests/test_supervisor.py", + "pattern": "workspace_binding_evidence\"][\"status\"] == \"blocked\"" + } + ], + "observation_markers": [ + { + "path": "docs/product_workspace_graph_versioning_roadmap.md", + "pattern": "Implemented producer-side in proposal `0211`" + }, + { + "path": "Sources/SpecGraph/Documentation.docc/ProductWorkspacePilots.md", + "pattern": "Proposal `0211` adds a public-safe" + }, + { + "path": "tools/README.md", + "pattern": "Proposal 0211 adds digest-bound" + } + ], + "notes": "SpecGraph product workspace initialization now emits digest-bound workspace-relative binding evidence for Platform without taking ownership of external routing, state namespaces, execution roots, or Git operations." } ] diff --git a/tools/supervisor.py b/tools/supervisor.py index c4521d26..c7b4e4b1 100644 --- a/tools/supervisor.py +++ b/tools/supervisor.py @@ -10924,6 +10924,10 @@ def write_project_environment(environment: dict[str, Any]) -> Path: PRODUCT_WORKSPACE_INITIALIZATION_ARTIFACT_KIND = "product_workspace_initialization" +PRODUCT_WORKSPACE_BINDING_EVIDENCE_PROPOSAL_ID = "0211" +PRODUCT_WORKSPACE_BINDING_EVIDENCE_CONTRACT_REF = ( + "specgraph.product-workspace.binding-evidence.v0.1" +) PRODUCT_WORKSPACE_INITIALIZATION_SCHEMA_VERSION = 1 PRODUCT_WORKSPACE_INITIALIZATION_PROPOSAL_ID = "0054" PRODUCT_WORKSPACE_REQUIRED_DIRECTORIES = ( @@ -11330,6 +11334,56 @@ def initialize_product_workspace( root_intent_content_sha256 = "" workspace_status = "blocked" if has_errors else ("initialized" if created_paths else "ready") + binding_evidence: dict[str, Any] = { + "contract_ref": PRODUCT_WORKSPACE_BINDING_EVIDENCE_CONTRACT_REF, + "proposal_id": PRODUCT_WORKSPACE_BINDING_EVIDENCE_PROPOSAL_ID, + "status": "blocked" if has_errors else "ready", + "identity": { + "workspace_id": normalized_project_id, + "display_name": normalized_display_name, + "governance_profile": "product_workspace", + "repository_role": "product_spec_workspace", + }, + "layout": { + "root_reference": "workspace_relative", + "project_config_ref": PROJECT_CONFIG_RELATIVE_PATH, + "specs_root_ref": "specs", + "proposals_root_ref": "docs/proposals", + "runs_root_ref": "runs", + "supervisor_state_root_ref": ".specgraph", + }, + "project_config": { + "source_ref": PROJECT_CONFIG_RELATIVE_PATH, + "source_sha256": hashlib.sha256(config_text.encode("utf-8")).hexdigest(), + }, + "repository": { + "repository_role": "product_spec_workspace", + "workspace_identity": normalized_project_id, + "worktree_identity": f"product-workspace/{normalized_project_id}", + "creates_worktree": False, + }, + "privacy_boundary": { + "workspace_relative_refs_only": True, + "local_input_path_persisted": False, + "raw_root_intent_published": False, + }, + "authority_boundary": { + "report_only": True, + "may_execute_platform": False, + "may_mutate_canonical_specs": False, + "may_write_ontology_packages": False, + "may_accept_ontology_terms": False, + "may_create_git_commit": False, + "may_open_pull_request": False, + }, + } + binding_evidence["evidence_sha256"] = hashlib.sha256( + json.dumps( + binding_evidence, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + ).hexdigest() report: dict[str, Any] = { "artifact_kind": PRODUCT_WORKSPACE_INITIALIZATION_ARTIFACT_KIND, "schema_version": PRODUCT_WORKSPACE_INITIALIZATION_SCHEMA_VERSION, @@ -11342,6 +11396,7 @@ def initialize_product_workspace( "display_name": normalized_display_name, "governance_profile": "product_workspace", }, + "workspace_binding_evidence": binding_evidence, "workspace": { "root": ".", "root_reference": "workspace_relative",