specfuse-loop 0.3.7__tar.gz → 0.3.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {specfuse_loop-0.3.7/specfuse_loop.egg-info → specfuse_loop-0.3.9}/PKG-INFO +1 -1
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/pyproject.toml +1 -1
- specfuse_loop-0.3.9/specfuse/loop/data/VERSION +1 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/rules/correlation-ids.md +6 -6
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/rules/never-touch.md +10 -8
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/rules/result-contract.md +7 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/rules/security-boundaries.md +9 -10
- specfuse_loop-0.3.9/specfuse/loop/data/rules/verification-discipline.md +81 -0
- specfuse_loop-0.3.9/specfuse/loop/data/schemas/event.schema.json +78 -0
- specfuse_loop-0.3.9/specfuse/loop/data/schemas/events/initiative_created.schema.json +42 -0
- specfuse_loop-0.3.9/specfuse/loop/data/schemas/events/spec_issue_resolved.schema.json +36 -0
- specfuse_loop-0.3.9/specfuse/loop/data/schemas/events/spec_issue_routed.schema.json +31 -0
- specfuse_loop-0.3.9/specfuse/loop/data/schemas/events/spec_validated.schema.json +77 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/loop.py +20 -4
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/scaffold.py +2 -1
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/validate_event.py +28 -30
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9/specfuse_loop.egg-info}/PKG-INFO +1 -1
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse_loop.egg-info/SOURCES.txt +7 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_init_integration.py +7 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_post_pass_invariant.py +24 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_prepare_feature.py +38 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_roadmap_add_skill.py +93 -5
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_data_in_sync.py +6 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_init.py +12 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_manifest.py +1 -1
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_resources.py +6 -0
- specfuse_loop-0.3.9/tests/test_skills_vendored_in_sync.py +70 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_validate_event.py +52 -51
- specfuse_loop-0.3.7/specfuse/loop/data/VERSION +0 -1
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/LICENSE +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/NOTICE +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/README.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/setup.cfg +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/__init__.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/_miniyaml.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/adopt_feature.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/LEARNINGS.template.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/docs/concepts/architecture-addendum-gates-and-iterative-planning.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/docs/concepts/ralph-lineage.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/docs/getting-started.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/docs/methodology.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/docs/skills.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/gitignore.snippet +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/roadmap.template.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/templates/GATE.template.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/templates/PLAN.template.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/templates/WU.template.md +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/data/verification.yml.example +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/gate_eval.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/gh_backend.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/gh_features.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse/loop/lint_plan.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse_loop.egg-info/dependency_links.txt +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse_loop.egg-info/entry_points.txt +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse_loop.egg-info/requires.txt +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/specfuse_loop.egg-info/top_level.txt +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_adopt_feature.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_arm_gate_edits_uncommitted.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_attempt_outcome_emission.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_autosync.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_autosync_consent.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_autosync_firstrun.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_autosync_plugin.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_backend.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_bookkeeping_commit_crash_run.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_bookkeeping_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_closing_deliverable_guard.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_cost_tracking.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_deliverable_presence_gate.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_doctor.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_driver_integration.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_driver_lock.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_duration_tracking.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_empty_files_escalation.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_ensure_feature_branch.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_events_redaction.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_extra_gates.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_force_full_close.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gate_eval.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gate_eval_calibration.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gate_eval_intermediate_wiring.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gate_eval_terminal_wiring.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gh_backend.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_gh_features.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_git_env_isolation.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_hashed_denylist.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_hashed_denylist_ci.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_leak_findings_redaction.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_leak_scan.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_leak_scan_content.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_learnings_query.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_legacy_4wu_terminal_flips.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lifecycle_integration.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_bare_produces_path.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_close_intermediate.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_close_wu.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_correlation_id.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_correlation_id_close_intermediate.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_oracle_env.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_plan_errors.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_plan_next_draft.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_plan_relative_import.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_produces_driver_helper.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_sections.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_lint_task_graph_yaml_selection.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_auto_archive.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_caveman_preamble.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_close_intermediate.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_defaults_by_type.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_effort.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_failure_note_cap.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_files_changed_guard.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_gate_budget.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_model_alias.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_orchestration.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_reset_preserving_events.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_smoke_runner.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_unsandboxed.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_loop_zero_token_guard.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_migrate_legacy.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_miniyaml_equivalence.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_miniyaml_negative.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_planned_cost_lint.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_produces_field.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_result_block.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_roadmap_archive_skill.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_roadmap_row_parser.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_docs.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_seed_sanity.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_upgrade.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_scaffold_wiring.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_squash_commit_hook_crash.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_template_closing_shapes.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_terminal_flip_ownership.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_terminal_flips.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_untracked_feature_folder.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_upgrade_integration.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_upgrade_merge_gate.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_verdict_coupling.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_verify_empty_gate_set.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_version_consistency.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_version_skew.py +0 -0
- {specfuse_loop-0.3.7 → specfuse_loop-0.3.9}/tests/test_wu_execution_metadata.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specfuse-loop"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.9"
|
|
8
8
|
description = "Local-first executor for the Specfuse Plan + Work Unit gate-cycle methodology."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.3.9
|
|
@@ -10,9 +10,9 @@ feature folder, the WU file, the event log, the branch, and the commit history.
|
|
|
10
10
|
This rule defines the format, the surfaces the ID must appear on, how the next ID
|
|
11
11
|
is chosen, and what to do when you see — or are about to produce — a malformed one.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
correlation-ID scheme; this file is the operational reference
|
|
15
|
-
before emitting an ID.
|
|
13
|
+
`methodology.md` §1 (the gate-cycle canonical, in this shared substrate set) is
|
|
14
|
+
normative on the correlation-ID scheme; this file is the operational reference
|
|
15
|
+
every session reads before emitting an ID.
|
|
16
16
|
|
|
17
17
|
## Format
|
|
18
18
|
|
|
@@ -192,6 +192,6 @@ any downstream tooling filter and join on these IDs; a typo breaks the thread.
|
|
|
192
192
|
feature crosses a calendar boundary.
|
|
193
193
|
|
|
194
194
|
When you detect a malformed ID in your own work in progress, fix it before reporting.
|
|
195
|
-
When you detect one in an artifact that is already committed,
|
|
196
|
-
|
|
197
|
-
[
|
|
195
|
+
When you detect one in an artifact that is already committed, signal blocked
|
|
196
|
+
rather than silently rewriting history — the
|
|
197
|
+
[verification-discipline](verification-discipline.md) applies here too.
|
|
@@ -8,9 +8,10 @@ Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
|
8
8
|
Three categories of path are off-limits to every work-unit session, regardless of
|
|
9
9
|
unit type. If a unit's acceptance criteria or verification commands appear to
|
|
10
10
|
require modifying something in this list, that is an escalation condition, not a
|
|
11
|
-
license to proceed —
|
|
12
|
-
[`
|
|
13
|
-
boundary.
|
|
11
|
+
license to proceed — signal blocked per
|
|
12
|
+
[`verification-discipline.md`](verification-discipline.md), naming the
|
|
13
|
+
boundary. (How "blocked" is signalled is surface-specific — a `status: blocked`
|
|
14
|
+
RESULT on the loop, a `blocked_*` transition on the orchestrator.)
|
|
14
15
|
|
|
15
16
|
## 1. Generated code directories
|
|
16
17
|
|
|
@@ -29,7 +30,7 @@ leaves no trace of why.
|
|
|
29
30
|
|
|
30
31
|
When a generated file is wrong, the response is to change the spec or the generator
|
|
31
32
|
that produced it, not the file itself. If that is outside this unit's boundary,
|
|
32
|
-
|
|
33
|
+
signal blocked with the spec/generator change named.
|
|
33
34
|
|
|
34
35
|
## 2. Secrets and credentials
|
|
35
36
|
|
|
@@ -53,9 +54,10 @@ You must not:
|
|
|
53
54
|
|
|
54
55
|
## 3. `.git/` internals
|
|
55
56
|
|
|
56
|
-
The `.git/` directory is the repository's internal state.
|
|
57
|
-
operations for work units
|
|
58
|
-
|
|
57
|
+
The `.git/` directory is the repository's internal state. On the loop surface the
|
|
58
|
+
driver owns all git operations for work units and the session does not run `git`
|
|
59
|
+
at all; on the orchestrator surface an agent may use `git`/`gh` within its unit's
|
|
60
|
+
scope. Either way, never write under `.git/` directly:
|
|
59
61
|
|
|
60
62
|
- No edits to `.git/hooks/*` to bypass checks. If a hook is failing, the right
|
|
61
63
|
response is to diagnose the underlying issue.
|
|
@@ -77,5 +79,5 @@ response to a failing gate is to fix what it is flagging, not to silence it.
|
|
|
77
79
|
Before writing to any path, confirm it is not in one of the three categories above.
|
|
78
80
|
If you are uncertain whether a path is "generated" or "hand-written," the repo's
|
|
79
81
|
declaration is authoritative; if the declaration is missing or ambiguous, treat
|
|
80
|
-
the path as generated and
|
|
82
|
+
the path as generated and signal blocked rather than write. Silence at a
|
|
81
83
|
boundary is not permission.
|
|
@@ -18,6 +18,13 @@ wastes an attempt and teaches the next session nothing.
|
|
|
18
18
|
|
|
19
19
|
## The four-step discipline
|
|
20
20
|
|
|
21
|
+
This contract is the loop-surface expression of the neutral
|
|
22
|
+
[`verification-discipline.md`](verification-discipline.md) rule (vendored from the
|
|
23
|
+
Specfuse methodology core): it extends that rule's **state intent → act → verify →
|
|
24
|
+
report** cycle with the loop's concrete RESULT-block shape and status vocabulary.
|
|
25
|
+
The neutral rule is normative on *what* the discipline requires; this file is
|
|
26
|
+
normative on *how* the loop surface reports it.
|
|
27
|
+
|
|
21
28
|
Every session operates on the same cycle: **state intent, act, verify, report.** The
|
|
22
29
|
RESULT block is step 4, but it is only trustworthy when steps 1–3 ran first.
|
|
23
30
|
|
|
@@ -25,8 +25,7 @@ anything conventionally treated as a credential (see the enumeration in
|
|
|
25
25
|
This applies whether the value came from a real secret or was constructed.
|
|
26
26
|
- **Never commit.** Secrets files are excluded from commits categorically. A
|
|
27
27
|
`.env` in your working directory is a mistake; remove it before the driver
|
|
28
|
-
commits, not after. If you discover a secret already committed,
|
|
29
|
-
`status: blocked` immediately — do not attempt history rewriting.
|
|
28
|
+
commits, not after. If you discover a secret already committed, signal blocked immediately — do not attempt history rewriting.
|
|
30
29
|
- **Reference by name, not by value.** When a command needs a secret, pass it via
|
|
31
30
|
an environment variable reference (`$GITHUB_TOKEN`) rather than substituting
|
|
32
31
|
the value into the command line. Do not expand secret variables into log output;
|
|
@@ -51,15 +50,15 @@ is almost always a unit-definition problem, not a license to break the rule.
|
|
|
51
50
|
2. **Re-read the unit.** Verify you have understood the step correctly. Often the
|
|
52
51
|
unit describes how the *human* will verify, with the session doing an
|
|
53
52
|
upstream-only step.
|
|
54
|
-
3. **If the requirement is genuine,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
3. **If the requirement is genuine, signal blocked.** Name the privilege
|
|
54
|
+
required (e.g., "verification command requires reading `config/db-prod.env`")
|
|
55
|
+
as the blocked reason. Work halts and the human decides whether to adjust the
|
|
56
|
+
unit, run the step out-of-band, or provide a scoped credential. (The signal is
|
|
57
|
+
surface-specific — a `status: blocked` RESULT on the loop, a `blocked_*`
|
|
58
|
+
transition on the orchestrator.)
|
|
60
59
|
4. **Do not report the unit complete.** A verification that could not be run is
|
|
61
60
|
not a verification; a unit whose verification cannot be run is not done (see
|
|
62
|
-
[`
|
|
61
|
+
[`verification-discipline.md`](verification-discipline.md)).
|
|
63
62
|
|
|
64
63
|
The common mistake here is to "helpfully" substitute a weaker check for a
|
|
65
64
|
privileged one — "I couldn't run the secret-requiring command, so I inspected the
|
|
@@ -76,7 +75,7 @@ yours, and you are not expected to inspect or transmit them. The prohibition is
|
|
|
76
75
|
unit's scope* — not on using a credentialed tool to do the work the unit names.
|
|
77
76
|
|
|
78
77
|
If an authenticated tool returns an error that implies a credential problem
|
|
79
|
-
(expired token, permission denied, 401/403), stop and
|
|
78
|
+
(expired token, permission denied, 401/403), stop and signal blocked. Do
|
|
80
79
|
not attempt to re-authenticate, reconfigure the credential, or swap accounts.
|
|
81
80
|
|
|
82
81
|
## Log hygiene
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Specfuse Contributors
|
|
3
|
+
Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Rule: the verification discipline
|
|
7
|
+
|
|
8
|
+
Every unit of work runs the same four-step cycle: **state intent, act, verify,
|
|
9
|
+
report.** This is the surface-neutral core of the discipline. Each execution
|
|
10
|
+
surface expresses step 4 (report) in its own shape — the loop's RESULT block, the
|
|
11
|
+
orchestrator's `task_completed` event — but the cycle, and the honesty it demands,
|
|
12
|
+
are identical everywhere. This rule is core substrate; the surface-specific report
|
|
13
|
+
shapes (`result-contract.md` on the loop, `verify-before-report.md` on the
|
|
14
|
+
orchestrator) build on it and must not weaken it.
|
|
15
|
+
|
|
16
|
+
## The four steps
|
|
17
|
+
|
|
18
|
+
### 1. State intent
|
|
19
|
+
Before acting, state in one sentence what you are about to do and why it is the
|
|
20
|
+
next right step. If you cannot state it clearly, you are not ready to act; if the
|
|
21
|
+
intent disagrees with the unit's acceptance criteria, the unit is not what you
|
|
22
|
+
thought it was. Stay factual and present-tense.
|
|
23
|
+
|
|
24
|
+
### 2. Act
|
|
25
|
+
Perform the change within the scope declared in step 1. If the act expands beyond
|
|
26
|
+
the intent — "while I was here I also fixed X" — either revise the intent and
|
|
27
|
+
stay, or stop and leave X for another unit. Drift between intent and action is the
|
|
28
|
+
most common source of ambiguous diffs. The unit's **Do not touch** boundary is
|
|
29
|
+
binding.
|
|
30
|
+
|
|
31
|
+
### 3. Verify
|
|
32
|
+
Two generic meanings, both apply unless the unit narrows the scope:
|
|
33
|
+
|
|
34
|
+
- **Re-read the produced artifact.** After writing a file, read it back; after
|
|
35
|
+
editing a fixture, inspect the result. The Write/Edit tool reports success for
|
|
36
|
+
the action it took, not for the property you wanted. This catches silent
|
|
37
|
+
truncation, encoding surprises, and "I thought I wrote X but wrote Y."
|
|
38
|
+
- **Run the declared verification commands.** The unit names the checks that
|
|
39
|
+
decide done. Run them yourself first, in declared order, with full output.
|
|
40
|
+
Generic verifications — "I assume the tests still pass" — are not verifications.
|
|
41
|
+
The commands are the verification.
|
|
42
|
+
|
|
43
|
+
**Verification is the exit oracle.** The agent's own report is advisory; done is
|
|
44
|
+
decided by the checks passing, not by the agent's belief. If a check fails you are
|
|
45
|
+
in one of three situations:
|
|
46
|
+
|
|
47
|
+
1. **Correctable locally.** Fix the cause, re-run the **full** check set from the
|
|
48
|
+
top, then continue. A re-run that fixes the original failure but introduces a
|
|
49
|
+
new one is still a failed cycle.
|
|
50
|
+
2. **Spinning threshold reached** (typically three fresh attempts on one unit).
|
|
51
|
+
Signal blocked with precise evidence rather than spending the cycle guessing.
|
|
52
|
+
3. **Fundamentally blocked.** A spec ambiguity, generated code that must change, a
|
|
53
|
+
missing dependency — signal blocked immediately, naming the boundary.
|
|
54
|
+
|
|
55
|
+
### 4. Report
|
|
56
|
+
Report only what verification confirmed. An optimistic report is worse than none:
|
|
57
|
+
the driver (or merge gate) re-runs the checks, discovers the gap, the attempt is
|
|
58
|
+
wasted, and the next session inherits no useful evidence about why. The concrete
|
|
59
|
+
report shape is surface-specific — see `result-contract.md` (loop RESULT block) or
|
|
60
|
+
`verify-before-report.md` (orchestrator event). Both carry the same obligation:
|
|
61
|
+
no claim of completion without the verification evidence behind it.
|
|
62
|
+
|
|
63
|
+
## Blocked is a respectable outcome
|
|
64
|
+
|
|
65
|
+
Signalling blocked after one honest attempt is cheaper than three fresh attempts
|
|
66
|
+
chasing a completion the checks keep rejecting. When an escalation trigger fires,
|
|
67
|
+
or a unit's acceptance criteria appear to require crossing a boundary named in
|
|
68
|
+
`never-touch.md` or `security-boundaries.md`, **stop and signal blocked with a
|
|
69
|
+
precise reason naming the boundary** — do not work around it. Silence at a
|
|
70
|
+
boundary is not permission. How "blocked" is signalled is surface-specific (a
|
|
71
|
+
`status: blocked` RESULT on the loop; a `blocked_*` state transition plus
|
|
72
|
+
escalation on the orchestrator); the obligation to stop rather than improvise is
|
|
73
|
+
core.
|
|
74
|
+
|
|
75
|
+
## Honesty over optimism
|
|
76
|
+
|
|
77
|
+
The whole coordination model runs on the report being an honest claim about what
|
|
78
|
+
happened. When a session skips verification, the cost is not its own — it is paid
|
|
79
|
+
by the next session that inherits a broken assumption, the driver that wastes
|
|
80
|
+
attempts, and the human who reviews evidence that turns out to be wrong. State
|
|
81
|
+
intent. Act. Verify. Report. Every time.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://specfuse.dev/orchestrator/schemas/event.schema.json",
|
|
4
|
+
"title": "Event log entry",
|
|
5
|
+
"$comment": "Additive history (WU 2.14, F2.4): Phase 1 baseline enum — feature_created, spec_validated, task_graph_drafted, plan_ready, plan_generated, plan_approved, task_created, task_ready, task_started, task_completed, task_blocked, spec_issue_raised, override_applied, override_expired, human_escalation. Phase 2 additions: template_coverage_checked (WU 2.6), feature_state_changed (WU 2.9). Phase 3 additions: test_plan_authored (WU 3.2), qa_execution_completed + qa_execution_failed (WU 3.3), qa_regression_filed + qa_regression_resolved + escalation_resolved (WU 3.4; escalation_resolved is substrate — resolution_kind discriminator, first application retires the Phase 2 orphan inbox file FEAT-2026-0005-plan-review-cycle.md; closes carry-item F2.10), regression_suite_curated (WU 3.5; emitted by the qa-curation skill on curation-PR merge; envelope correlation_id task-level on the qa_curation task; affected_feature_correlation_ids in payload carries the cross-feature fanout). Phase 4 additions: spec_issue_resolved + spec_issue_routed (WU 4.5; emitted by the specs agent's spec-issue-triage skill — spec_issue_resolved when a spec-content fix is applied in /product/, spec_issue_routed when the issue is filed against the generator project). Per-type payload schemas for these types live under shared/schemas/events/. NOTE: plan_reingested was considered in WU 2.14 but deliberately not added — WU 2.11's template_coverage_checked re-chain on every Phase B success already makes Phase B observable in the event log. Reframe migration (docs/naming-convention.md, additive): correlation_id pattern widened from ^FEAT-\\d{4}-\\d{4}(/T\\d{2})?$ to ^(FEAT|INIT)-\\d{4}-\\d{4}(/F\\d{2})?(/T\\d{2})?$ to carry the initiative/feature/work-unit hierarchy (INIT-YYYY-NNNN[/FNN][/TNN]) alongside the legacy/component-local FEAT- forms; existing FEAT- entries validate unchanged. Per-type payload schemas under events/ broadened their FEAT-only correlation/path patterns the same way.",
|
|
6
|
+
"description": "A single JSONL entry in a feature's event log. Event log semantics are defined in orchestrator-architecture.md §7.3.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"timestamp",
|
|
11
|
+
"correlation_id",
|
|
12
|
+
"event_type",
|
|
13
|
+
"source",
|
|
14
|
+
"source_version",
|
|
15
|
+
"payload"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"timestamp": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"format": "date-time",
|
|
21
|
+
"description": "ISO 8601 timestamp with timezone offset (e.g. 2026-04-20T14:32:05Z or 2026-04-20T10:32:05-04:00).",
|
|
22
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$"
|
|
23
|
+
},
|
|
24
|
+
"correlation_id": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Correlation ID at any level of the unit hierarchy (docs/naming-convention.md): initiative INIT-YYYY-NNNN, dispatched feature INIT-YYYY-NNNN/FNN, work unit INIT-YYYY-NNNN/FNN/TNN; or the legacy/component-local feature FEAT-YYYY-NNNN and task FEAT-YYYY-NNNN/TNN. Existing FEAT- entries validate unchanged.",
|
|
27
|
+
"pattern": "^(FEAT|INIT)-\\d{4}-\\d{4}(/F\\d{2})?(/T\\d{2})?$"
|
|
28
|
+
},
|
|
29
|
+
"event_type": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Minimum event type set from architecture §7.3. New types must be registered in this schema before use.",
|
|
32
|
+
"enum": [
|
|
33
|
+
"initiative_created",
|
|
34
|
+
"feature_created",
|
|
35
|
+
"spec_validated",
|
|
36
|
+
"feature_graph_drafted",
|
|
37
|
+
"task_graph_drafted",
|
|
38
|
+
"template_coverage_checked",
|
|
39
|
+
"plan_ready",
|
|
40
|
+
"plan_generated",
|
|
41
|
+
"plan_approved",
|
|
42
|
+
"task_created",
|
|
43
|
+
"task_ready",
|
|
44
|
+
"task_started",
|
|
45
|
+
"task_completed",
|
|
46
|
+
"task_blocked",
|
|
47
|
+
"spec_issue_raised",
|
|
48
|
+
"override_applied",
|
|
49
|
+
"override_expired",
|
|
50
|
+
"human_escalation",
|
|
51
|
+
"feature_state_changed",
|
|
52
|
+
"test_plan_authored",
|
|
53
|
+
"qa_execution_completed",
|
|
54
|
+
"qa_execution_failed",
|
|
55
|
+
"qa_regression_filed",
|
|
56
|
+
"qa_regression_resolved",
|
|
57
|
+
"escalation_resolved",
|
|
58
|
+
"regression_suite_curated",
|
|
59
|
+
"spec_issue_resolved",
|
|
60
|
+
"spec_issue_routed"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"source": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Emitter of the event: a fixed role (`specs`, `pm`, `qa`, `config-steward`, `merge-watcher`), the loop's single-repo `driver`, a component agent scoped by repo (`component:<name>`), or `human`. The `driver` value is the loop execution surface's sole emitter (single-repo gate-cycle); the orchestrator surface uses the role-scoped values. For `component:<name>`, `<name>` is the bare component repository name with no owner prefix — the short form of the git remote (e.g. `component:api-sample`, not `component:acme/api-sample`). This matches the `source: component:<repo>` convention used across roles; the PM and QA agents emit events with `source: pm` and `source: qa` respectively (no repo suffix, since those roles are not scoped by component repo).",
|
|
66
|
+
"pattern": "^(human|driver|specs|pm|qa|config-steward|merge-watcher|component:[a-z0-9][a-z0-9-]*)$"
|
|
67
|
+
},
|
|
68
|
+
"source_version": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Agent configuration version in effect when the event was emitted. Format is semver-like but not enforced here; `human` events use the orchestration repo commit SHA or `n/a`.",
|
|
71
|
+
"minLength": 1
|
|
72
|
+
},
|
|
73
|
+
"payload": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"description": "Event-specific structured content. Per-type payload schemas will be added under /shared/schemas/events/ as they stabilize."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://specfuse.dev/orchestrator/schemas/events/initiative_created.schema.json",
|
|
4
|
+
"title": "initiative_created event payload",
|
|
5
|
+
"$comment": "Provenance: authored for the Model-B reframe (docs/naming-convention.md) as the initiative-level counterpart of feature_created. Emitted by the specs agent's initiative-intake skill (renamed from feature-intake) when a new INITIATIVE registry entry is created. Fields mirror feature_created with initiative_title in place of feature_title and an INIT-YYYY-NNNN correlation ID. feature_created is retained for legacy/feature-level use.",
|
|
6
|
+
"description": "Payload shape for the `initiative_created` event emitted by the specs agent during initiative intake. Captures the human-provided inputs (title, repos, autonomy) alongside the minted initiative correlation ID, duplicated from the envelope for payload self-containment.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"initiative_title",
|
|
11
|
+
"involved_repos",
|
|
12
|
+
"autonomy_default",
|
|
13
|
+
"correlation_id"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"initiative_title": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Human-provided title for the initiative. Free-form prose; used in the initiative registry's Description header. Not a machine identifier — the correlation ID is the stable reference.",
|
|
19
|
+
"minLength": 1
|
|
20
|
+
},
|
|
21
|
+
"involved_repos": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"description": "Component repositories this initiative spans, as `owner/repo` strings. Mirrors the `involved_repos` frontmatter field at creation time.",
|
|
24
|
+
"items": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"minItems": 1,
|
|
29
|
+
"uniqueItems": true
|
|
30
|
+
},
|
|
31
|
+
"autonomy_default": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Initiative-level autonomy default chosen by the human at intake. One of `auto`, `review`, or `supervised`.",
|
|
34
|
+
"enum": ["auto", "review", "supervised"]
|
|
35
|
+
},
|
|
36
|
+
"correlation_id": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The initiative-level correlation ID minted during intake (INIT-YYYY-NNNN). Matches the envelope `correlation_id` and the registry filename. Duplicated here for payload self-containment.",
|
|
39
|
+
"pattern": "^INIT-\\d{4}-\\d{4}$"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://specfuse.dev/orchestrator/schemas/events/spec_issue_resolved.schema.json",
|
|
4
|
+
"title": "spec_issue_resolved event payload",
|
|
5
|
+
"$comment": "Provenance: authored 2026-04-25 in WU 4.5 (spec-issue-triage skill). Fields: original_issue_correlation_id (required, task-level FEAT-YYYY-NNNN/TNN — the correlation ID from the triggering task that filed the spec issue), affected_files (required, array of paths edited in /product/ to resolve the issue), resolution_summary (required, string describing what was fixed and why). Emitted by the specs agent after fixing a spec-content problem in /product/ and re-validating the affected spec. The event type is new in Phase 4; per-type schema is authored alongside.",
|
|
6
|
+
"description": "Payload shape for the `spec_issue_resolved` event emitted by the specs agent's spec-issue-triage skill when a spec issue filed by a downstream agent (component, QA) is resolved by editing spec content under `/product/`. The resolution is a spec-side fix — the specs agent edits the affected spec file, re-validates, and emits this event. The original inbox file is archived to `/inbox/spec-issue/processed/`, never deleted.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"original_issue_correlation_id",
|
|
11
|
+
"affected_files",
|
|
12
|
+
"resolution_summary"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"original_issue_correlation_id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The task-level correlation ID from the triggering task that filed the spec issue (e.g. `FEAT-2026-0042/T09`). Links the resolution event back to the original `spec_issue_raised` event and the downstream task that discovered the problem. This is the task-level ID, not the feature-level ID, because spec issues are raised in the context of a specific task's work.",
|
|
18
|
+
"pattern": "^((FEAT-\\d{4}-\\d{4})|(INIT-\\d{4}-\\d{4}/F\\d{2}))/T\\d{2}$"
|
|
19
|
+
},
|
|
20
|
+
"affected_files": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"description": "Paths of spec files edited under `/product/` to resolve the issue, relative to the product specs repo root (e.g. `product/specs/inventory-api-bookmarks.yaml`). At least one file must be affected — a resolution that touches no files is not a resolution.",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
},
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"uniqueItems": true
|
|
29
|
+
},
|
|
30
|
+
"resolution_summary": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Human-readable summary of what was fixed and why. States the concrete change (e.g. 'Changed response status from 201 to 200 for the idempotent repeat case on POST /bookmarks') — enough for downstream agents and the human to understand the fix without re-reading the diff.",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://specfuse.dev/orchestrator/schemas/events/spec_issue_routed.schema.json",
|
|
4
|
+
"title": "spec_issue_routed event payload",
|
|
5
|
+
"$comment": "Provenance: authored 2026-04-25 in WU 4.5 (spec-issue-triage skill). Fields: original_issue_correlation_id (required, task-level FEAT-YYYY-NNNN/TNN — the correlation ID from the triggering task that filed the spec issue), target_project (required, owner/repo of the generator project the issue was filed against), filed_issue_reference (required, GitHub issue reference in owner/repo#N format). Emitted by the specs agent after determining a spec issue is a generator-template problem and filing a GitHub issue against the generator project. The event type is new in Phase 4; per-type schema is authored alongside.",
|
|
6
|
+
"description": "Payload shape for the `spec_issue_routed` event emitted by the specs agent's spec-issue-triage skill when a spec issue filed by a downstream agent (component, QA) is determined to be a generator-template problem rather than a spec-content problem. The specs agent files a GitHub issue against the generator project and emits this event. The original inbox file is archived to `/inbox/spec-issue/processed/`, never deleted. The generator feedback loop (Phase 5) will consume these issues; Phase 4 only files them.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"original_issue_correlation_id",
|
|
11
|
+
"target_project",
|
|
12
|
+
"filed_issue_reference"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"original_issue_correlation_id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The task-level correlation ID from the triggering task that filed the spec issue (e.g. `FEAT-2026-0042/T09`). Links the routing event back to the original `spec_issue_raised` event and the downstream task that discovered the problem.",
|
|
18
|
+
"pattern": "^((FEAT-\\d{4}-\\d{4})|(INIT-\\d{4}-\\d{4}/F\\d{2}))/T\\d{2}$"
|
|
19
|
+
},
|
|
20
|
+
"target_project": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "The GitHub `owner/repo` identifier of the generator project the issue was filed against (e.g. `acme/specfuse-generator`). Identifies which project owns the template that needs fixing.",
|
|
23
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$"
|
|
24
|
+
},
|
|
25
|
+
"filed_issue_reference": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The GitHub issue reference for the filed generator issue, in `owner/repo#N` format (e.g. `acme/specfuse-generator#42`). This is the durable reference that the generator feedback loop (Phase 5) will track.",
|
|
28
|
+
"pattern": "^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#\\d+$"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://specfuse.dev/orchestrator/schemas/events/spec_validated.schema.json",
|
|
4
|
+
"title": "spec_validated event payload",
|
|
5
|
+
"$comment": "Provenance: authored 2026-04-25 in WU 4.4 (spec-validation skill). Fields: feature_correlation_id (required, FEAT-YYYY-NNNN), pass (required, boolean), spec_files_checked (required, array of paths), errors (required, array of structured error objects — empty on pass), validator_version (required, string). The spec_validated event type has existed in event.schema.json since the Phase 1 baseline; this per-type schema is additive, preserving the Phase 1 freeze contract.",
|
|
6
|
+
"description": "Payload shape for the `spec_validated` event emitted by the specs agent on every validation run (pass or fail). The event is append-only: re-validation after a fix produces a new `spec_validated` event; prior failures are preserved in the event log. Consumers use the most recent `spec_validated` event to determine current validation status. The `errors` array is empty on a clean pass and populated with structured error objects on failure — each object carries enough context (file, line, message, severity) for the spec-validation skill to produce actionable remediation guidance without the human needing to consult raw validator output.",
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"required": [
|
|
10
|
+
"feature_correlation_id",
|
|
11
|
+
"pass",
|
|
12
|
+
"spec_files_checked",
|
|
13
|
+
"errors",
|
|
14
|
+
"validator_version"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"feature_correlation_id": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "The feature-level correlation ID of the feature whose specs were validated. Duplicated from the envelope `correlation_id` for payload self-containment — downstream consumers that index payloads without re-reading the envelope benefit from the redundancy.",
|
|
20
|
+
"pattern": "^(FEAT|INIT)-\\d{4}-\\d{4}(/F\\d{2})?$"
|
|
21
|
+
},
|
|
22
|
+
"pass": {
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"description": "True if all spec files passed validation with zero errors (warnings do not block); false if any file produced at least one error-severity finding."
|
|
25
|
+
},
|
|
26
|
+
"spec_files_checked": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"description": "Paths of every spec file the validator checked, relative to the product specs repo root (e.g. `product/specs/inventory-api-bookmarks.yaml`, `product/features/FEAT-2026-0042.md`). Order matches the order the validator processed them. At least one file must be checked — a validation run against zero files is a configuration error, not a pass.",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"minItems": 1
|
|
34
|
+
},
|
|
35
|
+
"errors": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"description": "Structured error objects from the validator. Empty array when `pass` is true; at least one element when `pass` is false. Each object identifies the file, line, raw message, and severity of the finding. The spec-validation skill's interpretation table translates each raw message into actionable remediation guidance for the human.",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"required": [
|
|
42
|
+
"file",
|
|
43
|
+
"line",
|
|
44
|
+
"message",
|
|
45
|
+
"severity"
|
|
46
|
+
],
|
|
47
|
+
"properties": {
|
|
48
|
+
"file": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Path of the spec file that produced this error, relative to the product specs repo root.",
|
|
51
|
+
"minLength": 1
|
|
52
|
+
},
|
|
53
|
+
"line": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"description": "Line number in the spec file where the error was detected. 0 when the validator does not report line-level granularity for this error type (e.g. a missing top-level field, a cross-file reference error).",
|
|
56
|
+
"minimum": 0
|
|
57
|
+
},
|
|
58
|
+
"message": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "The validator's raw error message. Preserved verbatim for audit — the spec-validation skill interprets this into human-readable guidance but keeps the original for traceability.",
|
|
61
|
+
"minLength": 1
|
|
62
|
+
},
|
|
63
|
+
"severity": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Severity level reported by the validator. `error` blocks validation (causes `pass: false`); `warning` is advisory and does not block — the validation can still pass with warnings.",
|
|
66
|
+
"enum": ["error", "warning"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"validator_version": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Version string of the Specfuse validator used for this run (e.g. `1.0.0`). Captured at invocation time via the validator's version-reporting mechanism, not cached across runs or eye-read from a config file.",
|
|
74
|
+
"minLength": 1
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -62,7 +62,7 @@ SPECFUSE_DIR = Path(".specfuse")
|
|
|
62
62
|
REPO_ROOT = SPECFUSE_DIR.parent
|
|
63
63
|
FEATURES_DIR = SPECFUSE_DIR / "features"
|
|
64
64
|
VERIFICATION_PATH = SPECFUSE_DIR / "verification.yml"
|
|
65
|
-
DRIVER_VERSION = "0.3.
|
|
65
|
+
DRIVER_VERSION = "0.3.9"
|
|
66
66
|
# Oldest scaffold layout this driver can drive. init.sh stamps the scaffold's own
|
|
67
67
|
# version into `.specfuse/VERSION`; check_scaffold_version() fails loud at startup if
|
|
68
68
|
# the consumer's scaffold is older than this, pointing at `specfuse upgrade`. Bump
|
|
@@ -880,8 +880,19 @@ def prepare_feature(feat_fm: dict, feature_dir: "Path", feature_id: str) -> None
|
|
|
880
880
|
"""
|
|
881
881
|
branch = feat_fm.get("branch")
|
|
882
882
|
ensure_feature_branch(feat_fm, feature_dir)
|
|
883
|
-
|
|
884
|
-
|
|
883
|
+
# Stage the feature folder AND the roadmap surfaces draft-feature wrote but
|
|
884
|
+
# left uncommitted (#127). The roadmap row is part of the feature's initial
|
|
885
|
+
# state; if it isn't folded into this scaffold commit, the first WU's
|
|
886
|
+
# per-attempt `git reset --hard head_before` discards it and terminal close
|
|
887
|
+
# fails with roadmap_row_not_done ~20 minutes later. ensure_feature_branch
|
|
888
|
+
# has already vetted the dirty set down to the expected /pick-feature flips.
|
|
889
|
+
repo_root = Path(git("rev-parse", "--show-toplevel"))
|
|
890
|
+
add_paths = [str(feature_dir)]
|
|
891
|
+
for rel in (".specfuse/roadmap.md", ".specfuse/roadmap-archive.md"):
|
|
892
|
+
if (repo_root / rel).exists():
|
|
893
|
+
add_paths.append(str(repo_root / rel))
|
|
894
|
+
git("add", "--", *add_paths)
|
|
895
|
+
staged = git("status", "--porcelain", "--", *add_paths)
|
|
885
896
|
if staged:
|
|
886
897
|
git("commit", "-m", f"chore: scaffold feature {feature_id}")
|
|
887
898
|
print(f"Prepared: committed feature folder on branch '{branch}'.")
|
|
@@ -2927,7 +2938,12 @@ def assert_terminal_flips_fired(
|
|
|
2927
2938
|
if not parsed:
|
|
2928
2939
|
return (
|
|
2929
2940
|
False,
|
|
2930
|
-
f"roadmap_row_not_done: row for {feature_id} not found"
|
|
2941
|
+
f"roadmap_row_not_done: row for {feature_id} not found in "
|
|
2942
|
+
f"{roadmap_path}. /draft-feature writes this row but leaves it "
|
|
2943
|
+
f"uncommitted; if it was dropped before dispatch, restore it and "
|
|
2944
|
+
f"re-commit. `specfuse-loop --prepare` folds the row into the "
|
|
2945
|
+
f"scaffold commit — dispatch through it so the row survives the "
|
|
2946
|
+
f"per-attempt reset.",
|
|
2931
2947
|
)
|
|
2932
2948
|
row_status = parsed["columns"].get("Status", "").strip()
|
|
2933
2949
|
if row_status != "done":
|
|
@@ -164,6 +164,7 @@ _RULES_BLOCK = (
|
|
|
164
164
|
"@.specfuse/rules/correlation-ids.md\n"
|
|
165
165
|
"@.specfuse/rules/never-touch.md\n"
|
|
166
166
|
"@.specfuse/rules/security-boundaries.md\n"
|
|
167
|
+
"@.specfuse/rules/verification-discipline.md\n"
|
|
167
168
|
)
|
|
168
169
|
|
|
169
170
|
_RULES_SENTINEL = "@.specfuse/rules/result-contract.md"
|
|
@@ -304,7 +305,7 @@ def _parse_version(v: str) -> tuple[int, int, int]:
|
|
|
304
305
|
|
|
305
306
|
|
|
306
307
|
# Seed relpaths overlaid verbatim into .specfuse/<relpath> on upgrade
|
|
307
|
-
_VERSIONED_OVERLAY_PREFIXES: tuple[str, ...] = ("templates/", "rules/", "docs/")
|
|
308
|
+
_VERSIONED_OVERLAY_PREFIXES: tuple[str, ...] = ("templates/", "rules/", "docs/", "schemas/")
|
|
308
309
|
_VERSIONED_OVERLAY_EXACT: frozenset[str] = frozenset({"verification.yml.example", "VERSION"})
|
|
309
310
|
|
|
310
311
|
# Versioned directories whose contents are pruned on upgrade
|