Skip to content

Commit 43a29bc

Browse files
committed
Harden owner-friction and event-spine truth gates
Add direct compliance-mode audit consumer regression tests, section-scoped current-truth framing validation, and an explicit operator-friction promotion contract. Keep historical evidence mentions legal while making current front-door drift fail validation. Constraint: Current direction is owner-operated harness-first; owner friction may be promoted only with source links and closure evidence. Rejected: Whole-file keyword bans | they false-positive on historical evidence and negated non-goal language. Confidence: high Scope-risk: narrow Directive: Future owner-friction work must preserve source-log, promoted-artifact, and closure-evidence links before claiming closed status. Tested: .venv/bin/python -m pytest tests/integration/test_audit_sink_isolation.py tests/acceptance/test_hook_lifecycle_flow.py tests/test_event_spine_wiring.py tests/lifecycle/test_run_event_spine.py tests/test_docs_consistency.py -q; .venv/bin/python scripts/validate_docs_consistency.py; .venv/bin/python scripts/validate_event_spine_wiring.py; .venv/bin/python scripts/detect_stale_plans.py; .venv/bin/ruff check scripts/validate_docs_consistency.py scripts/audit_test_quality.py tests/test_docs_consistency.py tests/lifecycle/test_run_event_spine.py; .venv/bin/ruff format --check scripts/validate_docs_consistency.py scripts/audit_test_quality.py tests/test_docs_consistency.py tests/lifecycle/test_run_event_spine.py; .venv/bin/mypy scripts/validate_docs_consistency.py scripts/audit_test_quality.py tests/test_docs_consistency.py tests/lifecycle/test_run_event_spine.py; git diff --check Not-tested: Full repository test suite.
1 parent 8e0361b commit 43a29bc

7 files changed

Lines changed: 240 additions & 10 deletions

File tree

docs/generated/docs-inventory.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ Do not edit this file manually — regenerate instead.
488488
| `processes/opencode-gap-watch.md` | working | 2678 | `e6ed2ad1d5b7` |
489489
| `processes/postfix-reaudit-process.md` | working | 3882 | `14fc6168be6d` |
490490
| `processes/quarterly-competitor-refresh.md` | working | 2818 | `6d6be956fa26` |
491-
| `processes/signal-to-acceptance-gap.md` | working | 7776 | `916d78ec2d67` |
491+
| `processes/signal-to-acceptance-gap.md` | working | 8459 | `fa7eb4937de8` |
492492
| `product-contract.md` | constitution | 3926 | `c3eb2e2bd505` |
493493
| `proposals/001-circular-dependencies.md` | working | 7916 | `87e27acd99d7` |
494494
| `proposals/002-approval-manager-refactoring.md` | working | 10851 | `0bf294f88177` |
@@ -592,7 +592,7 @@ Do not edit this file manually — regenerate instead.
592592
| `work-log/m4-approval-sliceB-blocked-2026-06-13.md` | archive | 7347 | `3981ed82bc08` |
593593
| `work-log/m4-budget-stays-inline-2026-06-13.md` | archive | 5727 | `0e7a6ee74954` |
594594
| `work-log/m5-hooks-observability-only-2026-06-13.md` | archive | 5000 | `8a87eaee4d15` |
595-
| `work-log/operator-friction-log.md` | working | 3910 | `6dd6635ed859` |
595+
| `work-log/operator-friction-log.md` | working | 4257 | `70fe078506bf` |
596596
| `work-log/p0-p1-governance-implementation-ledger-2026-06-11.md` | archive | 5212 | `0b72cd69de32` |
597597
| `work-log/parallel-phase-0-implementation-report-2026-06-04.md` | archive | 13181 | `098186167459` |
598598
| `work-log/phase-0-governance-closure-report-2026-06-04.md` | archive | 4309 | `2af44267aaf4` |

docs/processes/signal-to-acceptance-gap.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**Status:** Active process
44
**Frequency:** Per signal (continuous), reviewed quarterly
55
**Owner:** TBD
6-
**Last reviewed:** 2026-06-05
6+
**Last reviewed:** 2026-06-14
77

88
## Purpose
99

@@ -85,6 +85,19 @@ operator friction log until the owner validates or rejects them. Signals routed
8585
to **Defer** do not create new surveys or strategy docs unless a documented
8686
review trigger applies.
8787

88+
### Operator-Friction Promotion
89+
90+
Owner-written entries in `docs/work-log/operator-friction-log.md` may promote
91+
directly to **Adopt** when they expose a real harness gap in approval, audit,
92+
rollback, cost, receipt, state, validation, or ergonomics. Hypothesis entries
93+
from competitor or community signals must be owner-validated before promotion.
94+
95+
Every promoted friction entry must carry its source log anchor into the ticket
96+
or acceptance gap, and the friction log's `Promoted to` field must point back to
97+
that artifact. A closed promoted entry must also link closure evidence: commit,
98+
test, or document. If either link is missing, the work is still open even when a
99+
patch exists.
100+
88101
### Phase 3: Acceptance Gap Filing
89102

90103
For signals promoted to acceptance gaps:

docs/work-log/operator-friction-log.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Use this log when:
3636
friction in real use or repository evidence shows a governance gap.
3737
- A closed entry must cite the commit, test, or document that resolved it; `n/a`
3838
is allowed only for open or rejected entries.
39+
- A promoted entry must cite the ticket or acceptance-gap artifact in
40+
`Promoted to`; `n/a` is allowed only before promotion or for rejected entries.
41+
- Closing promoted work requires both links: the downstream artifact in
42+
`Promoted to` and the commit/test/doc in `Closure evidence`.
3943
- Do not use this log for public positioning, competitor ranking, or feature
4044
parity requests.
4145
- Agents may ask the intake prompts below, but must not answer them on the
@@ -77,8 +81,8 @@ fields only when the entry is promoted to work.
7781
- **Actual:** What happened instead.
7882
- **Harness impact:** approval | audit | rollback | cost | receipt | state | validation | ergonomics
7983
- **Status:** open | closed | rejected
80-
- **Closure evidence:** commit/test/doc link, or `n/a`
81-
- **Promoted to:** ticket/acceptance-gap link, or `n/a`
84+
- **Closure evidence:** commit/test/doc link; `n/a` only while open or rejected
85+
- **Promoted to:** ticket/acceptance-gap link; `n/a` only before promotion or when rejected
8286
```
8387

8488
## Owner Evidence Entries

scripts/audit_test_quality.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,13 @@ def classify_test_type(file_path: Path, source_text: str | None = None) -> str:
350350

351351
def metrics_to_json(all_metrics: list[FileMetrics], total_nodes: int) -> dict[str, Any]:
352352
"""Convert metrics to JSON schema."""
353-
files_data = []
353+
files_data: list[dict[str, Any]] = []
354354

355355
for metrics in all_metrics:
356356
if metrics.has_syntax_error:
357357
continue
358358

359-
file_data = {
359+
file_data: dict[str, Any] = {
360360
'path': _repo_relative(metrics.path),
361361
'collected_tests': len(metrics.test_functions),
362362
'test_type': metrics.test_type,

scripts/validate_docs_consistency.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@
5353
)
5454
ROADMAP_DOCUMENTATION_TRUTH = re.compile(r'documentation-current-truth', re.IGNORECASE)
5555
ROADMAP_DOC_VS_HEAD = re.compile(r'doc-vs-head', re.IGNORECASE)
56+
CURRENT_DIRECTION_FORBIDDEN_README = ('aspirational adoption',)
57+
CURRENT_DIRECTION_FORBIDDEN_INDEX_CURRENT = (
58+
'What can a daily user trust today?',
59+
'Current daily-driver behavior',
60+
'Ticket execution order (daily-driver)',
61+
)
62+
CURRENT_DIRECTION_FORBIDDEN_ROADMAP = (
63+
'Packaging and adoption',
64+
'general-user trust onboarding',
65+
'external-facing release channels',
66+
)
5667
ROADMAP_TABLE_SEPARATOR = re.compile(r'^\|[-:\s|]+\|$')
5768
ROADMAP_EXIT_COLUMNS = ('Exit Evidence', 'Exit Criteria', 'Risk')
5869
ROADMAP_REQUIRED_ROW_FIELDS = ('Owner', 'Status', 'Confidence', 'Next Gate')
@@ -664,6 +675,90 @@ def validate_roadmap_status(roadmap_text: str) -> list[str]:
664675
return errors
665676

666677

678+
def _markdown_section(text: str, heading: str) -> str:
679+
lines = text.splitlines()
680+
start_index: int | None = None
681+
heading_level = len(heading) - len(heading.lstrip('#'))
682+
for index, line in enumerate(lines):
683+
if line.strip() == heading:
684+
start_index = index
685+
break
686+
if start_index is None:
687+
return ''
688+
689+
end_index = len(lines)
690+
for index in range(start_index + 1, len(lines)):
691+
line = lines[index]
692+
if not line.startswith('#'):
693+
continue
694+
level = len(line) - len(line.lstrip('#'))
695+
if level <= heading_level:
696+
end_index = index
697+
break
698+
return '\n'.join(lines[start_index:end_index])
699+
700+
701+
def validate_current_truth_framing(
702+
*,
703+
readme_text: str,
704+
docs_index_text: str,
705+
roadmap_text: str,
706+
) -> list[str]:
707+
"""Keep current-truth front doors aligned to owner-operated harness direction."""
708+
errors: list[str] = []
709+
readme_header = '\n'.join(readme_text.splitlines()[:8])
710+
index_current = _markdown_section(docs_index_text, '## Current Truth')
711+
index_start = _markdown_section(docs_index_text, '## Start Here')
712+
roadmap_horizons = _markdown_section(roadmap_text, '## Roadmap Horizons')
713+
roadmap_header = roadmap_text.split('## Purpose', 1)[0]
714+
715+
if 'owner-operator harness-first current direction' not in readme_header:
716+
errors.append(
717+
'README.md direction record must stay owner-operator harness-first.'
718+
)
719+
if 'owner-operator' not in index_start:
720+
errors.append(
721+
'docs/INDEX.md Start Here must frame current use as owner-operated.'
722+
)
723+
if 'owner-operated' not in index_current:
724+
errors.append(
725+
'docs/INDEX.md Current Truth must frame current use as owner-operated.'
726+
)
727+
if 'owner-operator is the current validated persona' not in roadmap_header:
728+
errors.append(
729+
'docs/roadmap-status.md must name owner-operator as the current '
730+
'validated persona.'
731+
)
732+
if 'not current goals' not in roadmap_header:
733+
errors.append(
734+
'docs/roadmap-status.md must state external adoption/hosted expansion '
735+
'are not current goals.'
736+
)
737+
738+
for phrase in CURRENT_DIRECTION_FORBIDDEN_README:
739+
if phrase in readme_header:
740+
errors.append(
741+
f'README.md direction record contains outdated phrase {phrase!r}.'
742+
)
743+
for phrase in CURRENT_DIRECTION_FORBIDDEN_INDEX_CURRENT:
744+
if phrase in index_start or phrase in index_current:
745+
errors.append(
746+
f'docs/INDEX.md current-truth sections contain outdated phrase '
747+
f'{phrase!r}.'
748+
)
749+
for phrase in CURRENT_DIRECTION_FORBIDDEN_ROADMAP:
750+
if phrase in roadmap_horizons:
751+
errors.append(
752+
f'docs/roadmap-status.md Roadmap Horizons contain outdated phrase '
753+
f'{phrase!r}.'
754+
)
755+
756+
return errors
757+
758+
759+
validate_current_direction_claims = validate_current_truth_framing
760+
761+
667762
def _normalize_roadmap_status(value: str) -> str:
668763
return re.sub(r'[*_`]', '', value).strip().lower()
669764

@@ -1185,6 +1280,7 @@ def validate_docs_consistency(
11851280
catalog_path: Path | None = None,
11861281
use_cases_path: Path | None = None,
11871282
roadmap_status_path: Path | None = None,
1283+
docs_index_path: Path | None = None,
11881284
pyproject_path: Path | None = None,
11891285
coverage_omit_ledger_path: Path | None = None,
11901286
dependency_audit_policy_path: Path | None = None,
@@ -1214,6 +1310,7 @@ def validate_docs_consistency(
12141310
roadmap_status_doc_path = roadmap_status_path or (
12151311
_REPO_ROOT / 'docs' / 'roadmap-status.md'
12161312
)
1313+
docs_index_doc_path = docs_index_path or (_REPO_ROOT / 'docs' / 'INDEX.md')
12171314
pyproject_doc_path = pyproject_path or (_REPO_ROOT / 'pyproject.toml')
12181315
coverage_omit_ledger_doc_path = coverage_omit_ledger_path or (
12191316
_REPO_ROOT / 'docs' / 'governance' / 'coverage-omit-ledger.md'
@@ -1242,6 +1339,11 @@ def validate_docs_consistency(
12421339
if roadmap_status_doc_path.is_file()
12431340
else ''
12441341
)
1342+
docs_index_text = (
1343+
docs_index_doc_path.read_text(encoding='utf-8')
1344+
if docs_index_doc_path.is_file()
1345+
else ''
1346+
)
12451347

12461348
if check_providers:
12471349
if architecture_path.is_file() and usage_doc_path.is_file():
@@ -1415,6 +1517,13 @@ def validate_docs_consistency(
14151517
errors.extend(
14161518
validate_index_status_vocabulary(index_path.read_text(encoding='utf-8'))
14171519
)
1520+
errors.extend(
1521+
validate_current_truth_framing(
1522+
readme_text=readme_text,
1523+
docs_index_text=docs_index_text,
1524+
roadmap_text=roadmap_status_text,
1525+
)
1526+
)
14181527

14191528
errors.extend(
14201529
validate_durable_docs_language(

tests/lifecycle/test_run_event_spine.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
ToolRequest,
2525
audit_event_to_run_event_type,
2626
)
27-
from teaagent.errors import ToolPermissionError
27+
from teaagent.errors import AuditDurabilityError, ToolPermissionError
2828
from teaagent.runner._events import (
2929
_AUDIT_EVENT_TO_RUN_EVENT_TYPE,
3030
_RUN_EVENT_TO_AUDIT_EVENT_TYPE,
31+
register_audit_consumer,
3132
run_event_to_audit_event_type,
3233
)
3334

@@ -121,6 +122,39 @@ def failing_critical(_: RunEvent) -> None:
121122
spine.emit(RunEventType.RUN_STARTED, 'run-1', {'task': 'test'})
122123

123124

125+
def test_audit_consumer_durability_failure_is_isolated_without_compliance(
126+
monkeypatch: pytest.MonkeyPatch,
127+
) -> None:
128+
"""Audit consumer failures stay isolated unless compliance mode is enabled."""
129+
spine = EventSpine()
130+
audit = AuditLogger(compliance_mode=False)
131+
132+
def failing_record(event_type: str, run_id: str, **payload: Any) -> None:
133+
raise AuditDurabilityError('disk failed')
134+
135+
monkeypatch.setattr(audit, 'record', failing_record)
136+
register_audit_consumer(spine, audit)
137+
138+
spine.emit(RunEventType.RUN_STARTED, 'run-1', {'task': 'test'})
139+
140+
141+
def test_audit_consumer_durability_failure_propagates_with_compliance(
142+
monkeypatch: pytest.MonkeyPatch,
143+
) -> None:
144+
"""Compliance-mode audit consumer is critical and halts on durability loss."""
145+
spine = EventSpine()
146+
audit = AuditLogger(compliance_mode=True)
147+
148+
def failing_record(event_type: str, run_id: str, **payload: Any) -> None:
149+
raise AuditDurabilityError('disk failed')
150+
151+
monkeypatch.setattr(audit, 'record', failing_record)
152+
register_audit_consumer(spine, audit)
153+
154+
with pytest.raises(AuditDurabilityError, match='disk failed'):
155+
spine.emit(RunEventType.RUN_STARTED, 'run-1', {'task': 'test'})
156+
157+
124158
def test_event_sequence_monotonicity() -> None:
125159
"""Test that event sequence numbers are monotonic."""
126160
spine = EventSpine()

tests/test_docs_consistency.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ def test_validate_docs_consistency_passes_when_inputs_match(tmp_path: Path) -> N
3535
)
3636

3737
readme.write_text(
38-
'(2 providers)\nexport A_API_KEY=\nexport B_API_KEY=\n', encoding='utf-8'
38+
'owner-operator\n(2 providers)\nexport A_API_KEY=\nexport B_API_KEY=\n',
39+
encoding='utf-8',
3940
)
4041
tier_block = render_tier_markdown()
4142
acceptance.write_text(
@@ -48,6 +49,7 @@ def test_validate_docs_consistency_passes_when_inputs_match(tmp_path: Path) -> N
4849
matrix.write_text('| Use Case | Covered |\n| yes |\n', encoding='utf-8')
4950
roadmap.write_text(
5051
'# Roadmap Status\n\n'
52+
'owner-operator\n'
5153
'| H0 | Claim and risk hygiene | documentation-current-truth |\n'
5254
'doc-vs-HEAD guard\n',
5355
encoding='utf-8',
@@ -79,7 +81,9 @@ def test_validate_docs_consistency_detects_mismatch(tmp_path: Path) -> None:
7981
'def test_a():\n assert True\n', encoding='utf-8'
8082
)
8183

82-
readme.write_text('(3 providers)\nexport A_API_KEY=\n', encoding='utf-8')
84+
readme.write_text(
85+
'owner-operator\n(3 providers)\nexport A_API_KEY=\n', encoding='utf-8'
86+
)
8387
acceptance.write_text(
8488
'`2 passed`\n<!-- ACCEPTANCE_TIERS:START -->\nwrong\n<!-- ACCEPTANCE_TIERS:END -->',
8589
encoding='utf-8',
@@ -152,15 +156,81 @@ def test_validate_roadmap_status_passes_for_repo_roadmap() -> None:
152156

153157
def test_current_roadmap_stays_owner_operator_harness_first() -> None:
154158
root = Path(__file__).resolve().parents[1]
159+
readme = (root / 'README.md').read_text(encoding='utf-8')
160+
index = (root / 'docs' / 'INDEX.md').read_text(encoding='utf-8')
155161
roadmap = (root / 'docs' / 'roadmap-status.md').read_text(encoding='utf-8')
156162

163+
errors = _VALIDATE_MODULE.validate_current_direction_claims(
164+
readme_text=readme,
165+
docs_index_text=index,
166+
roadmap_text=roadmap,
167+
)
168+
assert errors == []
157169
assert 'owner-operator' in roadmap
158170
assert 'owner packaging and local distribution'.lower() in roadmap.lower()
159171
assert 'general-user trust onboarding' not in roadmap
160172
assert 'Packaging and adoption' not in roadmap
161173
assert 'external-facing release channels' not in roadmap
162174

163175

176+
def test_current_direction_guard_detects_old_adoption_framing() -> None:
177+
errors = _VALIDATE_MODULE.validate_current_direction_claims(
178+
readme_text=(
179+
'# TeaAgent\n'
180+
'> **Direction record:** owner-operator harness-first current direction, '
181+
'aspirational adoption\n'
182+
),
183+
docs_index_text=(
184+
'# Index\n'
185+
'## Start Here\n'
186+
'| What can a daily user trust today? | x | y |\n'
187+
'## Current Truth\n'
188+
'| Current daily-driver behavior | x |\n'
189+
),
190+
roadmap_text=(
191+
'# Roadmap Status\n'
192+
'owner-operator is the current validated persona; not current goals\n'
193+
'## Purpose\n'
194+
'owner-operator roadmap\n'
195+
'## Roadmap Horizons\n'
196+
'| H6 | Packaging and adoption | external-facing release channels |\n'
197+
'general-user trust onboarding\n'
198+
),
199+
)
200+
assert any('aspirational adoption' in err for err in errors)
201+
assert any('Packaging and adoption' in err for err in errors)
202+
assert any('external-facing release channels' in err for err in errors)
203+
assert any('general-user trust onboarding' in err for err in errors)
204+
205+
206+
def test_current_direction_guard_ignores_historical_evidence_mentions() -> None:
207+
errors = _VALIDATE_MODULE.validate_current_direction_claims(
208+
readme_text=(
209+
'# TeaAgent\n'
210+
'> **Direction record:** owner-operator harness-first current direction\n'
211+
),
212+
docs_index_text=(
213+
'# Index\n'
214+
'## Start Here\n'
215+
'| What can the owner-operator trust today? | x | y |\n'
216+
'## Current Truth\n'
217+
'| Current owner-operated daily behavior | x |\n'
218+
'## Evidence And Review\n'
219+
'| June 10 conversation experience refresh | '
220+
'General-User Conversation Experience Refresh |\n'
221+
),
222+
roadmap_text=(
223+
'# Roadmap Status\n'
224+
'owner-operator is the current validated persona; not current goals\n'
225+
'## Purpose\n'
226+
'Roadmap purpose.\n'
227+
'## Roadmap Horizons\n'
228+
'| H6 | Owner packaging and local distribution | owner-operated use |\n'
229+
),
230+
)
231+
assert errors == []
232+
233+
164234
def test_validate_roadmap_status_detects_missing_h0_truth_links() -> None:
165235
roadmap = (
166236
'# Roadmap Status\n\n'

0 commit comments

Comments
 (0)