okstra 0.150.0 → 0.151.1

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.
Files changed (33) hide show
  1. package/docs/cli.md +2 -2
  2. package/docs/project-structure-overview.md +1 -1
  3. package/package.json +3 -2
  4. package/runtime/BUILD.json +2 -2
  5. package/runtime/agents/workers/antigravity-worker.md +2 -1
  6. package/runtime/agents/workers/claude-worker.md +2 -1
  7. package/runtime/agents/workers/codex-worker.md +2 -1
  8. package/runtime/agents/workers/grok-worker.md +2 -1
  9. package/runtime/agents/workers/kimi-worker.md +2 -1
  10. package/runtime/agents/workers/report-writer-worker.md +9 -1
  11. package/runtime/bin/okstra-report-translate.py +56 -2
  12. package/runtime/prompts/launch.template.md +1 -1
  13. package/runtime/prompts/lead/convergence.md +16 -4
  14. package/runtime/prompts/lead/plan-body-verification.md +1 -1
  15. package/runtime/prompts/lead/report-writer.md +28 -16
  16. package/runtime/prompts/lead/team-contract.md +2 -1
  17. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  18. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  19. package/runtime/prompts/profiles/final-verification.md +1 -1
  20. package/runtime/prompts/profiles/implementation-planning.md +8 -7
  21. package/runtime/python/okstra_ctl/analysis_packet.py +1 -0
  22. package/runtime/python/okstra_ctl/convergence.py +15 -1
  23. package/runtime/python/okstra_ctl/dispatch_state.py +5 -1
  24. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +7 -0
  25. package/runtime/python/okstra_ctl/report_finalize.py +27 -4
  26. package/runtime/python/okstra_ctl/report_translation.py +29 -0
  27. package/runtime/python/okstra_ctl/worker_prompt_body.py +4 -2
  28. package/runtime/python/okstra_ctl/worker_prompt_contract.py +41 -1
  29. package/runtime/python/okstra_ctl/worker_prompt_headers.py +3 -0
  30. package/runtime/templates/implementation-worker-preamble.md +12 -3
  31. package/runtime/templates/report-writer-prompt-preamble.md +5 -1
  32. package/runtime/templates/worker-prompt-preamble.md +12 -3
  33. package/runtime/validators/validate-run.py +142 -0
@@ -20,7 +20,7 @@
20
20
  - **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
21
21
  - no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
22
22
  - scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
23
- - project review-rule packs (when present) — search `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`; read the matching `SKILL.md` + referenced `references/*.md` and apply their rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none found`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
23
+ - project review-rule packs (when brief-cited) — apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path; read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Apply the cited rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none cited`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
24
24
  - Residual-tracked — note as Residual Risk unless severe enough to block:
25
25
  - unresolved edge cases
26
26
  - regression risk in adjacent code paths not directly changed
@@ -42,7 +42,7 @@
42
42
  - **Follow established patterns**: in existing codebases, conform to current conventions. Targeted cleanup of a file you are already modifying is acceptable; unrelated refactors are not.
43
43
  - **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and include an option that extracts the variation point behind an interface (a port, or a strategy the next implementation plugs into), scored against the non-extracted option in the trade-off matrix. Penalize an option that branches on resource identity inside a service (one `if` / `switch` arm per implementation): adding the next implementation then means editing that same call site again, which is the closed-for-extension shape this principle exists to catch. This does not contradict YAGNI below: YAGNI drops *speculative* variation (a second implementation nobody named), while a behavior with two implementations already on the table is a present fact, not a forecast. **Enforced:** the `variationPointAnalysis` bullet under `Required deliverable shape` names the schema / validator / `P-Var-*` enforcement points.
44
44
  - **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement.
45
- - **Project review-rule preflight**: before choosing the recommended option, look for project-local review rule packs such as `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`. If present, read the relevant `SKILL.md` plus referenced `references/*.md` files and treat their rules as planning constraints. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
45
+ - **Project review-rule preflight**: apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path. Read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
46
46
  - Expected output emphasis:
47
47
  - feasible plan options
48
48
  - dependency and risk visibility
@@ -80,11 +80,12 @@
80
80
  - admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
81
81
  - NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything inside an option the plan does not recommend, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
82
82
  - A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
83
- - Section heading contract (BLOCKING — validator scans for these literal English substrings):
84
- - The final report MUST include section headings containing each of the following exact strings — this list mirrors `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. (Approval is no longer a body section it is the YAML frontmatter `approved` field.) Three further headings are enforced elsewhere, not by that scan: `Stage Exit Contract` and `Stage Validation` are per-stage subsections checked by `validators/validate-implementation-plan-stages.py`, and `Implementation Design Preparation` is enforced and rendered by `schemas/final-report-v1.0.schema.json` plus `templates/reports/final-report.template.md` from its required data block.
85
- - Korean translations are allowed in parentheses (e.g. `### Recommended Option (Korean gloss)`), but the English keyword must be present verbatim in the heading line.
86
- - The shape and ordering follow `final-report-template.md` sections 5.4 (`Implementation Plan Deliverables`) + 5.5 (`Stage Map`). `validators/validate-run.py` substring-matches the raw report text, so a Korean-only heading fails the gate — the cause of repeated observed failures.
87
- - Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary not deferred to the `implementation` entry gate. It enforces: the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤8), the `depends-on` DAG, and the per-stage vertical-slice contract (S10). S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
83
+ - Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
84
+ - The plan lives in `data.json` under `implementationPlanning`, and `schemas/final-report-v2.0.schema.json` requires every one of these keys: `optionCandidates`, `tradeoffMatrix`, `recommendedOption`, `stageMap`, `stages`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `planBodyVerification`, `crossProjectDependencies`, `decisionDrafts`, `skippedAdrCandidates`, `variationPointAnalysis`, `userNarrative`. A missing block fails schema validation; there is nothing to satisfy by naming a heading. (Approval is not a body section it is the YAML frontmatter `approved` field.)
85
+ - Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
86
+ - Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
87
+ - **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the structural Stage Map scan (`validators/validate-implementation-plan-stages.py`) live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` they gate historical v1 Markdown only. The v2 AI-handoff template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
88
+ - *Guideline, not gated:* write each `stepwiseExecution` row's `action` with a `RED:` / `GREEN:` prefix (or state a TDD exemption in `stageValidation`), and name the success / boundary / failure cases in `acceptance`. The v1 scan checked those tokens literally; v2 requires only that the fields be non-empty, so this is now review-enforced rather than validator-enforced.
88
89
  - Required deliverable shape (final report, in addition to the standard sections):
89
90
  - at least two implementation options. **Each option must include**:
90
91
  - **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
@@ -188,7 +189,7 @@
188
189
  3. **Internal consistency** — option file lists, trade-off matrix, and recommended step list must agree on file paths, names, and signatures. A symbol called `clearLayers()` in the matrix and `clearFullLayers()` in the steps is a bug.
189
190
  4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
190
191
  5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan. Then walk the plan in the expansion direction: for every stage, name the Requirement Coverage row that demanded it, and for every requirement row, read its `Source` cell as a skeptic — does the cited brief heading actually exist, and does a `derived:` rationale state a real technical consequence rather than a preference? Move anything that fails to a `Blocks=approval` clarification row.
191
- 6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
192
+ 6. **Review-rule preflight check** — if the task brief cites a project review rule pack under `Source Material` or `Reporter Confirmations`, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
192
193
  7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
193
194
  8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
194
195
  9. **Cross-project dependency check** — confirm you have not missed a dependency on another repo / another top-level deployable module / a published package. If `dependencyMigrationRisk` has a `kind: cross-project` row, confirm a matching `direction: upstream-precondition` `XP-NNN` row exists in `crossProjectDependencies`, and re-read as a reviewer whether its `requiredWork` is the concrete work the other side must actually build rather than an abstract phrase ("other side's work done") — validator S only checks existence, so concreteness is the self-review's responsibility. Confirm cross-repo work is split into a separate run + XP row instead of being crammed into one task's stages, and that the cross-project substance is not duplicated in `§3 Recommended Next Steps` but lives only in `§5.4 Cross-Project Dependencies`.
@@ -53,6 +53,7 @@ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
53
53
  ),
54
54
  "implementation-planning": (
55
55
  "Worker planning procedure",
56
+ "Pre-planning context exploration",
56
57
  "Design principles applied when scoring options",
57
58
  ),
58
59
  "final-verification": (
@@ -123,7 +123,21 @@ def _parser() -> argparse.ArgumentParser:
123
123
  subparsers = parser.add_subparsers(dest="operation", required=True)
124
124
 
125
125
  example = subparsers.add_parser(
126
- "example", help="print a deterministic input artifact example"
126
+ "example",
127
+ help="print a deterministic input artifact example",
128
+ description=(
129
+ "Print one deterministic valid example as JSON.\n\n"
130
+ "`groups` feeds `seed --groups` and `round-results` feeds "
131
+ "`apply-round --results`, but `critic-results` is the critic "
132
+ "worker's own result document — NOT the `apply-critic-gaps "
133
+ "--results` input. That input is the coverage batch the lead "
134
+ "assembles from those candidates plus each analyser's vote "
135
+ '({schemaVersion, taskKey, mode: "coverage", provider, '
136
+ "modelExecutionValue, dispatches[], gaps[]}); see "
137
+ 'prompts/lead/convergence.md §"Coverage critic". Feeding this '
138
+ "example straight into `apply-critic-gaps` is rejected, by design."
139
+ ),
140
+ formatter_class=argparse.RawDescriptionHelpFormatter,
127
141
  )
128
142
  example.add_argument(
129
143
  "--kind",
@@ -275,7 +275,11 @@ def validate_initial_prompts(
275
275
  PromptRecord(job.worker_id, job.dispatch_kind, job.prompt_path)
276
276
  for job in jobs
277
277
  ]
278
- errors = validate_initial_prompt_records(manifest=manifest, records=records)
278
+ errors = validate_initial_prompt_records(
279
+ manifest=manifest,
280
+ records=records,
281
+ require_evidence_ledger=True,
282
+ )
279
283
  if errors:
280
284
  task_type = require_string(manifest, "taskType")
281
285
  raise DispatchError(f"{task_type} prompt contract: " + "; ".join(errors))
@@ -521,6 +521,7 @@ def _validate_prepublication_set(
521
521
  errors = validate_initial_prompt_records(
522
522
  manifest=context.manifest,
523
523
  records=[record],
524
+ require_evidence_ledger=True,
524
525
  )
525
526
  if errors:
526
527
  raise InitialPromptMaterializationError(
@@ -530,6 +531,7 @@ def _validate_prepublication_set(
530
531
  errors = validate_initial_prompt_records(
531
532
  manifest=context.manifest,
532
533
  records=records,
534
+ require_evidence_ledger=True,
533
535
  )
534
536
  if errors:
535
537
  raise InitialPromptMaterializationError(
@@ -577,6 +579,7 @@ def _validate_existing_prompt(
577
579
  basic_errors = validate_initial_prompt_records(
578
580
  manifest=context.manifest,
579
581
  records=[basic_record],
582
+ require_evidence_ledger=True,
580
583
  )
581
584
  if basic_errors:
582
585
  raise InitialPromptMaterializationError(
@@ -593,6 +596,7 @@ def _validate_existing_prompt(
593
596
  expected_errors = validate_initial_prompt_records(
594
597
  manifest=context.manifest,
595
598
  records=[expected_record],
599
+ require_evidence_ledger=True,
596
600
  )
597
601
  if expected_errors:
598
602
  reason = _existing_metadata_reason(expected_errors)
@@ -765,6 +769,7 @@ def _validate_published_set(
765
769
  errors = validate_initial_prompt_records(
766
770
  manifest=context.manifest,
767
771
  records=[record],
772
+ require_evidence_ledger=True,
768
773
  )
769
774
  if errors:
770
775
  raise InitialPromptMaterializationError(
@@ -774,6 +779,7 @@ def _validate_published_set(
774
779
  errors = validate_initial_prompt_records(
775
780
  manifest=context.manifest,
776
781
  records=records,
782
+ require_evidence_ledger=True,
777
783
  )
778
784
  if errors:
779
785
  raise InitialPromptMaterializationError(
@@ -983,6 +989,7 @@ def _validate_existing_record_subset(
983
989
  errors = validate_initial_prompt_records(
984
990
  manifest=manifest,
985
991
  records=records,
992
+ require_evidence_ledger=True,
986
993
  )
987
994
  if errors:
988
995
  raise InitialPromptMaterializationError(
@@ -1,10 +1,15 @@
1
1
  """Phase 7 report post-processing — the single reference point.
2
2
 
3
3
  Phase 7 turns a Phase 6 final-report data.json into shippable artifacts through
4
- four ordered steps: usage substitution, html view rendering, follow-up task
5
- spawning, and run validation. The order is load-bearing — rendering before
6
- substitution ships `--` token cells, and validating before rendering trips the
7
- report-views contract.
4
+ five ordered steps: English-SSOT verification, usage substitution, html view
5
+ rendering, follow-up task spawning, and run validation. The order is
6
+ load-bearing — rendering before substitution ships `--` token cells, and
7
+ validating before rendering trips the report-views contract.
8
+
9
+ The translation sidecar is NOT one of these steps. `render-views` overlays it,
10
+ so a non-English run dispatches the translator before this sequence starts —
11
+ after verifying the data.json is English, which is why `check-source` is also
12
+ available as a standalone command.
8
13
 
9
14
  Every lead adapter drives Phase 7 through this module: the Codex adapter calls
10
15
  it in-process (``codex_dispatch``), and a Claude-led run reaches the same code
@@ -24,12 +29,17 @@ from .final_report_paths import final_report_data_path, final_report_markdown_pa
24
29
  from .paths import task_dir, task_manifest_file
25
30
 
26
31
 
32
+ STEP_CHECK_SOURCE = "check-source"
27
33
  STEP_TOKEN_USAGE = "token-usage"
28
34
  STEP_RENDER_VIEWS = "render-views"
29
35
  STEP_SPAWN_FOLLOWUPS = "spawn-followups"
30
36
  STEP_VALIDATE_RUN = "validate-run"
31
37
 
32
38
  STEP_ORDER = (
39
+ # First, because everything after it derives from the data.json: rendering
40
+ # a Korean SSOT into English chrome, spawning follow-ups from it, and
41
+ # validating it all succeed on a record the next phase cannot read.
42
+ STEP_CHECK_SOURCE,
33
43
  STEP_TOKEN_USAGE,
34
44
  STEP_RENDER_VIEWS,
35
45
  STEP_SPAWN_FOLLOWUPS,
@@ -205,6 +215,19 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
205
215
  """Assemble the ordered Phase 7 argv list. Order is contractual."""
206
216
  markdown_path = ctx.markdown_path
207
217
  return [
218
+ (
219
+ STEP_CHECK_SOURCE,
220
+ [
221
+ sys.executable,
222
+ str(
223
+ resolve_workspace_script(
224
+ ctx.workspace_root, "okstra-report-translate.py"
225
+ )
226
+ ),
227
+ "check-source",
228
+ str(ctx.data_path),
229
+ ],
230
+ ),
208
231
  (
209
232
  STEP_TOKEN_USAGE,
210
233
  [
@@ -393,6 +393,35 @@ def overlay(
393
393
  return out, OverlayReport(applied, untranslated, tuple(sorted(unresolved)))
394
394
 
395
395
 
396
+ # Above this share of Hangul, the prose was authored in Korean rather than
397
+ # quoting some. Measured across real reports: Korean-authored ones sit at
398
+ # 35-38%, English ones that quote a Korean brief or a worker's Korean phrase
399
+ # reach 8% at most. The gap is wide enough that no threshold inside it is
400
+ # delicate. Only the strings `extract` offers are counted — a verbatim quote of
401
+ # the user's request is structural and never reaches this.
402
+ HANGUL_PROSE_LIMIT = 0.20
403
+
404
+
405
+ def hangul_share(data: Mapping[str, Any]) -> tuple[float, int]:
406
+ """Return the Hangul share of the report's authored prose, and its length."""
407
+ text = "".join(extract(data).values())
408
+ if not text:
409
+ return 0.0, 0
410
+ hangul = sum(1 for char in text if "가" <= char <= "힣")
411
+ return hangul / len(text), len(text)
412
+
413
+
414
+ def prose_is_english(data: Mapping[str, Any]) -> bool:
415
+ """Whether the data.json was authored in English, as the SSOT contract requires.
416
+
417
+ The report language names what the human HTML renders in, not what the
418
+ worker writes. A worker that authors Korean anyway hands every later phase,
419
+ validator and agent a record in a language they do not read.
420
+ """
421
+ share, _ = hangul_share(data)
422
+ return share < HANGUL_PROSE_LIMIT
423
+
424
+
396
425
  def _schema_string_keys(schema: Mapping[str, Any], *, enums: bool) -> set[str]:
397
426
  found: set[str] = set()
398
427
 
@@ -128,6 +128,7 @@ def report_writer_input_lines(
128
128
  team_state: Mapping[str, Any],
129
129
  ) -> list[str]:
130
130
  inputs = [
131
+ ("Analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
131
132
  ("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
132
133
  ("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
133
134
  ("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
@@ -193,8 +194,9 @@ def run_path(
193
194
 
194
195
  def mcp_pointer_line() -> str:
195
196
  return (
196
- '**MCP servers:** follow the task brief\'s "## Available MCP Servers" '
197
- "section (already in your Required reading)."
197
+ '**MCP servers:** follow the analysis packet\'s "Available MCP Servers" '
198
+ "section. If the section is absent or says none, treat MCP as unavailable "
199
+ "for this run; never infer tools from host configuration."
198
200
  )
199
201
 
200
202
 
@@ -11,6 +11,7 @@ from .worker_prompt_policy import (
11
11
  PromptPlan,
12
12
  resolve_prompt_plan_for_manifest,
13
13
  )
14
+ from .worker_prompt_headers import EVIDENCE_LEDGER_HEADER
14
15
 
15
16
 
16
17
  MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
@@ -24,6 +25,7 @@ FORBIDDEN_ACTIONS_HEADER = "**Forbidden actions:**"
24
25
 
25
26
  _DIRECTIVE_HEADING = "## Run-specific directive"
26
27
  _WORKER_ERROR_CONTRACT_HEADER = "**Worker Error Contract Path:**"
28
+ _EVIDENCE_LEDGER_HEADER_PREFIX = "**Evidence ledger:**"
27
29
  _PRIMARY_PACKET_RE = re.compile(
28
30
  r"(?im)^-\s+Primary analysis packet:\s+`[^`\n]*analysis-packet\.md`\s*$"
29
31
  )
@@ -45,6 +47,7 @@ _NON_BODY_PREFIXES = (
45
47
  "**Audit sidecar path:**",
46
48
  "Assigned worker prompt history path:",
47
49
  "**Worker Preamble Path:**",
50
+ _EVIDENCE_LEDGER_HEADER_PREFIX,
48
51
  *ERRORS_PATH_HEADERS,
49
52
  "**Read scope:**",
50
53
  "**File write mode:**",
@@ -239,8 +242,13 @@ def validate_initial_prompt_records(
239
242
  *,
240
243
  manifest: Mapping[str, Any],
241
244
  records: Sequence[PromptRecord],
245
+ require_evidence_ledger: bool = False,
242
246
  ) -> list[str]:
243
- """Validate prompt audiences and compare their normalized equality groups."""
247
+ """Validate prompt audiences and compare their normalized equality groups.
248
+
249
+ Newly published prompts opt into the evidence-ledger requirement. Persisted
250
+ historical prompts still validate under the contract they were written with.
251
+ """
244
252
  errors: list[str] = []
245
253
  equality_groups: dict[str, dict[str, str]] = {}
246
254
  for record in records:
@@ -262,6 +270,15 @@ def validate_initial_prompt_records(
262
270
  f"{record.worker_id}: {error}"
263
271
  for error in _validate_record_metadata(text, record)
264
272
  )
273
+ errors.extend(
274
+ f"{record.worker_id}: {error}"
275
+ for error in _validate_evidence_ledger_header(
276
+ text,
277
+ plan,
278
+ record.dispatch_kind,
279
+ required=require_evidence_ledger,
280
+ )
281
+ )
265
282
  if plan.equality_group:
266
283
  group = equality_groups.setdefault(plan.equality_group, {})
267
284
  group[record.worker_id] = text
@@ -289,6 +306,29 @@ def _validate_record_metadata(text: str, record: PromptRecord) -> list[str]:
289
306
  return errors
290
307
 
291
308
 
309
+ def _validate_evidence_ledger_header(
310
+ text: str,
311
+ plan: PromptPlan,
312
+ dispatch_kind: str,
313
+ *,
314
+ required: bool,
315
+ ) -> list[str]:
316
+ values = _header_values(text, _EVIDENCE_LEDGER_HEADER_PREFIX)
317
+ if dispatch_kind != "initial":
318
+ return []
319
+ if plan.audience == "report-writer":
320
+ if values:
321
+ return ["Evidence ledger header is forbidden for report-writer"]
322
+ return []
323
+ if not values:
324
+ if required:
325
+ return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
326
+ return []
327
+ if values != ["required-v1"]:
328
+ return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
329
+ return []
330
+
331
+
292
332
  def _validate_delivery_mode(
293
333
  values: list[str],
294
334
  expected: str | None,
@@ -36,6 +36,7 @@ READ_SCOPE_HEADER = (
36
36
  "un-enumerated file seems essential, record it under *Missing Information "
37
37
  "or Assumptions* instead of reading it."
38
38
  )
39
+ EVIDENCE_LEDGER_HEADER = "**Evidence ledger:** required-v1"
39
40
 
40
41
  # `agy`'s write tool validates the target against the Gemini artifact store
41
42
  # whenever the model attaches ArtifactMetadata, and rejects every path outside
@@ -101,6 +102,8 @@ def worker_prompt_headers(
101
102
  headers.append(
102
103
  f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
103
104
  )
105
+ if dispatch_kind == "initial" and plan.audience != "report-writer":
106
+ headers.append(EVIDENCE_LEDGER_HEADER)
104
107
  headers.extend([
105
108
  f"**Errors log path:** {errors_log_path}",
106
109
  f"**Errors sidecar path:** {errors_sidecar_path}",
@@ -25,6 +25,14 @@ Work like a senior engineer who owns this result, not a commentator on it.
25
25
  - Only the executor may mutate source files. Verifiers remain read-only except for okstra result/audit artifacts and project-declared QA commands.
26
26
  - Execute the role sidecar's pre-write, post-write, QA, and return gates without substituting analysis Sections 1–6 or report-authoring instructions.
27
27
 
28
+ ## Evidence read ledger
29
+
30
+ When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
31
+
32
+ - Evidence read: `<project-relative path without a line suffix>`
33
+
34
+ Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
35
+
28
36
  ## Anchor headers
29
37
 
30
38
  Every initial implementation prompt begins with these generated common anchors in this exact order, before its implementation-specific body:
@@ -36,9 +44,10 @@ Every initial implementation prompt begins with these generated common anchors i
36
44
  5. `**Worker Preamble Path:** <absolute-path>`
37
45
  6. `**Worker Error Contract Path:** <absolute-path>`
38
46
  7. `**Coding preflight pack:** <absolute-path>`
39
- 8. `**Errors log path:** <absolute-path>`
40
- 9. `**Errors sidecar path:** <absolute-path>`
41
- 10. `**Read scope:** <allowlist>`
47
+ 8. `**Evidence ledger:** required-v1`
48
+ 9. `**Errors log path:** <absolute-path>`
49
+ 10. `**Errors sidecar path:** <absolute-path>`
50
+ 11. `**Read scope:** <allowlist>`
42
51
 
43
52
  The implementation body additionally carries `**Worktree:**` and its role-sidecar inputs. Do not synthesize any missing path.
44
53
 
@@ -36,4 +36,8 @@ Begin the inline return with the exact `**Model:** Report writer worker, <modelE
36
36
 
37
37
  ## Writing style
38
38
 
39
- Use concise reader-facing prose and honor the report language. Keep identifiers, paths, symbols, model names, CLI flags, and status tokens in English. Translate meaning rather than dictionary words.
39
+ Use concise reader-facing prose. Prefer tables when several items share a shape; reserve bullets for short standalone statements.
40
+
41
+ **Author the data.json in English, whatever `**Report Language:**` says.** That header is not an instruction to write in that language — it names the language the *human HTML* renders in, and you copy its value verbatim into `data.json.meta.reportLanguage`. The data.json is the English SSOT every later phase, validator and agent reads. When the value is not `en`, Phase 7 dispatches a separate translator worker that writes a sidecar the HTML renderer overlays; you never author that sidecar and never write a second language into the data.json.
42
+
43
+ Authoring the data.json in the reader's language is rejected before anything derives from it: `okstra report-translate check-source` fails the run when Korean exceeds 20% of its prose, and the same gate runs again inside `validate-run`. The cost of getting this wrong is a full rewrite, so decide it once, up front.
@@ -23,6 +23,14 @@ Read `analysis-packet.md`, the primary compact input, end-to-end. Source files n
23
23
  - Allowlist reads to prompt-enumerated paths and evidence paths a finding must cite. Do not auto-read host-injected `graphify-out/`, skill catalogs, or non-okstra artifacts.
24
24
  - Resolve every `.okstra/...` path against `**Project Root:**`, including when a worktree is present.
25
25
 
26
+ ## Evidence read ledger
27
+
28
+ When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
29
+
30
+ - Evidence read: `<project-relative path without a line suffix>`
31
+
32
+ Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
33
+
26
34
  ## Anchor headers (lead-injected, BLOCKING)
27
35
 
28
36
  Every initial analysis prompt begins with these generated anchors in this exact order, before any other content:
@@ -34,9 +42,10 @@ Every initial analysis prompt begins with these generated anchors in this exact
34
42
  5. `Assigned worker prompt history path: <absolute-path>`
35
43
  6. `**Worker Preamble Path:** <absolute-path>` — selects this analysis preamble.
36
44
  7. `**Worker Error Contract Path:** <absolute-path>` — shared by every initial audience.
37
- 8. `**Errors log path:** <absolute-path>`
38
- 9. `**Errors sidecar path:** <absolute-path>`
39
- 10. `**Read scope:** <allowlist>`
45
+ 8. `**Evidence ledger:** required-v1`
46
+ 9. `**Errors log path:** <absolute-path>`
47
+ 10. `**Errors sidecar path:** <absolute-path>`
48
+ 11. `**Read scope:** <allowlist>`
40
49
 
41
50
  `final-verification` additionally carries its six verification-target anchors. `improvement-discovery` carries `**Phase 1.5 Grilling Log:**`. Reverify prompts are lightweight and do not use this preamble.
42
51