Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ SPECSPACE_REAL_IDEA_ANSWER_INTAKE_SESSION ?= $(REAL_IDEA_SMOKE_RUN_DIR)/user_ide
SPECSPACE_REAL_IDEA_ANSWER_IMPORT_PREVIEW_OUTPUT ?= $(REAL_IDEA_SMOKE_RUN_DIR)/specspace_real_idea_answer_import_preview.json
SPECSPACE_REAL_IDEA_VALIDATED_ANSWERS_OUTPUT ?= $(REAL_IDEA_SMOKE_RUN_DIR)/idea_intake_clarification_answers.json
REAL_IDEA_ANSWER_CONTINUATION_REPORT_OUTPUT ?= $(REAL_IDEA_SMOKE_RUN_DIR)/real_idea_answer_continuation_report.json
REAL_IDEA_ANSWER_CONTINUATION_WORKSPACE_ID ?=
SPECSPACE_REAL_IDEA_ENTRY_REQUESTS ?= $(REAL_IDEA_SMOKE_RUN_DIR)/real_idea_entry_requests.json
SPECSPACE_REAL_IDEA_ENTRY_WORKSPACE_ID ?=
SPECSPACE_REAL_IDEA_ENTRY_WORKSPACE_ID_ARG := $(if $(strip $(SPECSPACE_REAL_IDEA_ENTRY_WORKSPACE_ID)),--workspace-id "$(SPECSPACE_REAL_IDEA_ENTRY_WORKSPACE_ID)",)
Expand Down Expand Up @@ -762,7 +763,7 @@ real-idea-smoke-continue:

.PHONY: real-idea-smoke-answer-template
real-idea-smoke-answer-template:
@$(PYTHON) tools/real_idea_answer_authoring.py template --run-dir "$(REAL_IDEA_SMOKE_RUN_DIR)" --stage "$(REAL_IDEA_ANSWER_AUTHORING_STAGE)" $(REAL_IDEA_ANSWER_AUTHORING_REQUESTS_ARG) --output "$(REAL_IDEA_ANSWER_TEMPLATE_OUTPUT)" --report "$(REAL_IDEA_ANSWER_AUTHORING_REPORT_OUTPUT)"
@$(PYTHON) tools/real_idea_answer_authoring.py template --run-dir "$(REAL_IDEA_SMOKE_RUN_DIR)" --stage "$(REAL_IDEA_ANSWER_AUTHORING_STAGE)" $(REAL_IDEA_ANSWER_AUTHORING_REQUESTS_ARG) --output "$(REAL_IDEA_ANSWER_TEMPLATE_OUTPUT)" --report "$(REAL_IDEA_ANSWER_AUTHORING_REPORT_OUTPUT)" --strict

.PHONY: real-idea-smoke-validate-answers
real-idea-smoke-validate-answers:
Expand Down Expand Up @@ -848,6 +849,29 @@ real-idea-intake-continue-from-specspace-answers:
IDEA_TO_SPEC_CLARIFICATION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_to_spec_clarification_requests.json" \
CANDIDATE_SPEC_MATERIALIZATION_OUTPUT_DIR="$(REAL_IDEA_SMOKE_RUN_DIR)/materialized_candidate_specs" \
CANDIDATE_SPEC_MATERIALIZATION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/candidate_spec_materialization_report.json" \
IDEA_TO_SPEC_PROMOTION_GATE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_to_spec_promotion_gate.json" \
ACTIVE_IDEA_TO_SPEC_CANDIDATE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/active_idea_to_spec_candidate.json"

.PHONY: real-idea-intake-continue-without-answers
real-idea-intake-continue-without-answers:
@$(PYTHON) tools/real_idea_answer_authoring.py verify-no-clarification \
--run-dir "$(REAL_IDEA_SMOKE_RUN_DIR)" \
--template "$(REAL_IDEA_SMOKE_RUN_DIR)/real_idea_answer_template.json" \
--workspace-id "$(REAL_IDEA_ANSWER_CONTINUATION_WORKSPACE_ID)"
@$(MAKE) real-idea-intake-active-candidate \
USER_IDEA_INTAKE_SESSION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/user_idea_intake_session.json" \
CLARIFIED_USER_IDEA_INTAKE_SESSION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/.no_clarified_intake_session.json" \
USER_IDEA_INTAKE_SESSION_SOURCE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/user_idea_intake_source.json" \
INTAKE_SESSION_CANDIDATE_SOURCE_REPORT_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/intake_session_candidate_source_report.json" \
USER_IDEA_EVENT_STORMING_SEED_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_event_storming_seed.json" \
IDEA_EVENT_STORMING_INTAKE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_event_storming_intake.json" \
PRODUCT_WORKSPACE_CANDIDATE_SEED_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/candidate_spec_graph_seed.json" \
CANDIDATE_SPEC_GRAPH_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/candidate_spec_graph.json" \
PRE_SIB_COHERENCE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/pre_sib_coherence_report.json" \
CANDIDATE_REPAIR_LOOP_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/candidate_repair_loop_report.json" \
IDEA_TO_SPEC_CLARIFICATION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_to_spec_clarification_requests.json" \
CANDIDATE_SPEC_MATERIALIZATION_OUTPUT_DIR="$(REAL_IDEA_SMOKE_RUN_DIR)/materialized_candidate_specs" \
CANDIDATE_SPEC_MATERIALIZATION_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/candidate_spec_materialization_report.json" \
IDEA_TO_SPEC_PROMOTION_GATE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/idea_to_spec_promotion_gate.json" \
ACTIVE_IDEA_TO_SPEC_CANDIDATE_OUTPUT="$(REAL_IDEA_SMOKE_RUN_DIR)/active_idea_to_spec_candidate.json"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,9 +732,10 @@ authority expansion.
The next producer-side slices should keep using Idea Maturity and repair
contracts instead of creating a second depth metric:

1. real intake should emit browser-answerable clarification templates, or an
explicit `clarification_not_required` state, so SpecSpace product demos do
not need deterministic answer fallback fixtures;
1. proposal `0210` makes real-intake clarification fallback-free:
workspace-bound templates now publish `answers_required`,
`clarification_not_required`, or `clarification_blocked`, and mandatory
policy context is represented as an ordinary browser-answerable target;
2. candidate graphs should keep stable machine ids while publishing
human-friendly display aliases for generated nodes, candidate overview, PR
artifacts, and topology/readiness panels;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 0210 Fallback-Free Real Intake Clarification Templates

## Status

Draft / producer-side real-intake clarification contract slice.

## Summary

The UI-started product demo currently falls back to deterministic clarification
fixtures when SpecGraph's real-intake artifacts are not usable in a non-demo
workspace. The underlying request and answer-template tools already produce
typed targets, but the artifacts do not carry enough workspace identity to be
selected safely, policy context is not part of the mandatory intake questions,
and an empty or unsupported template is not distinguished from a legitimate
"no clarification required" outcome.

This slice makes the existing answer-template contract authoritative enough for
browser consumers without adding a second clarification protocol.

## Decision

The existing `idea_to_spec_clarification_requests` and
`real_idea_answer_template` artifacts carry:

- `workspace_id`, `candidate_id`, and a sanitized `workspace` identity;
- a stable digest binding the template to its clarification request source;
- one explicit `clarification_outcome`:
- `answers_required`;
- `clarification_not_required`;
- `clarification_blocked`;
- typed browser-answerable targets for every mandatory request;
- public-safe findings when a mandatory request cannot be represented.

Real intake requires policy context alongside actors, commands, domain events,
and constraints. Missing policy context therefore becomes an ordinary
`event_storming_hints.policies` clarification target.

A ready complete intake emits `clarification_not_required` with no answer
targets. A mandatory request with an unsupported action or value shape emits
`clarification_blocked` and fails strict template generation. A blocked
generation does not overwrite an existing ready template.

## Authority Boundary

This slice does not:

- execute a prompt agent or infer product answers;
- auto-fill clarification answers;
- mutate canonical specs or accepted candidate artifacts;
- write Ontology packages or lockfiles;
- accept Ontology terms;
- approve candidates or change promotion gates;
- create Git branches, commits, or pull requests;
- publish a read model;
- publish raw idea text, prompts, model output, or operator notes.

SpecSpace remains an operator-answer surface. Platform remains the controlled
execution boundary.

## Acceptance Criteria

- Incomplete real ideas produce workspace-bound browser-answerable answer
templates without deterministic fixture fallback.
- Complete real ideas produce an explicit `clarification_not_required`
outcome and can continue safely without an answer set.
- Unsupported mandatory clarification requests produce
`clarification_blocked`, strict failure, and public-safe findings.
- Template source refs and digests bind to the current workspace/session.
- Stale or cross-workspace artifacts cannot be selected as current intake
evidence.
- Failed generation does not clobber a pre-existing ready template.
- Raw idea text does not appear in request, template, report, or public bundle
artifacts.
- The standard SpecSpace product demo completes with
`clarification_fallback_used=false`.

13 changes: 6 additions & 7 deletions docs/product_workspace_graph_versioning_roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -1640,13 +1640,12 @@ contracts. Do not add another competing "depth score" or product-quality metric.
The next producer-side work should instead make the existing idea-to-spec flow
more reliable and easier for SpecSpace to present:

1. **Fallback-free real-intake clarification templates.** Real intake should
emit either a browser-answerable `real_idea_answer_template` or an explicit
`clarification_not_required` state. The current SpecSpace product-demo
fallback remains a temporary local harness policy, not a producer contract.
A strict UI-started demo should be able to pass without deterministic answer
fixtures while still proving that the candidate came from the new workspace
rather than the Team Decision Log pilot.
1. **Fallback-free real-intake clarification templates.** Implemented
producer-side in proposal `0210`. Real intake now emits a workspace-bound
browser-answerable `real_idea_answer_template`, an explicit
`clarification_not_required` outcome, or a strict
`clarification_blocked` finding. SpecSpace can remove its deterministic
product-demo fallback and rely on producer evidence.
2. **Human-friendly candidate display aliases.** Keep stable machine ids for
refs, materialization, and promotion paths, but add deterministic display
aliases or readable labels for long generated candidate node ids. Candidate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 0210 Fallback-Free Real Intake Clarification Templates

## Status

Draft / producer-side real-intake clarification contract slice.

## Summary

The UI-started product demo currently falls back to deterministic clarification
fixtures when SpecGraph's real-intake artifacts are not usable in a non-demo
workspace. The underlying request and answer-template tools already produce
typed targets, but the artifacts do not carry enough workspace identity to be
selected safely, policy context is not part of the mandatory intake questions,
and an empty or unsupported template is not distinguished from a legitimate
"no clarification required" outcome.

This slice makes the existing answer-template contract authoritative enough for
browser consumers without adding a second clarification protocol.

## Decision

The existing `idea_to_spec_clarification_requests` and
`real_idea_answer_template` artifacts carry:

- `workspace_id`, `candidate_id`, and a sanitized `workspace` identity;
- a stable digest binding the template to its clarification request source;
- one explicit `clarification_outcome`:
- `answers_required`;
- `clarification_not_required`;
- `clarification_blocked`;
- typed browser-answerable targets for every mandatory request;
- public-safe findings when a mandatory request cannot be represented.

Real intake requires policy context alongside actors, commands, domain events,
and constraints. Missing policy context therefore becomes an ordinary
`event_storming_hints.policies` clarification target.

A ready complete intake emits `clarification_not_required` with no answer
targets. A mandatory request with an unsupported action or value shape emits
`clarification_blocked` and fails strict template generation. A blocked
generation does not overwrite an existing ready template.

## Authority Boundary

This slice does not:

- execute a prompt agent or infer product answers;
- auto-fill clarification answers;
- mutate canonical specs or accepted candidate artifacts;
- write Ontology packages or lockfiles;
- accept Ontology terms;
- approve candidates or change promotion gates;
- create Git branches, commits, or pull requests;
- publish a read model;
- publish raw idea text, prompts, model output, or operator notes.

SpecSpace remains an operator-answer surface. Platform remains the controlled
execution boundary.

## Acceptance Criteria

- Incomplete real ideas produce workspace-bound browser-answerable answer
templates without deterministic fixture fallback.
- Complete real ideas produce an explicit `clarification_not_required`
outcome and can continue safely without an answer set.
- Unsupported mandatory clarification requests produce
`clarification_blocked`, strict failure, and public-safe findings.
- Template source refs and digests bind to the current workspace/session.
- Stale or cross-workspace artifacts cannot be selected as current intake
evidence.
- Failed generation does not clobber a pre-existing ready template.
- Raw idea text does not appear in request, template, report, or public bundle
artifacts.
- The standard SpecSpace product demo completes with
`clarification_fallback_used=false`.

11 changes: 11 additions & 0 deletions tests/fixtures/idea_intake_clarification/answers_ready.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@
]
}
},
{
"answer_kind": "answer_question",
"authority": "operator_approved",
"request_id": "clarification.intake.question-event-storming-policies",
"status": "accepted_for_candidate",
"value": {
"entries": [
"Decision Review Policy"
]
}
},
{
"answer_kind": "answer_question",
"authority": "operator_approved",
Expand Down
12 changes: 12 additions & 0 deletions tests/fixtures/user_idea_intake_session/raw_idea_ready.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@
"name": "Case Escalated"
}
],
"policies": [
{
"command_refs": [
"command.escalate-case"
],
"id": "policy.escalation-review",
"name": "Escalation Review Policy",
"trigger_event_refs": [
"event.case-escalated"
]
}
],
"risks": [
{
"id": "risk.unowned-escalation",
Expand Down
6 changes: 3 additions & 3 deletions tests/test_idea_intake_clarification_rerun.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def build_intake_requests(tmp_path: Path, session: Path) -> Path:
text=True,
)
assert result.returncode == 0, result.stderr
assert load_json(requests)["summary"]["blocking_request_count"] == 9
assert load_json(requests)["summary"]["blocking_request_count"] == 10
return requests


Expand Down Expand Up @@ -174,7 +174,7 @@ def test_idea_intake_clarification_rerun_materializes_clarified_session(
rerun_report = load_json(report)
assert rerun["artifact_kind"] == "idea_intake_answer_rerun_input"
assert rerun["readiness"]["ready"] is True
assert len(rerun["accepted_answer_targets"]) == 9
assert len(rerun["accepted_answer_targets"]) == 10
assert session_payload["readiness"]["review_state"] == "ready_for_event_storming_intake"
assert not clarified_source.exists()
assert rerun_report["summary"]["ready_for_candidate_source"] is True
Expand Down Expand Up @@ -358,7 +358,7 @@ def test_real_idea_intake_clarification_requests_preserves_existing_session(
request_payload = load_json(requests)
assert after == before
assert after["workspace"]["candidate_id"] == "team-decision-log"
assert request_payload["summary"]["blocking_request_count"] == 9
assert request_payload["summary"]["blocking_request_count"] == 10


def test_real_idea_intake_active_candidate_target_builds_seed_first(
Expand Down
33 changes: 29 additions & 4 deletions tests/test_idea_to_spec_clarification_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
INTAKE_TOOL_PATH = ROOT / "tools" / "idea_event_storming_intake.py"
REPAIR_TOOL_PATH = ROOT / "tools" / "candidate_repair_loop.py"
SESSION_FIXTURE = ROOT / "tests" / "fixtures" / "user_idea_intake_session"
READY_FIXTURE = SESSION_FIXTURE / "raw_idea_ready.json"
NEEDS_CLARIFICATION_FIXTURE = SESSION_FIXTURE / "raw_idea_needs_clarification.json"
INTAKE_REVIEW_REQUIRED = (
ROOT / "tests" / "fixtures" / "idea_event_storming_intake" / "idea_review_required.json"
Expand Down Expand Up @@ -100,18 +101,42 @@ def test_clarification_requests_collects_intake_session_questions() -> None:
assert report["canonical_mutations_allowed"] is False
assert report["tracked_artifacts_written"] is False
assert report["readiness"]["ready"] is False
assert report["summary"]["request_count"] == 9
assert report["summary"]["blocking_request_count"] == 9
assert report["summary"]["request_count"] == 10
assert report["summary"]["blocking_request_count"] == 10
assert report["summary"]["review_required_request_count"] == 0
assert report["clarification_outcome"] == "answers_required"
assert report["workspace_id"] == "i-want-a-small-tool-for-team-decisions"
assert report["candidate_id"] == "i-want-a-small-tool-for-team-decisions"
assert "0210" in report["contract_extensions"]
assert "clarification.intake.question-active-frame-ontology-refs" in request_ids(report)
assert "clarification.intake.question-event-storming-actors" in request_ids(report)
assert "clarification.intake.question-event-storming-policies" in request_ids(report)
assert request_kinds(report) == {"missing_context", "missing_event_storming_context"}
dumped = json.dumps(report)
raw_text = load_json(NEEDS_CLARIFICATION_FIXTURE)["idea"]["text"]
assert raw_text not in dumped
assert "private prompt trace" not in dumped


def test_clarification_requests_marks_ready_session_not_required() -> None:
session_module = load_module(SESSION_TOOL_PATH, "user_idea_session_without_clarifications")
clarification_module = load_module(TOOL_PATH, "clarification_requests_not_required")
session, source = session_module.build_user_idea_intake_session(
load_json(READY_FIXTURE),
source_path=READY_FIXTURE,
)

report = clarification_module.build_idea_to_spec_clarification_requests(
user_idea_intake_session=session,
)

assert source is not None
assert report["clarification_outcome"] == "clarification_not_required"
assert report["readiness"]["ready"] is True
assert report["summary"]["request_count"] == 0
assert report["workspace_id"] == "support-triage-log"


def test_clarification_requests_project_repair_context_actions() -> None:
repair_module = load_module(REPAIR_TOOL_PATH, "repair_loop_for_clarifications")
clarification_module = load_module(TOOL_PATH, "clarification_requests_for_repair_loop")
Expand Down Expand Up @@ -242,7 +267,7 @@ def test_clarification_requests_cli_writes_output(tmp_path: Path) -> None:
assert result.returncode == 0
report = load_json(output_path)
assert report["artifact_kind"] == "idea_to_spec_clarification_requests"
assert report["summary"]["request_count"] == 14
assert report["summary"]["request_count"] == 15
assert "clarification_required" in result.stdout
repair_request = request_by_id(
report,
Expand Down Expand Up @@ -484,5 +509,5 @@ def test_product_workspace_raw_idea_writes_clarification_before_abort(tmp_path:
assert output_path.exists()
report = load_json(output_path)
assert report["readiness"]["ready"] is False
assert report["summary"]["blocking_request_count"] == 9
assert report["summary"]["blocking_request_count"] == 10
assert not (tmp_path / "source.json").exists()
Loading