okstra 0.169.0 → 0.170.0
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.
- package/docs/architecture.md +17 -1
- package/docs/cli.md +11 -1
- package/docs/for-ai/skills/okstra-setup.md +8 -0
- package/docs/project-structure-overview.md +3 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-error-log.py +38 -282
- package/runtime/prompts/duties/acceptance-critic.md +25 -5
- package/runtime/prompts/duties/acceptance-verifier.md +25 -5
- package/runtime/prompts/duties/analysis-worker.md +25 -5
- package/runtime/prompts/duties/code-reviewer.md +25 -5
- package/runtime/prompts/duties/common.md +15 -11
- package/runtime/prompts/duties/diagnosis-worker.md +44 -0
- package/runtime/prompts/duties/discovery-worker.md +44 -0
- package/runtime/prompts/duties/implementation-executor.md +25 -5
- package/runtime/prompts/duties/implementation-verifier.md +25 -5
- package/runtime/prompts/duties/lead.md +25 -5
- package/runtime/prompts/duties/planning-worker.md +44 -0
- package/runtime/prompts/duties/report-writer.md +25 -5
- package/runtime/prompts/duties/reverification-worker.md +25 -5
- package/runtime/prompts/duties/schedule-verifier.md +25 -5
- package/runtime/prompts/duties/scope-critic.md +25 -5
- package/runtime/prompts/duties/translator.md +25 -5
- package/runtime/prompts/lead/convergence.md +53 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/plan-body-verification.md +5 -1
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +2 -2
- package/runtime/python/okstra_ctl/agent_invocation.py +146 -6
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +38 -0
- package/runtime/python/okstra_ctl/cmux.py +36 -19
- package/runtime/python/okstra_ctl/dispatch_core.py +317 -27
- package/runtime/python/okstra_ctl/dispatch_state.py +143 -9
- package/runtime/python/okstra_ctl/doctor.py +31 -0
- package/runtime/python/okstra_ctl/error_log_write.py +308 -0
- package/runtime/python/okstra_ctl/plan_derivations.py +94 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +114 -3
- package/runtime/python/okstra_ctl/run.py +7 -1
- package/runtime/python/okstra_ctl/schema_excerpt.py +34 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +17 -0
- package/runtime/python/okstra_ctl/worker_audit_check.py +26 -4
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +59 -9
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +24 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +2 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +12 -1
- package/runtime/python/okstra_project/resolver.py +34 -0
- package/runtime/skills/okstra-setup/references/project-config.md +38 -0
- package/runtime/validators/lib/fixtures.sh +9 -1
- package/runtime/validators/validate-run.py +37 -2
package/docs/architecture.md
CHANGED
|
@@ -383,7 +383,8 @@ If all three fail, `okstra.sh` exits immediately with an error (there is no auto
|
|
|
383
383
|
"createdAt": "2026-05-10T00:00:00Z",
|
|
384
384
|
"updatedAt": "2026-05-10T00:00:00Z",
|
|
385
385
|
"worktreeSyncDirs": [".project-docs", ".scratch", "graphify-out", ".claude"],
|
|
386
|
-
"architecture": { "style": "hexagonal" }
|
|
386
|
+
"architecture": { "style": "hexagonal" },
|
|
387
|
+
"reviewRulePacks": ["/Users/me/.claude/skills/team-pr-reviewer/SKILL.md"]
|
|
387
388
|
}
|
|
388
389
|
```
|
|
389
390
|
|
|
@@ -399,6 +400,8 @@ On the first run, it writes the four fields `projectId`, `projectRoot`, `created
|
|
|
399
400
|
|
|
400
401
|
`none` (or an absent field) keeps layer 1 only: the placement overlay stays advisory and Stage 3 stays detection-driven, so an already-configured project's behavior does not change until it opts in.
|
|
401
402
|
|
|
403
|
+
`reviewRulePacks` (optional, an array of absolute paths, default empty) declares the project's own review rule packs — the review standard a phase reads before it judges a plan or a diff, such as a team's PR-review skill. A pack used to reach a run only when the task brief cited its exact path, so whether the team standard applied depended on who wrote the brief; a declaration here applies to every run in the project, and the two channels are a union. It is read by [`scripts/okstra_project/resolver.py`](../scripts/okstra_project/resolver.py) `resolve_review_rule_packs`, which drops a relative entry — a worker's cwd is a worktree, where a relative path names a different file — and falls back to "none declared" on an unreadable or malformed `project.json`. Three phases consume it: `implementation-planning` (plan away the findings before code exists), the implementation executor's coding-conventions preflight, and the static review passes of the implementation verifier and `final-verification`. **Enforced:** `okstra doctor --phase <phase>` fails the `review rule packs` check when a declared path is not a readable file ([`scripts/okstra_ctl/doctor.py`](../scripts/okstra_ctl/doctor.py) `_review_rule_pack_check`), because a stale path otherwise costs the whole pack in silence. Whether a pack that *does* resolve was actually read stays the phase's own `project-review-rules:` record — no machine check reads a worker's reasoning.
|
|
404
|
+
|
|
402
405
|
The authoritative source for `okstra-ctl` reindex/backfill also changed under the new model. Previously it sourced `examples/projects/*.conf.sh`; it now scans `~/.okstra/projects/<projectId>/meta.json` (the mirror of the project.json information produced by record_start) to restore (projectId, projectRoot) mappings. The `OKSTRA_PROJECT_DEFINITION_DIR_OVERRIDE` environment variable has also been retired.
|
|
403
406
|
|
|
404
407
|
## Artifact-home rule
|
|
@@ -626,6 +629,19 @@ complete run manifest and its immutable `agentContract` and
|
|
|
626
629
|
change to those fields or to the canonical lead-prompt path. This prevents a
|
|
627
630
|
prompt from being verified against state that appeared only after dispatch.
|
|
628
631
|
|
|
632
|
+
A duty is bound to the **role** an invocation performs, not to its phase. The
|
|
633
|
+
analysing phases therefore share four contracts rather than one: `discovery-worker`
|
|
634
|
+
(requirements-discovery, improvement-discovery — hand over candidates without
|
|
635
|
+
starting them), `diagnosis-worker` (error-analysis — fix the symptom, establish
|
|
636
|
+
reproduction, submit only falsifiable causes), `planning-worker`
|
|
637
|
+
(implementation-planning — compare options, stage the work, and never approve its
|
|
638
|
+
own plan), and `analysis-worker` for the observational phases (project-,
|
|
639
|
+
feature-, and change-impact-analysis), which describe an area without designing
|
|
640
|
+
for it. The map lives in [`scripts/okstra_ctl/worker_prompt_policy.py`](../scripts/okstra_ctl/worker_prompt_policy.py)
|
|
641
|
+
`ANALYSIS_DUTY_BY_TASK_TYPE`; an unmapped analysis task type takes the
|
|
642
|
+
observational default. `run.py` resolves the manifest's `allowedAudiences` from
|
|
643
|
+
that same map, so a phase can only be dispatched with the duty its role owns.
|
|
644
|
+
|
|
629
645
|
`invocationAssignments` is the assignment source of truth for the lead,
|
|
630
646
|
initial workers, critics, translators, reverification workers, and report
|
|
631
647
|
writer. Each `assignmentRef` resolves to exactly six fields: `provider`,
|
package/docs/cli.md
CHANGED
|
@@ -331,6 +331,14 @@ User-added fields are preserved across that upsert, so optional settings can be
|
|
|
331
331
|
|
|
332
332
|
Declaring a style promotes that architecture's placement rules from advisory to binding. Under `hexagonal`: a variation point that `implementation-planning` decides to extract has to be extracted behind a port (`interfaceKind: "port"` — validator-enforced; deciding *not* to extract a point stays legal, the style does not force extraction), the implementation executor loads `architectures/hexagonal.md` even when directory-shape detection did not match it, and the verifier grades a placement violation as a blocking `FAIL` rather than a recommendation. `layered` has no preflight pack resource; its binding invariant is dependency direction — an upper layer may import a lower one, never the reverse — and a reverse import is a blocking placement violation found by worker judgement, since no machine check reads layer names. Leaving the field out changes nothing — the style-agnostic planning rules (variation-point analysis and test seams) apply either way. See [`architecture.md`](architecture.md) § Project self-registration for the full two-layer model.
|
|
333
333
|
|
|
334
|
+
A second optional field is `reviewRulePacks` — an array of **absolute** paths to the project's own review rule packs (a team PR-review skill's `SKILL.md`, for instance). A pack otherwise reaches a run only when the task brief cites its exact path, so the team standard applied or not depending on who wrote the brief; declaring it here applies it to every run, and the brief citation keeps working alongside it. `implementation-planning`, the implementation executor's preflight, the implementation verifier, and `final-verification` all read it. A relative entry is dropped (a worker's cwd is a worktree, where it would name a different file), and `okstra doctor --phase <phase>` fails its `review rule packs` check when a declared path is not a readable file:
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"reviewRulePacks": ["/Users/me/.claude/skills/team-pr-reviewer/SKILL.md"]
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
334
342
|
Example:
|
|
335
343
|
|
|
336
344
|
```bash
|
|
@@ -745,6 +753,8 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
745
753
|
| `okstra convergence example --kind <groups\|round-results\|critic-results\|coverage-batch>` | Print one deterministic valid input example as JSON. Each kind feeds one command: `groups` → `seed --groups`, `round-results` → `apply-round --results`, `coverage-batch` → `apply-critic-gaps --results`. `critic-results` feeds nothing — it is the critic worker's own result document, and feeding it to `apply-critic-gaps` is rejected by design; that reducer takes the coverage batch the lead assembles from those candidates plus each analyser's vote, which is what `--kind coverage-batch` prints |
|
|
746
754
|
| `okstra plan-items extract --data <data.json> --output <items.json>` | Deterministically extract the complete implementation-planning `P-*` queue from report-writer data.json |
|
|
747
755
|
| `okstra plan-items validate --data <data.json> --items <items.json>` | Require the persisted `P-*` queue to match a fresh deterministic extraction exactly |
|
|
756
|
+
| `okstra plan-items derivations --data <data.json> --response <user-response sidecar> [--clarification C-NNN]` | List the plan statements an answered clarification may have falsified. Extracts the symbols, paths, and ids the answer names (backticked spans plus `R-001` / `DEV-10174`-style ids) and reports every string in the plan body that mentions one, as a JSON pointer plus excerpt. Advisory: it says where a decision's subject is mentioned, never which mentions are now wrong — the supersession rule (`prompts/profiles/_common-contract.md`) requires the author to enumerate before editing, and this supplies the enumeration |
|
|
757
|
+
| `okstra plan-items seed --data <data.json>` | Create the `implementationPlanning.planBodyVerification.planItems[]` row every verdict lands in, from the same deterministic extraction `extract` uses. `apply-verdicts` refuses a verdict whose item has no row, and the report writer leaves the array empty, so this is the step between them. Idempotent by id: an existing row keeps its verdicts and carried fields, so it is safe to re-run between rounds. Reports `seeded` / `existing` counts |
|
|
748
758
|
| `okstra plan-items collect-verdicts --result <worker-id>=<path>… --items <items.json> --output <verdicts.json>` | Read one plan-verify round's worker responses into a verdicts envelope, using the `### <item-id>` / `**Verdict**` block shape fixed by `prompts/lead/plan-body-verification.md`. Exits 2 on an assigned item the worker left unanswered, a verdict for an item outside the persisted queue, a `DISAGREE` with no breakage kind, or a `DISAGREE` without a fixability value — every failure mode a per-round regex drops silently |
|
|
749
759
|
| `okstra plan-items apply-verdicts --data <data.json> --verdicts <verdicts.json>` | Overwrite `implementationPlanning.planBodyVerification.planItems[].verdicts` with that envelope. Overwrites rather than merges, because the contract records one round at a time and a merged table lets the previous round's votes keep voting. Exits 2 when the report carries no row for an incoming item |
|
|
750
760
|
| `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
|
|
@@ -774,7 +784,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
774
784
|
| `okstra codex-run <args…>` | Codex lead-adapter dry-run entry point. Accepts the same arguments as `render-bundle` but owns `--render-only --lead-runtime codex`. It prepares the task bundle and prints the prompt for the Codex lead without dispatching workers |
|
|
775
785
|
| `okstra worker-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Provider-neutral deterministic dispatcher for `runner=cli-wrapper` assignments. It verifies each adjacent invocation specification against the immutable run manifest immediately before process creation and records `core-pre-dispatch`; native-session rows stay with the host. The default selects CLI analysis assignments only. Phase 6 uses explicit `--workers report-writer`, and a mixed analysis/report batch is rejected. `--dry-run` performs the same verification and resolution without starting a provider process. |
|
|
776
786
|
| `okstra codex-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Compatibility alias for `okstra worker-dispatch`; it no longer selects a Codex-only transport-agent path. |
|
|
777
|
-
| `okstra agent-prompt materialize\|verify\|record-dispatch\|link-result\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. |
|
|
787
|
+
| `okstra agent-prompt materialize\|verify\|record-dispatch\|link-result\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result; one result path belongs to one dispatch, so a corrective round retires the first attempt with `reject-result --dispatch-id <first> --superseded-by <corrective> --reason <text>` before the new link is accepted — the rejected row stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason` rather than being deleted. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. A published prompt is immutable, so re-running `materialize` with an edited instruction file fails as `existing_invocation_conflict`; `--replace-undispatched` is the one exit, for a prompt that failed a pre-dispatch gate and therefore ran nowhere. It republishes prompt and metadata together, and it is verified rather than trusted — a row in `agentDispatches` or `workerDispatches` naming this `invocationId` refuses the replacement and names the dispatch that used it. |
|
|
778
788
|
| `okstra team dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--jobs-file <path>] [--dry-run]` / `okstra team await --project-root <dir> --run-manifest <path> [--json]` / `okstra team teardown --project-root <dir> --run-manifest <path> [--dry-run] [--json]` | Read a `leadRuntime=external` run manifest and dispatch, await, or tear down tmux-pane workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a tmux pane cannot be created, gracefully degrade to the CLI wrapper and record the fallback in `workerDispatches[].degradedFrom` |
|
|
779
789
|
| `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, then prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order, which matters because `validate-run` is the step that usually fails and retrying it otherwise repeats the three steps before it at full token and wall-clock cost. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) the Codex lead adapter runs automatically after its report-writer completes, so a Claude-led and a Codex-led run finalize identically. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the four steps individually |
|
|
780
790
|
| `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly (contract: `schemas/final-report-v2.0.schema.json`) into an always-generated, task-specific human HTML sibling while `templates/reports/final-report-v2.template.md` independently owns the AI handoff Markdown. Passing the Markdown sibling locates the same v2 data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, and Response ID parity |
|
|
@@ -113,6 +113,14 @@ Optional settings:
|
|
|
113
113
|
constraint — under `hexagonal` an extracted variation point must be a port,
|
|
114
114
|
under `layered` the dependency direction is worker-judged with no machine
|
|
115
115
|
check. See section F of `references/project-config.md`.
|
|
116
|
+
- `reviewRulePacks`: absolute paths to the project's own review rule packs (a
|
|
117
|
+
team PR-review skill's `SKILL.md`). Without a declaration a pack applies only
|
|
118
|
+
when the task brief cites its exact path; declared here it applies to every
|
|
119
|
+
run, and the two channels are a union. Read by `implementation-planning`, the
|
|
120
|
+
executor preflight, the implementation verifier, and `final-verification`.
|
|
121
|
+
`okstra setup` never writes it. `okstra doctor --phase <phase>` fails when a
|
|
122
|
+
declared path is not readable. See section G of
|
|
123
|
+
`references/project-config.md`.
|
|
116
124
|
|
|
117
125
|
If `qaCommands.cmd` contains a token implying mutation, the verifier refuses it. The actual authority for the deny-list is `scripts/okstra_ctl/qa_commands.py`.
|
|
118
126
|
|
|
@@ -273,6 +273,7 @@ Important modules:
|
|
|
273
273
|
| `index.py`, `jsonl.py`, `reconcile.py`, `listing.py`, `batch.py`, `backfill.py` | `~/.okstra` run index and history operations |
|
|
274
274
|
| `run_index_row.py` | single reference point for creating / slimming / hydrating a `~/.okstra` run-index row — runId SSOT, preserves projectId raw |
|
|
275
275
|
| `error_report.py`, `error_log_core.py`, `error_zip.py` | backend for the okstra-inspect errors/error-zip facets — `error_log_core` is the read-only core that globs/parses/aggregates `errors-*.jsonl`, `error_report` renders the errors facet, and `error_zip` collects cross-project run directories, allowlist-anonymizes, aggregates clusters, and produces a zip |
|
|
276
|
+
| `error_log_write.py` | the single writer for `errors-*.jsonl`, shared by the `okstra error-log` CLI and by `dispatch_core`, which records a wrapper's non-zero exit as a `cli-failure` in-process. Owns the agent/role/error-type allow-lists (agents derived from the provider registry) and the cause-evidence gate |
|
|
276
277
|
| `run_audit.py` | backend for the okstra-inspect run-audit facet — reads run-manifest / final-report / team-state artifacts and reports invariant violations (read-only, never the lead's self-report) |
|
|
277
278
|
| `worker_heartbeat.py`, `worker_liveness.py` | `worker_heartbeat` is the single definition of the `- PROGRESS:` heartbeat line shape and its 5-minute (+60s grace) cadence budget, shared by the Phase 7 audit (`validators/validate_session_conformance.py`) and the live probe; `worker_liveness` backs `okstra worker-liveness`, resolving each pending worker from its team-state row (`livenessMode` picks the artifact, `startedAt` anchors the grace) and reporting `stalled` (heartbeat past the budget, or none yet for this dispatch past the grace) or `did-not-launch` (no wrapper `.log`/`.status.json` past the launch grace) |
|
|
278
279
|
| `log_report.py`, `time_report.py` | read-side backend for the okstra-inspect logs/time facets (`okstra log-report` pairs each wrapper transcript `.log` with its sibling prompt `.md` and reports both byte counts without changing legacy transcript-size fields; `okstra time-report` is per-task time aggregation) |
|
|
@@ -323,7 +324,8 @@ Important modules:
|
|
|
323
324
|
| `convergence_engine.py` | pure `ConvergenceEngine` reducer — seeds Round 0 working state, plans roster-aware rounds, applies structured outcomes and one critic-gap batch, finalizes schema v1.3, and validates replayable state without dispatch or filesystem ownership |
|
|
324
325
|
| `convergence_store.py`, `convergence_migration.py` | atomic JSON persistence plus legacy/new-engine seed decisions; valid terminal finals are reused, while invalid state requires byte-preserving archival before restart |
|
|
325
326
|
| `convergence.py` | `okstra convergence` internal CLI orchestration for `seed`, `plan-round`, `apply-round`, `apply-critic-gaps`, `finalize`, `validate`, and `example`; it composes the reducer, store, and migration policy without duplicating their decisions |
|
|
326
|
-
| `plan_items.py`, `plan_items_cli.py` | deterministic extraction of the report-writer data.json `P-*` plan-item queue plus the `okstra plan-items extract` / `validate` adapter |
|
|
327
|
+
| `plan_items.py`, `plan_items_cli.py` | deterministic extraction of the report-writer data.json `P-*` plan-item queue plus the `okstra plan-items extract` / `validate` / `seed` / `collect-verdicts` / `apply-verdicts` / `derivations` adapter |
|
|
328
|
+
| `plan_derivations.py` | the supersession sweep `_common-contract.md` requires an author to do by hand — extracts the symbols, paths, and ids an answered clarification names and reports every plan string that mentions one. Advisory: it locates candidates and never judges which are now false |
|
|
327
329
|
| `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
|
|
328
330
|
| `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
|
|
329
331
|
| `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -1,296 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""OKSTRA error log helper.
|
|
2
|
+
"""OKSTRA error log helper — CLI adapter.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
The writer itself lives in `okstra_ctl.error_log_write`, which the deterministic
|
|
5
|
+
dispatcher also calls in-process. This file is the argparse surface over it and
|
|
6
|
+
re-exports the writer's names so the contract tests keep reading one module.
|
|
5
7
|
"""
|
|
6
8
|
from __future__ import annotations
|
|
7
9
|
|
|
8
10
|
import argparse
|
|
9
|
-
import datetime as dt
|
|
10
11
|
import json
|
|
11
|
-
import os
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ALLOWED_ERROR_TYPES
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
CAUSE_EVIDENCE_FIELDS = ("targetProbe", "controlProbe")
|
|
31
|
-
# Both probes share a record's PIPE_BUF_BYTES budget with stderrExcerpt, so
|
|
32
|
-
# they cannot reuse the 2048 cap that assumes stderrExcerpt owns it alone.
|
|
33
|
-
CAUSE_PROBE_MAX_BYTES = 256
|
|
34
|
-
# Backstop vocabulary: scanned in `message` only — never in stderrExcerpt,
|
|
35
|
-
# where a kernel's real "Operation not permitted" is legitimate content.
|
|
36
|
-
# Deliberately excludes bare "blocked"/"blocks": everyday English that would
|
|
37
|
-
# reject honest records like "test blocked on upstream dependency".
|
|
38
|
-
_BLOCKING_CLAIM_TERMS = (
|
|
39
|
-
"sandbox", "not permitted", "permission denied", "eperm",
|
|
14
|
+
from okstra_ctl.error_log_write import (
|
|
15
|
+
ALLOWED_AGENT_ROLES,
|
|
16
|
+
ALLOWED_AGENTS,
|
|
17
|
+
ALLOWED_CAUSES,
|
|
18
|
+
ALLOWED_ERROR_TYPES,
|
|
19
|
+
CAUSE_EVIDENCE_FIELDS,
|
|
20
|
+
CAUSE_PROBE_MAX_BYTES,
|
|
21
|
+
PIPE_BUF_BYTES,
|
|
22
|
+
STDERR_EXCERPT_MAX_BYTES,
|
|
23
|
+
SUPPORTED_SIDECAR_SCHEMA_VERSIONS,
|
|
24
|
+
TRUNCATION_SUFFIX,
|
|
25
|
+
append_jsonl_line,
|
|
26
|
+
append_observed,
|
|
27
|
+
dump_from_worker_sidecar,
|
|
28
|
+
normalize_cause_context,
|
|
29
|
+
truncate_stderr,
|
|
40
30
|
)
|
|
41
|
-
# The backstop targets *unclassified* blocking claims. A worker that declared
|
|
42
|
-
# a specific cause has already done the honest work — `auth-failed` legitimately
|
|
43
|
-
# reads "permission denied" (MySQL 1045).
|
|
44
|
-
_UNCLASSIFIED_CAUSES = (None, "unknown")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _now_utc():
|
|
48
|
-
return dt.datetime.now(dt.timezone.utc)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _iso(t):
|
|
52
|
-
return t.isoformat()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _truncate_utf8(s, limit):
|
|
56
|
-
"""Truncate to `limit` bytes without splitting a multibyte character."""
|
|
57
|
-
if s is None:
|
|
58
|
-
return None
|
|
59
|
-
encoded = s.encode("utf-8")
|
|
60
|
-
if len(encoded) <= limit:
|
|
61
|
-
return s
|
|
62
|
-
cut = encoded[:limit]
|
|
63
|
-
while cut:
|
|
64
|
-
try:
|
|
65
|
-
return cut.decode("utf-8") + TRUNCATION_SUFFIX
|
|
66
|
-
except UnicodeDecodeError:
|
|
67
|
-
cut = cut[:-1]
|
|
68
|
-
return TRUNCATION_SUFFIX
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def truncate_stderr(s):
|
|
72
|
-
"""Truncate stderr text to STDERR_EXCERPT_MAX_BYTES, multibyte-safe."""
|
|
73
|
-
return _truncate_utf8(s, STDERR_EXCERPT_MAX_BYTES)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def normalize_cause_context(context, *, message):
|
|
77
|
-
"""Validate a record's cause claim and return the normalized context.
|
|
78
|
-
|
|
79
|
-
Raises ValueError on three conditions:
|
|
80
|
-
- `cause` is set to a value outside ALLOWED_CAUSES;
|
|
81
|
-
- `cause` is 'sandbox-denied' but the two probes are missing or blank —
|
|
82
|
-
those probes are what distinguish a real denial from an unreachable
|
|
83
|
-
or auth-gated target, the misdiagnosis this gate exists to stop;
|
|
84
|
-
- `message` asserts a block in prose while the record left its cause
|
|
85
|
-
unclassified, which would smuggle the same claim past the gate.
|
|
86
|
-
"""
|
|
87
|
-
cause = context.get("cause") if isinstance(context, dict) else None
|
|
88
|
-
|
|
89
|
-
if cause is not None and cause not in ALLOWED_CAUSES:
|
|
90
|
-
raise ValueError(
|
|
91
|
-
f"invalid cause: {cause!r} (allowed: {sorted(ALLOWED_CAUSES)})"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if cause == "sandbox-denied":
|
|
95
|
-
evidence = context.get("causeEvidence")
|
|
96
|
-
if not isinstance(evidence, dict):
|
|
97
|
-
raise ValueError(
|
|
98
|
-
"cause 'sandbox-denied' requires context.causeEvidence with "
|
|
99
|
-
f"{list(CAUSE_EVIDENCE_FIELDS)}"
|
|
100
|
-
)
|
|
101
|
-
normalized_evidence = {}
|
|
102
|
-
for field in CAUSE_EVIDENCE_FIELDS:
|
|
103
|
-
value = evidence.get(field)
|
|
104
|
-
if not isinstance(value, str) or not value.strip():
|
|
105
|
-
raise ValueError(
|
|
106
|
-
f"cause 'sandbox-denied' requires a non-empty "
|
|
107
|
-
f"context.causeEvidence.{field}: record the command and "
|
|
108
|
-
f"its raw output that proves the claim"
|
|
109
|
-
)
|
|
110
|
-
normalized_evidence[field] = _truncate_utf8(
|
|
111
|
-
value, CAUSE_PROBE_MAX_BYTES
|
|
112
|
-
)
|
|
113
|
-
return {**context, "causeEvidence": normalized_evidence}
|
|
114
31
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
With ``O_APPEND`` and a single ``write()`` syscall, the kernel
|
|
134
|
-
appends the entire payload as one indivisible operation as long as
|
|
135
|
-
the payload size is at most ``PIPE_BUF`` (4096 bytes on Linux and
|
|
136
|
-
macOS). Larger payloads may be split across syscalls and interleave
|
|
137
|
-
with concurrent writers, so this helper rejects them with
|
|
138
|
-
``ValueError`` rather than silently losing atomicity.
|
|
139
|
-
|
|
140
|
-
The atomicity contract holds only on POSIX filesystems with O_APPEND
|
|
141
|
-
semantics. Concurrent writers using ``O_TRUNC``, ``unlink``, or
|
|
142
|
-
non-append modes against the same path break the contract and are
|
|
143
|
-
out of scope for this helper.
|
|
144
|
-
|
|
145
|
-
Caller responsibilities:
|
|
146
|
-
- Keep records small (this module's stderr excerpt cap of
|
|
147
|
-
``STDERR_EXCERPT_MAX_BYTES`` exists to keep records well under
|
|
148
|
-
``PIPE_BUF_BYTES``).
|
|
149
|
-
- Handle ``TypeError`` from ``json.dumps`` for non-serializable values.
|
|
150
|
-
|
|
151
|
-
Creates parent directories as needed.
|
|
152
|
-
"""
|
|
153
|
-
p = Path(path)
|
|
154
|
-
p.parent.mkdir(parents=True, exist_ok=True)
|
|
155
|
-
# ensure_ascii=False keeps UTF-8 compact (no \uXXXX escapes).
|
|
156
|
-
# json.dumps escapes literal newlines inside string values, so the
|
|
157
|
-
# only unescaped newline is the record separator we append below.
|
|
158
|
-
line = json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n"
|
|
159
|
-
data = line.encode("utf-8")
|
|
160
|
-
if len(data) > PIPE_BUF_BYTES:
|
|
161
|
-
raise ValueError(
|
|
162
|
-
f"record too large for atomic append: {len(data)} bytes > "
|
|
163
|
-
f"PIPE_BUF ({PIPE_BUF_BYTES})"
|
|
164
|
-
)
|
|
165
|
-
# mode 0o644: owner read/write, group/world read-only.
|
|
166
|
-
fd = os.open(str(p), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
|
|
167
|
-
try:
|
|
168
|
-
os.write(fd, data)
|
|
169
|
-
finally:
|
|
170
|
-
os.close(fd)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def append_observed(
|
|
174
|
-
*,
|
|
175
|
-
out_path,
|
|
176
|
-
task_key,
|
|
177
|
-
phase,
|
|
178
|
-
agent,
|
|
179
|
-
agent_role,
|
|
180
|
-
model,
|
|
181
|
-
error_type,
|
|
182
|
-
command,
|
|
183
|
-
command_kind,
|
|
184
|
-
exit_code,
|
|
185
|
-
duration_ms,
|
|
186
|
-
message,
|
|
187
|
-
stderr_excerpt,
|
|
188
|
-
context,
|
|
189
|
-
now=None,
|
|
190
|
-
):
|
|
191
|
-
"""Append a lead-observed error event to errors.jsonl."""
|
|
192
|
-
if error_type not in ALLOWED_ERROR_TYPES:
|
|
193
|
-
raise ValueError(f"invalid errorType: {error_type!r}")
|
|
194
|
-
if agent not in ALLOWED_AGENTS:
|
|
195
|
-
raise ValueError(f"invalid agent: {agent!r}")
|
|
196
|
-
if agent_role not in ALLOWED_AGENT_ROLES:
|
|
197
|
-
raise ValueError(f"invalid agentRole: {agent_role!r}")
|
|
198
|
-
# Runs before append_jsonl_line so a rejected claim leaves no trace in the
|
|
199
|
-
# log: a written-then-flagged record is still a record someone can cite.
|
|
200
|
-
context = normalize_cause_context(context, message=message)
|
|
201
|
-
ts = _iso(now or _now_utc())
|
|
202
|
-
rec = {
|
|
203
|
-
"ts": ts,
|
|
204
|
-
"recordedAt": ts,
|
|
205
|
-
"taskKey": task_key,
|
|
206
|
-
"phase": str(phase),
|
|
207
|
-
"agent": agent,
|
|
208
|
-
"agentRole": agent_role,
|
|
209
|
-
"model": model,
|
|
210
|
-
"source": "lead-observed",
|
|
211
|
-
"errorType": error_type,
|
|
212
|
-
"command": command,
|
|
213
|
-
"commandKind": command_kind,
|
|
214
|
-
"exitCode": exit_code,
|
|
215
|
-
"durationMs": duration_ms,
|
|
216
|
-
"message": message,
|
|
217
|
-
"stderrExcerpt": truncate_stderr(stderr_excerpt),
|
|
218
|
-
"context": context,
|
|
219
|
-
}
|
|
220
|
-
append_jsonl_line(out_path, rec)
|
|
221
|
-
return rec
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def dump_from_worker_sidecar(
|
|
225
|
-
*,
|
|
226
|
-
sidecar_path,
|
|
227
|
-
out_path,
|
|
228
|
-
task_key,
|
|
229
|
-
agent,
|
|
230
|
-
agent_role,
|
|
231
|
-
model,
|
|
232
|
-
now=None,
|
|
233
|
-
):
|
|
234
|
-
"""Read worker sidecar errors[] and append each to errors.jsonl with
|
|
235
|
-
Lead-side metadata filled in. Returns number of records appended.
|
|
236
|
-
|
|
237
|
-
Raises ValueError if:
|
|
238
|
-
- ``agent`` or ``agent_role`` is not in the allow-lists
|
|
239
|
-
- sidecar ``schemaVersion`` is not in ``SUPPORTED_SIDECAR_SCHEMA_VERSIONS``
|
|
240
|
-
- any entry's ``errorType`` is not in ``ALLOWED_ERROR_TYPES``
|
|
241
|
-
- any entry asserts a blocking cause without its required evidence
|
|
242
|
-
|
|
243
|
-
Returns 0 (no-op) if the sidecar file does not exist or its
|
|
244
|
-
``errors`` list is empty.
|
|
245
|
-
|
|
246
|
-
Partial-failure semantics: entries are validated and appended in
|
|
247
|
-
order. If entry N fails validation, entries 0..N-1 have already
|
|
248
|
-
been written to ``out_path`` and are NOT rolled back. Callers that
|
|
249
|
-
require atomicity must validate the sidecar payload before invoking
|
|
250
|
-
this function.
|
|
251
|
-
"""
|
|
252
|
-
if agent not in ALLOWED_AGENTS:
|
|
253
|
-
raise ValueError(f"invalid agent: {agent!r}")
|
|
254
|
-
if agent_role not in ALLOWED_AGENT_ROLES:
|
|
255
|
-
raise ValueError(f"invalid agentRole: {agent_role!r}")
|
|
256
|
-
p = Path(sidecar_path)
|
|
257
|
-
if not p.exists():
|
|
258
|
-
return 0
|
|
259
|
-
payload = json.loads(p.read_text())
|
|
260
|
-
schema = payload.get("schemaVersion")
|
|
261
|
-
if schema not in SUPPORTED_SIDECAR_SCHEMA_VERSIONS:
|
|
262
|
-
raise ValueError(f"unsupported sidecar schemaVersion: {schema!r}")
|
|
263
|
-
entries = payload.get("errors") or []
|
|
264
|
-
recorded_at = _iso(now or _now_utc())
|
|
265
|
-
count = 0
|
|
266
|
-
for e in entries:
|
|
267
|
-
et = e.get("errorType")
|
|
268
|
-
if et not in ALLOWED_ERROR_TYPES:
|
|
269
|
-
raise ValueError(f"invalid errorType in sidecar: {et!r}")
|
|
270
|
-
entry_context = normalize_cause_context(
|
|
271
|
-
e.get("context"), message=e.get("message")
|
|
272
|
-
)
|
|
273
|
-
rec = {
|
|
274
|
-
"ts": e.get("ts"),
|
|
275
|
-
"recordedAt": recorded_at,
|
|
276
|
-
"taskKey": task_key,
|
|
277
|
-
"phase": str(e.get("phase")) if e.get("phase") is not None else None,
|
|
278
|
-
"agent": agent,
|
|
279
|
-
"agentRole": agent_role,
|
|
280
|
-
"model": model,
|
|
281
|
-
"source": "worker-reported",
|
|
282
|
-
"errorType": et,
|
|
283
|
-
"command": e.get("command"),
|
|
284
|
-
"commandKind": e.get("commandKind"),
|
|
285
|
-
"exitCode": e.get("exitCode"),
|
|
286
|
-
"durationMs": e.get("durationMs"),
|
|
287
|
-
"message": e.get("message"),
|
|
288
|
-
"stderrExcerpt": truncate_stderr(e.get("stderrExcerpt")),
|
|
289
|
-
"context": entry_context,
|
|
290
|
-
}
|
|
291
|
-
append_jsonl_line(out_path, rec)
|
|
292
|
-
count += 1
|
|
293
|
-
return count
|
|
32
|
+
__all__ = [
|
|
33
|
+
"ALLOWED_AGENT_ROLES",
|
|
34
|
+
"ALLOWED_AGENTS",
|
|
35
|
+
"ALLOWED_CAUSES",
|
|
36
|
+
"ALLOWED_ERROR_TYPES",
|
|
37
|
+
"CAUSE_EVIDENCE_FIELDS",
|
|
38
|
+
"CAUSE_PROBE_MAX_BYTES",
|
|
39
|
+
"PIPE_BUF_BYTES",
|
|
40
|
+
"STDERR_EXCERPT_MAX_BYTES",
|
|
41
|
+
"SUPPORTED_SIDECAR_SCHEMA_VERSIONS",
|
|
42
|
+
"TRUNCATION_SUFFIX",
|
|
43
|
+
"append_jsonl_line",
|
|
44
|
+
"append_observed",
|
|
45
|
+
"dump_from_worker_sidecar",
|
|
46
|
+
"main",
|
|
47
|
+
"normalize_cause_context",
|
|
48
|
+
"truncate_stderr",
|
|
49
|
+
]
|
|
294
50
|
|
|
295
51
|
|
|
296
52
|
def _build_parser():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: acceptance-critic
|
|
3
|
-
version:
|
|
3
|
+
version: 3
|
|
4
4
|
kind: role
|
|
5
5
|
appliesTo: acceptance-critic
|
|
6
6
|
---
|
|
@@ -9,16 +9,36 @@ appliesTo: acceptance-critic
|
|
|
9
9
|
|
|
10
10
|
## Responsibility
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Challenge a declared completion as an adversarial but fair reviewer, and return distinct, evidence-backed candidate defects that could invalidate it or prevent acceptance.
|
|
13
13
|
|
|
14
14
|
## Required conduct
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Map each challenged claim to its acceptance basis, inspect the supporting evidence, attempt to falsify it through the most relevant boundary or omission, check whether the candidate is already known, and state the concrete acceptance consequence.
|
|
17
|
+
|
|
18
|
+
## Decision principles
|
|
19
|
+
|
|
20
|
+
Target the strongest completion claims rather than the easiest ones, and prioritize candidates that are both plausible and acceptance-relevant. Prefer one well-supported counterexample over many weak suspicions, distinguish a new defect from a duplicate or narrower restatement, and leave the final acceptance judgment to the verifier or lead.
|
|
21
|
+
|
|
22
|
+
## Authority and boundaries
|
|
23
|
+
|
|
24
|
+
Challenge only the declared completion within the assigned acceptance scope. Inspect and test as authorized, but do not modify the deliverable, expand the acceptance standard, or decide the final outcome.
|
|
25
|
+
|
|
26
|
+
## Evidence standard
|
|
27
|
+
|
|
28
|
+
Every candidate must identify the challenged claim, the observed or reproducible counterevidence, and why that evidence could change acceptance. Label an unexecuted concern as a hypothesis rather than a defect.
|
|
29
|
+
|
|
30
|
+
## Collaboration contract
|
|
31
|
+
|
|
32
|
+
Remain independent from the acceptance verifier and other critics. Return distinct candidates in a form they can evaluate without prescribing their verdict, and preserve any evidence that weakens your own challenge.
|
|
33
|
+
|
|
34
|
+
## Completion criteria
|
|
35
|
+
|
|
36
|
+
The strongest material completion claims have been challenged, every submitted candidate is distinct and evidence-backed, duplicates and non-acceptance preferences have been excluded, and unchallenged areas are acknowledged.
|
|
17
37
|
|
|
18
38
|
## Forbidden conduct
|
|
19
39
|
|
|
20
|
-
Do not repeat an existing defect, lower
|
|
40
|
+
Do not repeat an existing defect, lower or invent an acceptance standard, omit counterevidence, inflate speculative edge cases into failures, repair the deliverable, or make the final acceptance decision.
|
|
21
41
|
|
|
22
42
|
## Blocked-state reporting
|
|
23
43
|
|
|
24
|
-
Name the completion claim that cannot be challenged
|
|
44
|
+
Name the completion claim that cannot be challenged, the inspection or test attempted, the exact evidence or capability missing, and the acceptance risk that remains unknown.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: acceptance-verifier
|
|
3
|
-
version:
|
|
3
|
+
version: 3
|
|
4
4
|
kind: role
|
|
5
5
|
appliesTo: acceptance-verifier
|
|
6
6
|
---
|
|
@@ -9,16 +9,36 @@ appliesTo: acceptance-verifier
|
|
|
9
9
|
|
|
10
10
|
## Responsibility
|
|
11
11
|
|
|
12
|
-
Independently decide whether
|
|
12
|
+
Independently decide whether every declared acceptance criterion and required deliverable is satisfied by the current state, as the final evidence gate for the assigned acceptance scope.
|
|
13
13
|
|
|
14
14
|
## Required conduct
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Enumerate every criterion and deliverable, inspect the current artifact or behavior, evaluate supporting and contrary evidence, reproduce decisive checks when authorized, and return an explicit pass, fail, or blocked judgment for each item and for the overall scope.
|
|
17
|
+
|
|
18
|
+
## Decision principles
|
|
19
|
+
|
|
20
|
+
Pass only what the evidence establishes. Fail criteria contradicted by current evidence, block criteria that cannot be decided because required evidence is unavailable, stay conservative wherever a required outcome remains unobserved, and keep advisory quality concerns separate from acceptance requirements.
|
|
21
|
+
|
|
22
|
+
## Authority and boundaries
|
|
23
|
+
|
|
24
|
+
Judge only the declared acceptance contract and current deliverables. Do not change the implementation, redefine criteria, waive a requirement without recorded authority, or convert desirable improvements into mandatory acceptance conditions.
|
|
25
|
+
|
|
26
|
+
## Evidence standard
|
|
27
|
+
|
|
28
|
+
Each item verdict must cite the criterion, the actual artifact or observation evaluated, the decisive evidence, and any relevant limitation. Passing unrelated checks cannot substitute for evidence of the criterion itself.
|
|
29
|
+
|
|
30
|
+
## Collaboration contract
|
|
31
|
+
|
|
32
|
+
Evaluate executor claims and critic candidates on their evidence rather than their source. Preserve unresolved disagreement and route it to the lead; do not coordinate a verdict or ask the producing role to certify its own work.
|
|
33
|
+
|
|
34
|
+
## Completion criteria
|
|
35
|
+
|
|
36
|
+
Every criterion and deliverable has a traceable disposition, the overall verdict is consistent with all item verdicts, blocking uncertainty is explicit, and residual non-blocking risk is separated from acceptance failure.
|
|
17
37
|
|
|
18
38
|
## Forbidden conduct
|
|
19
39
|
|
|
20
|
-
Do not infer acceptance from effort, intent, or unrelated passing checks.
|
|
40
|
+
Do not infer acceptance from effort, intent, file existence, vote count, or unrelated passing checks; do not hide an undecidable criterion, repair the subject under review, or silently lower the standard.
|
|
21
41
|
|
|
22
42
|
## Blocked-state reporting
|
|
23
43
|
|
|
24
|
-
List each undecidable criterion
|
|
44
|
+
List each undecidable criterion, the exact missing artifact, environment, authority, or observation, the checks attempted, and the effect on the overall verdict.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: analysis-worker
|
|
3
|
-
version:
|
|
3
|
+
version: 4
|
|
4
4
|
kind: role
|
|
5
5
|
appliesTo: analysis-worker
|
|
6
6
|
---
|
|
@@ -9,16 +9,36 @@ appliesTo: analysis-worker
|
|
|
9
9
|
|
|
10
10
|
## Responsibility
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Describe the assigned area as it actually is — its behavior, structure, dependencies, and the impact a proposed change would have — so later work can navigate it without rediscovering it, and without this role changing or redesigning anything.
|
|
13
13
|
|
|
14
14
|
## Required conduct
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Read the assigned inputs completely, address every assigned question, inspect the sources needed to support each material claim, surface the assumptions the inputs leave implicit, identify counterevidence, distinguish what the scope showed from what it did not reach, and state uncertainty explicitly.
|
|
17
|
+
|
|
18
|
+
## Decision principles
|
|
19
|
+
|
|
20
|
+
Rank findings by consequence, confidence, and relevance to the assignment. Report what the code establishes rather than what it suggests, prefer a falsifiable statement over a broad impression, separate observed behavior from its possible explanations, and leave a block out rather than guessing at its contents.
|
|
21
|
+
|
|
22
|
+
## Authority and boundaries
|
|
23
|
+
|
|
24
|
+
Analyze only the assigned scope. Read additional evidence only when it is necessary to verify a claim or resolve an identified gap. Do not mutate project state, and do not turn description into design: implementation alternatives, file-change specifications, and execution plans belong to the planning role, not this one.
|
|
25
|
+
|
|
26
|
+
## Evidence standard
|
|
27
|
+
|
|
28
|
+
Support each finding with evidence that directly bears on the claim and identify the inspected location or observation. State when evidence is indirect, incomplete, stale, or contradicted; absence of evidence is not evidence of absence.
|
|
29
|
+
|
|
30
|
+
## Collaboration contract
|
|
31
|
+
|
|
32
|
+
Reason independently from other workers and do not imitate their expected answers, coordinate conclusions, or optimize for consensus. Hold a minority conclusion whose evidence is stronger rather than folding it into the expected answer, and leave cross-worker synthesis and final acceptance to the lead while making disagreements easy to compare.
|
|
33
|
+
|
|
34
|
+
## Completion criteria
|
|
35
|
+
|
|
36
|
+
Every assigned question has an explicit disposition; material findings, assumptions, counterevidence, unreached areas, uncertainty, and recommended next actions are recorded; and each conclusion is traceable to inspected evidence.
|
|
17
37
|
|
|
18
38
|
## Forbidden conduct
|
|
19
39
|
|
|
20
|
-
Do not
|
|
40
|
+
Do not expand the assigned question, omit inconvenient evidence, inflate preferences into defects, present speculation as fact, propose an implementation approach the assignment did not ask for, repeat another worker's conclusion without independent support, or claim completeness after sampling only part of a required input.
|
|
21
41
|
|
|
22
42
|
## Blocked-state reporting
|
|
23
43
|
|
|
24
|
-
Identify the unavailable evidence, the checks attempted, and the precise
|
|
44
|
+
Identify the unavailable or contradictory evidence, the checks attempted, the questions affected, and the precise limit the blocker places on the requested conclusion.
|