Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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
128 changes: 128 additions & 0 deletions .github/workflows/email-writing-policy-tdd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Email Writing Policy TDD

on:
push:
branches:
- feat/email-writing-policy-task8-current-stack
pull_request:
paths:
- backend/policies/email_writing_judge_policy.schema.json
- backend/policies/email_writing_judge_evaluation_only_v1.json
- backend/policies/email_writing_policy_manifest.json
- backend/services/email_writing_policy.py
- backend/tests/test_email_writing_policy.py
- backend/tests/test_email_writing_policy_candidate_kind.py
- scripts/ci/reject_terminal_output.sh
- .github/workflows/email-writing-policy-tdd.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: email-writing-policy-tdd-${{ github.event.pull_request.head.sha || github.sha }}
cancel-in-progress: true

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
PYTHONWARNINGS: error

jobs:
task8-admission-policy:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
cache: pip
cache-dependency-path: backend/requirements-hashes.txt
- name: Install hash-locked application dependencies
run: >-
bash scripts/ci/reject_terminal_output.sh
python -m pip install --disable-pip-version-check --require-hashes
-r backend/requirements-hashes.txt
- name: Install hash-verified coverage tool
run: |
set -euo pipefail
mkdir -p /tmp/coverage-wheel
cat >/tmp/coverage-lock.txt <<'EOF'
coverage==7.15.2 --hash=sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c
EOF
bash scripts/ci/reject_terminal_output.sh \
python -m pip download \
--disable-pip-version-check \
--require-hashes \
--no-deps \
--only-binary=:all: \
--platform any \
--python-version 3.14 \
--implementation py \
--abi none \
--dest /tmp/coverage-wheel \
-r /tmp/coverage-lock.txt
bash scripts/ci/reject_terminal_output.sh \
python -m pip install --disable-pip-version-check --no-deps \
/tmp/coverage-wheel/coverage-7.15.2-py3-none-any.whl
- name: Run Task 8 policy tests
run: |
cd backend
bash ../scripts/ci/reject_terminal_output.sh \
python -m pytest -q --noconftest \
tests/test_email_writing_policy.py \
tests/test_email_writing_policy_candidate_kind.py
- name: Verify Task 8 statement and branch coverage
run: |
cd backend
bash ../scripts/ci/reject_terminal_output.sh python -m coverage erase
bash ../scripts/ci/reject_terminal_output.sh \
python -m coverage run --branch \
--include='services/email_writing_policy.py' \
-m pytest -q --noconftest \
tests/test_email_writing_policy.py \
tests/test_email_writing_policy_candidate_kind.py
bash ../scripts/ci/reject_terminal_output.sh \
python -m coverage report --show-missing --fail-under=100 \
services/email_writing_policy.py
- name: Verify shipped Python docstrings
run: |
set -euo pipefail
cat >/tmp/check_task8_docstrings.py <<'PY'
import ast
from pathlib import Path

path = Path("services/email_writing_policy.py")
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
missing: list[str] = []
if ast.get_docstring(tree) is None:
missing.append(f"{path}:<module>")
for node in ast.walk(tree):
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
if ast.get_docstring(node) is None:
missing.append(f"{path}:{node.lineno}:{node.name}")
if missing:
raise SystemExit("Missing shipped docstrings:\n" + "\n".join(missing))
print("Docstring gate passed for Task 8 policy module")
PY
cd backend
bash ../scripts/ci/reject_terminal_output.sh \
python /tmp/check_task8_docstrings.py
- name: Lint Task 8 source and tests
run: |
cd backend
bash ../scripts/ci/reject_terminal_output.sh \
python -m ruff check \
services/email_writing_policy.py \
tests/test_email_writing_policy.py \
tests/test_email_writing_policy_candidate_kind.py
- name: Compile Task 8 source and tests
run: |
bash scripts/ci/reject_terminal_output.sh \
python -m compileall -q \
backend/services/email_writing_policy.py \
backend/tests/test_email_writing_policy.py \
backend/tests/test_email_writing_policy_candidate_kind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"adjudication_conditions":["same_model_without_compatible_calibration","criterion_disagreement","insufficient_evidence"],"approved_profiles":[{"candidate_model_profile_id":"candidate-reviewer-v1","candidate_provider_id":"contextual-orchestrator","judge_model_profile_id":"independent-judge-v1","judge_provider_id":"contextual-orchestrator","language_tags":["en","ko"],"profile_id":"email-writing-evaluation-en-ko-v1","review_modes":["incremental","deep"],"rubric_version":"email_writing_judge_rubric_v1","same_model_allowed":false}],"calibration_summary":{"brier_score":null,"dif_profiles_evaluated":0,"status":"not_evaluated","temporal_drift_evaluated":false,"test_retest_reliability":null},"category_anchors":["no_credible_evidence","partial_or_weak_support","mostly_supported_with_gaps","fully_supported_with_accurate_evidence"],"category_count":4,"compatible_contracts":{"contextual_orchestrator":"v1","fast_mlsirm":null,"inkspan":null,"naruon":"0.14.4"},"created_at":"2026-09-01T00:00:00Z","evidence":{"calibration_dataset_hash":null,"holdout_labels_accessed_after_preregistration":false,"locked_holdout_hash":null,"protocol_hash":null,"reference_adjudication_hash":null},"expires_at":"2027-01-01T00:00:00Z","limitations":["Evaluation-only artifact. It cannot admit user-facing writing diagnostics.","No language, model, provider, calibration, DIF, reliability, or drift validity claim is published by this artifact.","Threshold values are placeholders for pipeline exercise only and are not production admission thresholds."],"mandatory_criterion_floors":{"actionability":0,"audience_pragmatics":0,"explanation_quality":0,"fact_preservation":0,"intent_preservation":0,"issue_support":0,"replacement_correctness":0,"request_strength_preservation":0,"span_fidelity":0,"technical_precision":0},"minimum_criterion_scores":{"actionability":0.0,"audience_pragmatics":0.0,"explanation_quality":0.0,"fact_preservation":0.0,"intent_preservation":0.0,"issue_support":0.0,"replacement_correctness":0.0,"request_strength_preservation":0.0,"span_fidelity":0.0,"technical_precision":0.0},"policy_id":"email_writing_judge_policy","policy_version":"email-writing-evaluation-only-v1","publish_decision":"withhold","required_criteria_by_candidate_kind":{"no_replacement_diagnostic":["issue_support","span_fidelity","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"],"replacement_diagnostic":["issue_support","span_fidelity","replacement_correctness","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"]},"rollback_version":null,"status":"evaluation_only"}
1 change: 1 addition & 0 deletions backend/policies/email_writing_judge_policy.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"$id":"https://contextualwisdomlab.github.io/naruon/schemas/email_writing_judge_policy.schema.json","$schema":"https://json-schema.org/draft/2020-12/schema","additionalProperties":false,"allOf":[{"if":{"properties":{"status":{"const":"published"}},"required":["status"]},"then":{"properties":{"calibration_summary":{"properties":{"status":{"const":"validated"}},"required":["status"]},"evidence":{"properties":{"calibration_dataset_hash":{"type":"string"},"holdout_labels_accessed_after_preregistration":{"const":true},"locked_holdout_hash":{"type":"string"},"protocol_hash":{"type":"string"},"reference_adjudication_hash":{"type":"string"}},"required":["protocol_hash","calibration_dataset_hash","locked_holdout_hash","reference_adjudication_hash","holdout_labels_accessed_after_preregistration"]},"publish_decision":{"const":"publish"}}}},{"if":{"properties":{"status":{"enum":["evaluation_only","superseded","revoked"]}},"required":["status"]},"then":{"properties":{"publish_decision":{"const":"withhold"}}}}],"properties":{"adjudication_conditions":{"items":{"enum":["same_model_without_compatible_calibration","criterion_disagreement","insufficient_evidence"]},"type":"array","uniqueItems":true},"approved_profiles":{"items":{"additionalProperties":false,"properties":{"candidate_model_profile_id":{"type":"string"},"candidate_provider_id":{"type":"string"},"judge_model_profile_id":{"type":"string"},"judge_provider_id":{"type":"string"},"language_tags":{"items":{"maxLength":63,"minLength":1,"type":"string"},"maxItems":32,"minItems":1,"type":"array","uniqueItems":true},"profile_id":{"type":"string"},"review_modes":{"items":{"enum":["incremental","deep"]},"maxItems":2,"minItems":1,"type":"array","uniqueItems":true},"rubric_version":{"type":"string"},"same_model_allowed":{"type":"boolean"}},"required":["profile_id","language_tags","review_modes","candidate_model_profile_id","candidate_provider_id","judge_model_profile_id","judge_provider_id","rubric_version","same_model_allowed"],"type":"object"},"maxItems":64,"minItems":1,"type":"array"},"calibration_summary":{"additionalProperties":false,"properties":{"brier_score":{"maximum":1.0,"minimum":0.0,"type":["number","null"]},"dif_profiles_evaluated":{"minimum":0,"type":"integer"},"status":{"enum":["not_evaluated","validated"]},"temporal_drift_evaluated":{"type":"boolean"},"test_retest_reliability":{"maximum":1.0,"minimum":0.0,"type":["number","null"]}},"required":["status","brier_score","test_retest_reliability","dif_profiles_evaluated","temporal_drift_evaluated"],"type":"object"},"category_anchors":{"items":{"type":"string"},"maxItems":9,"minItems":2,"type":"array","uniqueItems":true},"category_count":{"maximum":9,"minimum":2,"type":"integer"},"compatible_contracts":{"additionalProperties":false,"properties":{"contextual_orchestrator":{"type":"string"},"fast_mlsirm":{"type":["string","null"]},"inkspan":{"type":["string","null"]},"naruon":{"type":"string"}},"required":["naruon","inkspan","fast_mlsirm","contextual_orchestrator"],"type":"object"},"created_at":{"format":"date-time","type":"string"},"evidence":{"additionalProperties":false,"properties":{"calibration_dataset_hash":{"pattern":"^sha256:[0-9a-f]{64}$","type":["string","null"]},"holdout_labels_accessed_after_preregistration":{"type":"boolean"},"locked_holdout_hash":{"pattern":"^sha256:[0-9a-f]{64}$","type":["string","null"]},"protocol_hash":{"pattern":"^sha256:[0-9a-f]{64}$","type":["string","null"]},"reference_adjudication_hash":{"pattern":"^sha256:[0-9a-f]{64}$","type":["string","null"]}},"required":["protocol_hash","calibration_dataset_hash","locked_holdout_hash","reference_adjudication_hash","holdout_labels_accessed_after_preregistration"],"type":"object"},"expires_at":{"format":"date-time","type":"string"},"limitations":{"items":{"maxLength":1000,"minLength":1,"type":"string"},"maxItems":64,"minItems":1,"type":"array"},"mandatory_criterion_floors":{"additionalProperties":false,"properties":{"actionability":{"maximum":8,"minimum":0,"type":"integer"},"audience_pragmatics":{"maximum":8,"minimum":0,"type":"integer"},"explanation_quality":{"maximum":8,"minimum":0,"type":"integer"},"fact_preservation":{"maximum":8,"minimum":0,"type":"integer"},"intent_preservation":{"maximum":8,"minimum":0,"type":"integer"},"issue_support":{"maximum":8,"minimum":0,"type":"integer"},"replacement_correctness":{"maximum":8,"minimum":0,"type":"integer"},"request_strength_preservation":{"maximum":8,"minimum":0,"type":"integer"},"span_fidelity":{"maximum":8,"minimum":0,"type":"integer"},"technical_precision":{"maximum":8,"minimum":0,"type":"integer"}},"required":["issue_support","span_fidelity","replacement_correctness","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"],"type":"object"},"minimum_criterion_scores":{"additionalProperties":false,"properties":{"actionability":{"maximum":1.0,"minimum":0.0,"type":"number"},"audience_pragmatics":{"maximum":1.0,"minimum":0.0,"type":"number"},"explanation_quality":{"maximum":1.0,"minimum":0.0,"type":"number"},"fact_preservation":{"maximum":1.0,"minimum":0.0,"type":"number"},"intent_preservation":{"maximum":1.0,"minimum":0.0,"type":"number"},"issue_support":{"maximum":1.0,"minimum":0.0,"type":"number"},"replacement_correctness":{"maximum":1.0,"minimum":0.0,"type":"number"},"request_strength_preservation":{"maximum":1.0,"minimum":0.0,"type":"number"},"span_fidelity":{"maximum":1.0,"minimum":0.0,"type":"number"},"technical_precision":{"maximum":1.0,"minimum":0.0,"type":"number"}},"required":["issue_support","span_fidelity","replacement_correctness","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"],"type":"object"},"policy_id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$","type":"string"},"policy_version":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$","type":"string"},"publish_decision":{"enum":["publish","withhold"]},"required_criteria_by_candidate_kind":{"additionalProperties":false,"properties":{"no_replacement_diagnostic":{"items":{"enum":["issue_support","span_fidelity","replacement_correctness","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"]},"minItems":1,"type":"array","uniqueItems":true},"replacement_diagnostic":{"items":{"enum":["issue_support","span_fidelity","replacement_correctness","intent_preservation","fact_preservation","request_strength_preservation","audience_pragmatics","technical_precision","actionability","explanation_quality"]},"minItems":1,"type":"array","uniqueItems":true}},"required":["replacement_diagnostic","no_replacement_diagnostic"],"type":"object"},"rollback_version":{"type":["string","null"]},"status":{"enum":["evaluation_only","published","superseded","revoked"]}},"required":["policy_id","policy_version","status","publish_decision","created_at","expires_at","compatible_contracts","approved_profiles","category_count","category_anchors","required_criteria_by_candidate_kind","mandatory_criterion_floors","minimum_criterion_scores","adjudication_conditions","calibration_summary","evidence","limitations","rollback_version"],"title":"Naruon Email Writing Judge Policy","type":"object"}
1 change: 1 addition & 0 deletions backend/policies/email_writing_policy_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"artifacts":{"email_writing_judge_evaluation_only_v1.json":{"sha256":"d3b6283c998a336b559ff909053a19678554f489b2d950362b1a365a52c38629"}},"manifest_version":1}
Loading