docs(rectification): archive upstream playbook and add stop-rule task
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled

The upstream interview playbook and evidence thresholds resolve task 0.
Upstream stops and reports when evidence is thin — fewer than three dated
events, fewer than two domains, or a tie — while this repo treats the same
kind of thresholds as a confirmation gate and keeps asking when they are
not met. Its label ladder has no confirmed rung at all, and every contract
test asserts candidate_range_not_birth_time_truth.

Task 1 capped the interview by round count, which is half of it. Task 6
adds the evidence-state stop rules, reframes exhausted as a normal
delivery, adopts the upstream label ladder and closing wording, and keeps
the 4/3 confirmation gate separate from the 3/2 delivery floor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
Jesse_Chen
2026-08-31 02:27:32 +00:00
co-authored by Claude Opus 5
parent 53450f5bf3
commit 30ef6afea7
4 changed files with 458 additions and 17 deletions
@@ -0,0 +1,41 @@
# Evidence Thresholds
## Labels
- `blocked`: required input, route, or event evidence is missing.
- `user_history_verification_required`: dates are too sparse or imprecise.
- `parameter_sensitive`: small changes in the candidate grid materially alter the lead.
- `manual_pattern_consensus`: standalone conversation evidence points to one interval, but no external calculator/audit was executed.
- `single_adapter_support`: one user-supplied adapter supports the leading interval.
- `multi_adapter_consensus`: multiple executed adapters support the same interval; this is still not birth-time truth.
## Standalone Cap
In `standalone_core`, the highest allowed label is `manual_pattern_consensus`.
Never label `高置信候选` unless the current user actually provided and executed additional audit adapters.
## Minimum Standalone Gate
To move beyond `blocked`, require all of the following:
- one valid route: `range`, `block`, or `unknown`
- at least three dated events
- at least two domains
- no unresolved tie for first place
- the explicit boundary `不等于唯一真实出生分钟`
Use `medium` only when there are at least five dated events across at least two domains and the lead interval is clearly ahead.
Use `low` when the evidence points somewhere but remains shallow or fragile.
## Optional Enhanced Gate
Use `高置信候选` only when all are visible and actually executed in the current user's environment:
- at least five dated events across at least two domains
- a clear leading interval without a material tie
- adapter status table with raw-result references
- if used, separate visibility for `Vimshottari + Narayana`
- if used, separate visibility for `Functional Benefic/Malefic`, relevant vargas, MEVG, Real Case Calibration, and holdout
- no decisive conflict from any executed adapter
Any blocked, conflicting, or missing raw-evidence item caps the output at `manual_pattern_consensus`, `medium`, `low`, `parameter_sensitive`, or `blocked` as appropriate. `高置信候选` **不等于唯一真实出生分钟**.
+70
View File
@@ -0,0 +1,70 @@
# Interview Playbook
## Intake
Collect birth date, birthplace, timezone, known time/range, source of the time, confidence in that source, and the user's priority domain. Ask permission before collecting dated life events.
## Routes
### `range`
For a remembered time or bounded interval:
1. Restate the interval in clock time.
2. Split it into `early / middle / late`.
3. Ask 3-5 A/B/C/D questions across education, relocation, relationship, career, and recent learning/expression.
4. Request dated events only in the strongest 2-3 domains.
### `block`
For `morning`, `afternoon`, `evening`, family recollections, or zodiac-hour style descriptions:
1. Convert the label into a stated coarse interval.
2. Split that interval into `early / middle / late`.
3. Ask the same domain questions.
4. Request events from the leading domains only.
### `unknown`
For no usable time memory:
1. State that the first valid result can only be a broad block.
2. Compare `morning / afternoon / evening / night` through high-signal domains.
3. Do not refine the winning block until enough dated evidence exists.
4. If one broad block leads, begin the next round by splitting only that block.
## Event Ledger
Use one row per event with:
- `id`
- `domain`
- `date`
- `precision` (`day`, `month`, `year`)
- `reliability` (`high`, `medium`, `low`)
- `user wording`
- `supports` (candidate labels helped by this event)
- `conflicts` (candidate labels weakened by this event)
Confirm date precision instead of inventing a day.
## Question Rhythm
- First round: 3-5 high-information A/B/C/D questions.
- Second round: ask only for dated events in the strongest domains.
- Third round: ask only the next 1-3 events that could separate the top candidates.
- Never collect a long autobiography before the first route decision.
## Stop Rules
Stop and report the current interval when any is true:
- fewer than three dated events
- fewer than two domains
- tied first-place candidates
- user uncertainty is too high to map support/conflict reliably
- an optional adapter was promised but cannot be executed and the user wants an audited result
## Final Wording
State: "当前最优结果是候选时间段,而不是已经确认的唯一出生分钟。临时代表时间仅用于下一轮验证与比较。"
@@ -0,0 +1,271 @@
import json
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SKILL = REPO_ROOT / "skills" / "jyotish-birth-time-rectification"
if not SKILL.is_dir():
SKILL = Path("/Users/wuyongnaren/.workbuddy/skills/jyotish-birth-time-rectification")
def test_rectification_skill_resources_and_portable_contract_exist() -> None:
skill_text = (SKILL / "SKILL.md").read_text(encoding="utf-8")
for relative_path in (
"references/main_repository_router.md",
"references/interview_playbook.md",
"references/evidence_thresholds.md",
"references/api_reference.md",
"references/manual_test_prompts.md",
"references/testing_playbook.md",
"references/adapter_integration_guide.md",
"references/public_distribution_checklist.md",
"assets/rectification_session_template.md",
"scripts/example.py",
):
assert (SKILL / relative_path).is_file(), relative_path
assert relative_path in skill_text
for required_term in (
"standalone_core",
"adapter_enhanced",
"main_repository_enhanced",
"range / block / unknown",
"Vimshottari + Narayana",
"Functional Benefic/Malefic",
"MEVG",
"Real Case Calibration",
"候选时间段",
"临时代表时间",
"唯一真实出生分钟",
"高置信候选",
"Require no fixed repository path",
"manual_test_prompts.md",
"testing_playbook.md",
"adapter_integration_guide.md",
"public_distribution_checklist.md",
):
assert required_term in skill_text
assert "/Users/wuyongnaren/Documents/印度占星" not in skill_text
def test_router_and_thresholds_define_portable_modes_and_caps() -> None:
router = (SKILL / "references/main_repository_router.md").read_text(encoding="utf-8")
for required_term in (
"standalone_core",
"adapter_enhanced",
"main_repository_enhanced",
"supports",
"conflicts",
"executed",
"blocked",
"not_applicable",
):
assert required_term in router
assert "/Users/wuyongnaren/Documents/印度占星" not in router
assert ".venv" not in router
thresholds = (SKILL / "references/evidence_thresholds.md").read_text(encoding="utf-8")
assert "manual_pattern_consensus" in thresholds
assert "高置信候选" in thresholds
assert "不等于唯一真实出生分钟" in thresholds
assert "standalone_core" in thresholds
def test_template_and_api_reference_preserve_audit_and_portability() -> None:
template = (SKILL / "assets/rectification_session_template.md").read_text(encoding="utf-8")
for heading in (
"Runtime mode",
"supports",
"conflicts",
"Functional Benefic/Malefic (optional)",
"External evidence / MEVG / Real Case (optional)",
):
assert heading in template
api_reference = (SKILL / "references/api_reference.md").read_text(encoding="utf-8")
for term in (
"route",
"known_time",
"known_block",
"adapters",
"repository_root",
"main_repository_enhanced",
"candidate_range_not_birth_time_truth",
):
assert term in api_reference
assert "/Users/wuyongnaren/Documents/印度占星" not in api_reference
def test_scaffold_runs_standalone_and_returns_candidate_only_receipt() -> None:
request = {
"route": "range",
"known_time": {"start": "14:00", "end": "16:00"},
"events": [
{
"id": "career-1",
"domain": "career",
"date": "2019-07",
"precision": "month",
"reliability": "high",
"supports": ["middle", "late"],
"conflicts": ["early"],
"note": "工作结构变化后责任显著增加",
},
{
"id": "relationship-1",
"domain": "relationship",
"date": "2016-10-03",
"precision": "day",
"reliability": "high",
"supports": ["middle"],
"conflicts": ["early"],
"note": "关系进入与承诺加强",
},
{
"id": "relocation-1",
"domain": "relocation",
"date": "2014",
"precision": "year",
"reliability": "medium",
"supports": ["late"],
"conflicts": [],
"note": "离乡发展",
},
],
}
result = subprocess.run(
[sys.executable, str(SKILL / "scripts/example.py")],
input=json.dumps(request),
text=True,
capture_output=True,
check=True,
)
receipt = json.loads(result.stdout)
assert receipt["claim_status"] == "candidate_range_not_birth_time_truth"
assert receipt["summary"]["claim_status"] == "candidate_range_not_birth_time_truth"
assert receipt["runtime_mode"] == "standalone_core"
assert receipt["summary"]["confidence_label"] in {"medium", "manual_pattern_consensus"}
assert receipt["candidate_result"]["candidate_range"]["representative_time"]
def test_scaffold_can_report_adapter_enhanced_without_fixed_repo_path() -> None:
request = {
"route": "unknown",
"events": [
{
"id": "career-1",
"domain": "career",
"date": "2020-05",
"precision": "month",
"reliability": "high",
"supports": ["afternoon"],
"conflicts": ["night"],
"note": "职业方向明显切换",
},
{
"id": "relationship-1",
"domain": "relationship",
"date": "2018-09",
"precision": "month",
"reliability": "high",
"supports": ["afternoon"],
"conflicts": ["morning"],
"note": "稳定关系建立",
},
{
"id": "education-1",
"domain": "education",
"date": "2012",
"precision": "year",
"reliability": "medium",
"supports": ["afternoon"],
"conflicts": [],
"note": "学习方向变化",
},
],
"adapters": [
{
"name": "user_vimshottari_engine",
"type": "calculator",
"location": "./tools/rectify.py",
"status": "executed",
"summary": "Vimshottari and Narayana both prefer afternoon",
"raw_result_ref": "outputs/rectify-afternoon.json",
}
],
}
result = subprocess.run(
[sys.executable, str(SKILL / "scripts/example.py")],
input=json.dumps(request),
text=True,
capture_output=True,
check=True,
)
receipt = json.loads(result.stdout)
assert receipt["runtime_mode"] == "adapter_enhanced"
assert receipt["summary"]["confidence_label"] == "single_adapter_support"
assert receipt["audit_appendix"]["adapter_statuses"][0]["status"] == "executed"
def test_scaffold_main_repository_enhanced_can_execute_with_minimal_valid_payload() -> None:
request = {
"runtime_mode": "main_repository_enhanced",
"repository_root": "/Users/wuyongnaren/Documents/印度占星",
"request": {
"birth_date": "1993-04-17",
"start_time": "14:29",
"end_time": "14:31",
"lat": 36.683333,
"lon": 114.35,
"tz": 8,
"events": [
{"id": "edu-1", "domain": "education", "date": "2011-09", "precision": "month"},
{"id": "career-1", "domain": "career", "date": "2019-07-01", "precision": "day"},
{"id": "rel-1", "domain": "relationship", "date": "2021", "precision": "year"}
]
}
}
result = subprocess.run(
[sys.executable, str(SKILL / "scripts/example.py")],
input=json.dumps(request),
text=True,
capture_output=True,
check=True,
)
receipt = json.loads(result.stdout)
assert receipt["runtime_mode"] == "main_repository_enhanced"
assert receipt["claim_status"] == "candidate_range_not_birth_time_truth"
assert receipt["summary"]["claim_status"] == "candidate_range_not_birth_time_truth"
def test_scaffold_main_repository_enhanced_returns_structured_block_when_fields_missing() -> None:
request = {
"runtime_mode": "main_repository_enhanced",
"repository_root": "/Users/wuyongnaren/Documents/印度占星",
"request": {
"events": [
{
"date": "2020-05",
"category": "career",
"description": "职业方向明显切换",
"confidence": "high"
}
]
}
}
result = subprocess.run(
[sys.executable, str(SKILL / "scripts/example.py")],
input=json.dumps(request),
text=True,
capture_output=True,
check=True,
)
receipt = json.loads(result.stdout)
assert receipt["runtime_mode"] == "main_repository_enhanced"
assert receipt["claim_status"] == "candidate_range_not_birth_time_truth"
assert receipt["candidate_result"]["status"] == "blocked"
assert receipt["candidate_result"]["reason"] == "missing_required_fields"
assert "birth_date" in receipt["candidate_result"]["missing_fields"]