@alphazede/bearing-lite 0.1.10 → 0.2.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/CONTRIBUTING.md +25 -0
- package/README.md +76 -32
- package/hooks/assurance-budget.cjs +167 -0
- package/hooks/closeout.cjs +64 -15
- package/hooks/com.anthropic.claude-code/host.cjs +8 -11
- package/hooks/com.anthropic.claude-code/mapping.md +88 -2
- package/hooks/com.cursor/hooks.json +11 -1
- package/hooks/hooks.json +45 -1
- package/hooks/planning-review.cjs +99 -0
- package/hooks/te-capability.cjs +137 -0
- package/hooks/te-host.cjs +620 -0
- package/hooks/transition-order.cjs +45 -2
- package/lineups.json +1 -0
- package/package.json +9 -4
- package/plugin.json +1 -1
- package/schemas/authority.schema.json +75 -0
- package/schemas/implementation.schema.json +127 -0
- package/schemas/journey.schema.json +88 -0
- package/schemas/lineups.schema.json +145 -0
- package/schemas/seit.schema.json +108 -0
- package/skills/bearing-lite/SKILL.md +39 -46
- package/skills/bearing-lite/agents/openai.yaml +1 -1
- package/skills/bearing-lite/references/assurance-policy.md +66 -0
- package/skills/bearing-lite/references/lineups.md +81 -0
- package/skills/bearing-lite/references/peer-synthesis.md +8 -4
- package/skills/bearing-lite/references/review-policy.md +37 -0
- package/skills/bearing-lite/references/role-routing.mmd +10 -10
- package/skills/bearing-lite/references/task-state.md +6 -6
- package/skills/bearing-lite/references/task-state.mmd +1 -1
- package/skills/bearing-lite/templates/default-role-lineup.md +11 -3
- package/skills/bearing-lite/templates/task.md +75 -21
- package/skills/crewmate/SKILL.md +26 -17
- package/skills/explorer/SKILL.md +35 -34
- package/skills/gather-supplies/SKILL.md +9 -2
- package/skills/integration-engineer/SKILL.md +41 -0
- package/skills/map-the-route/SKILL.md +48 -36
- package/skills/map-the-route/references/artifact-grammar.md +94 -35
- package/skills/navigator/SKILL.md +18 -41
- package/skills/park-ranger/SKILL.md +24 -16
- package/skills/plan-integrator/SKILL.md +37 -0
- package/skills/scribe/SKILL.md +34 -0
- package/skills/surveyor/SKILL.md +22 -15
- package/skills/systems-modeler/SKILL.md +35 -0
- package/skills/test-engineer/SKILL.md +46 -0
- package/skills/validator/SKILL.md +20 -25
- package/skills/validator/references/grading-rubric.md +5 -4
package/CONTRIBUTING.md
CHANGED
|
@@ -54,8 +54,33 @@ checkout):
|
|
|
54
54
|
node --test test/diagram-sync.test.mjs
|
|
55
55
|
node --test test/public-boundary.test.mjs
|
|
56
56
|
node --test test/skills-conformance.test.mjs
|
|
57
|
+
python3 test/schema-validation.py
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
Schema validation requires Python 3.12. `python3 test/schema-validation.py` is
|
|
61
|
+
the required developer and CI gate (`CMD-LITE-SCHEMA-VALIDATE`). It installs the
|
|
62
|
+
pinned hashes from `test/schema-validator-requirements.txt` into an isolated
|
|
63
|
+
temporary directory with `pip install --isolated --require-hashes --no-deps` and
|
|
64
|
+
runs Draft 2020-12 validation with no remote `$ref` retrieval. Do not install
|
|
65
|
+
those packages globally, with `--user`, or as production dependencies of this
|
|
66
|
+
package.
|
|
67
|
+
|
|
68
|
+
CI on `ubuntu-24.04` with Python 3.12 x64 performs the same isolated
|
|
69
|
+
`--require-hashes --no-deps` install into a job-local target directory, then
|
|
70
|
+
runs `python3 test/schema-validation.py`. To reuse a local isolated tree instead
|
|
71
|
+
of a temporary install:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
python3.12 -m venv .venv-schema-validator
|
|
75
|
+
.venv-schema-validator/bin/python -m pip install \
|
|
76
|
+
--isolated --require-hashes --no-deps \
|
|
77
|
+
-r test/schema-validator-requirements.txt
|
|
78
|
+
BEARING_LITE_SCHEMA_ISOLATED_ROOT=.venv-schema-validator/lib/python3.12/site-packages \
|
|
79
|
+
python3 test/schema-validation.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Keep that virtualenv uncommitted.
|
|
83
|
+
|
|
59
84
|
Keep skills lean. Prefer references and assets only when progressive disclosure
|
|
60
85
|
needs them. Do not add dependencies or change the lockfile without explicit
|
|
61
86
|
maintainer approval. Do not add `mcp.json`, postinstall scripts, or global hook
|
package/README.md
CHANGED
|
@@ -8,10 +8,11 @@ planning, routing, bounded execution, and independent review of repository work.
|
|
|
8
8
|
It ships portable skills, references, templates, and optional client hooks.
|
|
9
9
|
|
|
10
10
|
An agent cannot certify its own work. The stateful Router owns the planning
|
|
11
|
-
conversation
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
verified deterministically without another review. Owner
|
|
11
|
+
conversation, visible Journey state, and Expedition sequencing. Crewmate and
|
|
12
|
+
Explorer may continue in-wave when the envelope is unchanged. Independent
|
|
13
|
+
assurance runs once at the end on the final integrated candidate. One repair
|
|
14
|
+
may follow; it is verified deterministically without another review. Owner
|
|
15
|
+
Authority remains human-only.
|
|
15
16
|
|
|
16
17
|
Bearing Lite was created by William Rumph.
|
|
17
18
|
|
|
@@ -28,9 +29,13 @@ Then give your agent a real task:
|
|
|
28
29
|
> Use Bearing Lite to add rate limiting to this API without changing its public
|
|
29
30
|
> responses. Require one independent review at the end.
|
|
30
31
|
|
|
31
|
-
Bearing Lite
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
Bearing Lite fills missing planning stages, then Map the Route creates the
|
|
33
|
+
complete five-artifact package (`<journey-topic>-technical-plan.md`,
|
|
34
|
+
`design.md`, `seit.json`, `implementation.json`, and two-state `review.html`)
|
|
35
|
+
with proposed route, lineup, role states, reasoning, and at-end cadence. One
|
|
36
|
+
integrated owner review approves or changes that package before bounded
|
|
37
|
+
sessions dispatch, with visible Markdown task state over those JSON
|
|
38
|
+
authorities.
|
|
34
39
|
|
|
35
40
|
If Bearing Lite helps keep a long agent task scoped and reviewable,
|
|
36
41
|
[star the repository](https://github.com/alphazede/bearing-lite). It helps other
|
|
@@ -66,13 +71,25 @@ agy plugin install /path/to/bearing-lite/.agy
|
|
|
66
71
|
|
|
67
72
|
# Pi — skills package, no command hooks
|
|
68
73
|
pi install npm:@alphazede/bearing-lite
|
|
74
|
+
|
|
75
|
+
# DeepCode — discover the packaged skills through ~/.deepcode/skills or ~/.agents/skills;
|
|
76
|
+
# DeepCode has no plugin-install or command-hook surface
|
|
69
77
|
```
|
|
70
78
|
|
|
71
79
|
Claude Code, Codex, Grok Build, Cursor, and Kimi Code are **partial** hook
|
|
72
80
|
clients: session start runs the activation advisory and stop runs the closeout
|
|
73
|
-
advisory. AGY and
|
|
74
|
-
checks stay in the skills on every host. Node.js must be on
|
|
75
|
-
hook adapters.
|
|
81
|
+
advisory. AGY, Pi, and DeepCode are **skills-only**. Transition-order and
|
|
82
|
+
protected-action checks stay in the skills on every host. Node.js must be on
|
|
83
|
+
`PATH` for the hook adapters.
|
|
84
|
+
|
|
85
|
+
Planning-review constraints live only in
|
|
86
|
+
[`skills/bearing-lite/references/review-policy.md`](skills/bearing-lite/references/review-policy.md);
|
|
87
|
+
the shared evaluator is `hooks/planning-review.cjs`. Journey-specific abstract
|
|
88
|
+
slot IDs and owner-selected primary/ordered fallback route references belong
|
|
89
|
+
only in the approved `lineup_snapshot`. Existing host mappings cannot safely
|
|
90
|
+
derive the nested record from session events, so they preserve partial or
|
|
91
|
+
skills-only coverage and apply this gate procedurally. Planning review is a
|
|
92
|
+
pre-dispatch plan gate; implementation `max_assurance_rounds` remains separate.
|
|
76
93
|
|
|
77
94
|
**Skills-only copy** of `skills/` into a host skills directory does not
|
|
78
95
|
register hooks. That path remains first-class. See
|
|
@@ -80,12 +97,24 @@ register hooks. That path remains first-class. See
|
|
|
80
97
|
|
|
81
98
|
## What it does
|
|
82
99
|
|
|
83
|
-
1. **
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
1. **Fill only missing planning stages:** Repository Fit → Set Bearings → Gather
|
|
101
|
+
Supplies.
|
|
102
|
+
2. **Invoke Map the Route** after material intent is settled. It creates
|
|
103
|
+
technical-plan, `design.md`, `seit.json`, `implementation.json`, and
|
|
104
|
+
two-state `review.html` together.
|
|
105
|
+
3. **Review once:** approve or change the proposed route, user-owned
|
|
106
|
+
primary/fallback lineup, role states, reasoning, at-end cadence, and plan.
|
|
107
|
+
4. **Dispatch bounded sessions** with compact receipts. Crewmate and Explorer may
|
|
108
|
+
continue in-wave; assurance always starts fresh at the end.
|
|
109
|
+
5. **Record state visibly.** The project's human-readable Markdown plan is the
|
|
110
|
+
only task-state record: task blocks, task states, and their transitions. The
|
|
111
|
+
Journey, execution, V&V, and authority records are machine-readable JSON
|
|
112
|
+
validated against `schemas/`: `journey.json` (Journey identity, checkout
|
|
113
|
+
lease, decisions, planning receipts, lineup selection), `seit.json` (V&V
|
|
114
|
+
proof rows), `implementation.json` (nested execution authority, waves,
|
|
115
|
+
slices, dispatch), and `authority.json` (authority envelope). `review.html`
|
|
116
|
+
and diagrams present state; like diagrams, they never authorize a
|
|
117
|
+
transition, create state, or grant authority.
|
|
89
118
|
|
|
90
119
|
Bearing Lite never selects models, providers, credentials, or launchers. The
|
|
91
120
|
owner provides each role's primary/fallback agent or harness, model, and
|
|
@@ -97,11 +126,11 @@ the applicable Journey snapshot before implementation.
|
|
|
97
126
|
| Route | When | Cost |
|
|
98
127
|
|---|---|---|
|
|
99
128
|
| **Explorer Journey** | One bounded packet or one wave | Direct Crewmate or one Explorer |
|
|
100
|
-
| **Expedition** | Multi-phase or concurrent independent lanes |
|
|
129
|
+
| **Expedition** | Multi-phase or concurrent independent lanes | Router sequences; Explorer owns waves |
|
|
101
130
|
|
|
102
131
|
An **Explorer Journey** uses a direct Crewmate for one ready packet or one
|
|
103
132
|
Explorer over a compact/sequential wave.
|
|
104
|
-
**Expedition**
|
|
133
|
+
**Expedition** lets the Router sequence waves so independent lanes
|
|
105
134
|
stay small and sharp instead of degrading in one long context. Either shape can
|
|
106
135
|
use substantial tokens; the product does not impose a default budget ceiling.
|
|
107
136
|
|
|
@@ -112,28 +141,41 @@ The text below remains authoritative for clients that do not render Mermaid.
|
|
|
112
141
|
|
|
113
142
|
**Authoritative text (vision optional):** Owner Authority remains human-only. The
|
|
114
143
|
Bearing Lite Router is the stateful planning controller, not a work role. It
|
|
115
|
-
invokes only missing planning stages
|
|
116
|
-
|
|
144
|
+
invokes only missing planning stages, has Map the Route generate all five
|
|
145
|
+
artifacts with proposed lineup and `review_cadence: at-end`, then presents one
|
|
146
|
+
integrated owner review before dispatching an Explorer Journey or Expedition. Explorer
|
|
117
147
|
coordinates proven-independent in-wave lanes without a nested coordinator.
|
|
118
|
-
|
|
119
|
-
|
|
148
|
+
Assurance Test Engineer, Park Ranger, and Surveyor appear only when declared
|
|
149
|
+
and only at the end on the final integrated candidate. Diagrams
|
|
120
150
|
explain orientation; they never authorize a transition.
|
|
121
151
|
|
|
122
152
|
## Roles and authority
|
|
123
153
|
|
|
124
154
|
| Role | What it is | Executes | Notes |
|
|
125
155
|
|---|---|---|---|
|
|
126
|
-
| **Router** | Stateful planning controller | no | User-facing; planning-state writer |
|
|
127
|
-
| **Navigator** |
|
|
156
|
+
| **Router** | Stateful planning controller | no | User-facing; planning-state writer; Expedition sequencing |
|
|
157
|
+
| **Navigator** | Compatibility diagnostic | no | Not a normal role; existing plans reroute to Router |
|
|
128
158
|
| **Explorer** | One-wave controller | no | Dispatches Crewmates; owns proven-independent lanes |
|
|
129
|
-
| **Crewmate** | Bounded implementer | yes |
|
|
130
|
-
| **
|
|
131
|
-
| **
|
|
132
|
-
| **
|
|
159
|
+
| **Crewmate** | Bounded implementer | yes | Split test-writing versus product; neither self-certifies |
|
|
160
|
+
| **Scribe** | Event side lane | no | Transcribes; cannot activate authority |
|
|
161
|
+
| **Plan Integrator** | Artifact reconciliation | no | Generates `implementation.json` and `review.html` |
|
|
162
|
+
| **Systems Modeler** | Engineering views | no | After requirements; before design finalization |
|
|
163
|
+
| **Integration Engineer** | Progressive assembly | no | Dual planning and execution sessions |
|
|
164
|
+
| **Test Engineer** | Planning and Assurance V&V | no | Retires Validator; Validator is not an active role |
|
|
165
|
+
| **Park Ranger** | Defect review | no | Consumes Assurance Test Engineer; no routine TE |
|
|
166
|
+
| **Surveyor** | User-facing acceptance | no | Read-only RE, SysML Modeling, and TE |
|
|
133
167
|
| **Owner Authority** | Human decision | n/a | Never an agent role |
|
|
134
168
|
|
|
135
|
-
**Independent review:** a candidate author never provides their own
|
|
136
|
-
Park Ranger, or Surveyor verdict.
|
|
169
|
+
**Independent review:** a candidate author never provides their own Assurance
|
|
170
|
+
Test Engineer, Park Ranger, or Surveyor verdict.
|
|
171
|
+
|
|
172
|
+
Public Bearing Lite remains usable without AlphaZede-specific skills
|
|
173
|
+
(`requirements-engineering`, `sysml-modeling`, `test-engineering`,
|
|
174
|
+
`integration-engineering`). Specialized capabilities activate when selected
|
|
175
|
+
or required. Unavailability of a selected-or-required capability is a typed capability gap,
|
|
176
|
+
not success and not invented behavior. Selected-only missing and required-only
|
|
177
|
+
missing are each typed gaps. Only unselected and unrequired absence remains
|
|
178
|
+
inactive / not a global failure.
|
|
137
179
|
|
|
138
180
|
Failure escalates to the nearest role whose scope can see it:
|
|
139
181
|
|
|
@@ -141,7 +183,7 @@ Failure escalates to the nearest role whose scope can see it:
|
|
|
141
183
|
|---|---|
|
|
142
184
|
| Within one slice or packet | Explorer or nearest parent |
|
|
143
185
|
| Across slices in a wave | Explorer |
|
|
144
|
-
| Across waves or phases |
|
|
186
|
+
| Across waves or phases | Router |
|
|
145
187
|
| Contract, security, or authority change | Owner Authority |
|
|
146
188
|
|
|
147
189
|
## Task state (explanatory)
|
|
@@ -162,7 +204,7 @@ no second review. Diagrams never create state or authorize transitions.
|
|
|
162
204
|
## Implementation process (explanatory)
|
|
163
205
|
|
|
164
206
|
Default packet completion is author self-check plus coordinator confirmation.
|
|
165
|
-
Declared independent assurance runs once at the
|
|
207
|
+
Declared independent assurance runs once at the end. A repairable
|
|
166
208
|
verdict permits one repair; deterministic coordinator verification then closes
|
|
167
209
|
the gate without another review. Once the Journey is `COMPLETE`, an already
|
|
168
210
|
authorized deployment proceeds with operational checks and rollback readiness,
|
|
@@ -186,6 +228,8 @@ flowchart LR
|
|
|
186
228
|
| `plugin.json` | Agent Plugins v1.0.0 manifest |
|
|
187
229
|
| `skills/` | Router, planning stages, and role skills |
|
|
188
230
|
| `hooks/` | Four portable class adapters plus the verified Claude Code / Codex mapping |
|
|
231
|
+
| `lineups.json` | Empty shipped catalog; no packaged providers, models, or defaults |
|
|
232
|
+
| `schemas/` | JSON Schema for `seit.json`, `implementation.json`, `authority.json`, and `journey.json` |
|
|
189
233
|
| `README.md` and governance docs | Public product and conduct surfaces |
|
|
190
234
|
|
|
191
235
|
There is no `mcp.json`, `bin` entrypoint, postinstall, or runtime dependency on
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-declared-phase-or-wave assurance budget (ROUTER-EMV-CADENCE-IMPLEMENTATION-001).
|
|
5
|
+
* Exact runtime mirror of `skills/bearing-lite/references/assurance-policy.md`.
|
|
6
|
+
* A pure evaluator behind the existing `transition` class, like planning-review.cjs:
|
|
7
|
+
* it declares no HOOK_CLASS and registers no host event.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
|
|
12
|
+
const POLICY = Object.freeze({
|
|
13
|
+
budget_scope: "per_declared_phase_or_wave",
|
|
14
|
+
review_rounds: 1,
|
|
15
|
+
aggregated_repairs_max: 1,
|
|
16
|
+
post_repair_gate: "deterministic_PASS",
|
|
17
|
+
automatic_phase_or_wave_end_review: "required",
|
|
18
|
+
automatic_per_slice_review: "prohibited",
|
|
19
|
+
post_repair_rereview: "prohibited",
|
|
20
|
+
automatic_rereview_of_same_unit: "prohibited",
|
|
21
|
+
budget_reset_on_candidate_change: false,
|
|
22
|
+
budget_reset_on_model_change: false,
|
|
23
|
+
budget_reset_on_harness_change: false,
|
|
24
|
+
budget_reset_on_role_change: false,
|
|
25
|
+
budget_reset_on_session_change: false,
|
|
26
|
+
budget_reset_on_resume: false,
|
|
27
|
+
budget_reset_on_alias_or_rename: false,
|
|
28
|
+
budget_reset_condition:
|
|
29
|
+
"next_distinct_declared_phase_or_wave_present_in_the_frozen_declaration",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/** The sentinel unit when the frozen declaration names neither waves nor phases. */
|
|
33
|
+
const DIRECT = "direct";
|
|
34
|
+
const PENDING_RECEIPTS = new Set(["WAITING_ON", "UNAVAILABLE"]);
|
|
35
|
+
|
|
36
|
+
const isCount = (value) => Number.isInteger(value) && value >= 0;
|
|
37
|
+
|
|
38
|
+
const isPlainObject = (value) =>
|
|
39
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
40
|
+
|
|
41
|
+
function readJson(file) {
|
|
42
|
+
if (typeof file !== "string" || !file) return null;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
45
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Declared unit ids from the frozen declaration, in declaration order. */
|
|
52
|
+
function declaredUnits(declaration) {
|
|
53
|
+
const waves = Array.isArray(declaration.waves) ? declaration.waves : [];
|
|
54
|
+
const waveIds = waves.map((wave) => wave && wave.id).filter((id) => typeof id === "string" && id);
|
|
55
|
+
if (waveIds.length) return waveIds;
|
|
56
|
+
const phases = Array.isArray(declaration.phases) ? declaration.phases : [];
|
|
57
|
+
const phaseIds = phases
|
|
58
|
+
.map((phase) => phase && phase.phaseId)
|
|
59
|
+
.filter((id) => typeof id === "string" && id);
|
|
60
|
+
if (phaseIds.length) return phaseIds;
|
|
61
|
+
return [DIRECT];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const verdict = (outcome, reason) => ({ outcome, reason });
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown} input assurance request
|
|
68
|
+
* @returns {{ outcome: string, reason: string }}
|
|
69
|
+
*/
|
|
70
|
+
function evaluateAssuranceBudget(input) {
|
|
71
|
+
try {
|
|
72
|
+
if (!isPlainObject(input)) {
|
|
73
|
+
return verdict("NEEDS_MORE_EVIDENCE", "assurance_request_missing");
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
POLICY.automatic_per_slice_review === "prohibited" &&
|
|
77
|
+
(input.request_scope === "slice" || input.unit_kind === "slice")
|
|
78
|
+
) {
|
|
79
|
+
return verdict("OWNER_AMENDMENT_REQUIRED", "automatic_per_slice_review_prohibited");
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
POLICY.automatic_rereview_of_same_unit === "prohibited" &&
|
|
83
|
+
(input.automatic_rereview_requested === true || input.review_after_repair === true)
|
|
84
|
+
) {
|
|
85
|
+
return verdict("OWNER_AMENDMENT_REQUIRED", "automatic_rereview_prohibited");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const declaration = readJson(input.declaration_path);
|
|
89
|
+
if (!declaration) {
|
|
90
|
+
return verdict("NEEDS_MORE_EVIDENCE", "frozen_declaration_unreadable");
|
|
91
|
+
}
|
|
92
|
+
const record = readJson(input.task_record_path);
|
|
93
|
+
if (!record) {
|
|
94
|
+
return verdict("NEEDS_MORE_EVIDENCE", "task_record_unreadable");
|
|
95
|
+
}
|
|
96
|
+
// The budget key is `journey + unit_kind + unit_id`; a record from another
|
|
97
|
+
// Journey never answers for this one.
|
|
98
|
+
if (
|
|
99
|
+
typeof record.journey === "string" &&
|
|
100
|
+
typeof input.journey === "string" &&
|
|
101
|
+
record.journey !== input.journey
|
|
102
|
+
) {
|
|
103
|
+
return verdict("NEEDS_MORE_EVIDENCE", "task_record_journey_mismatch");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const declared = declaredUnits(declaration);
|
|
107
|
+
const requested =
|
|
108
|
+
typeof input.assurance_unit === "string" && input.assurance_unit
|
|
109
|
+
? input.assurance_unit
|
|
110
|
+
: declared.length === 1 && declared[0] === DIRECT
|
|
111
|
+
? DIRECT
|
|
112
|
+
: "";
|
|
113
|
+
if (!requested || !declared.includes(requested)) {
|
|
114
|
+
return verdict("OWNER_AMENDMENT_REQUIRED", "undeclared_review_unit");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const receipts = Array.isArray(input.receipts) ? input.receipts : [];
|
|
118
|
+
if (receipts.some((receipt) => PENDING_RECEIPTS.has(receipt && receipt.verdict))) {
|
|
119
|
+
return verdict("NEEDS_MORE_EVIDENCE", "assurance_transport_pending");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const units = Array.isArray(record.units) ? record.units : [];
|
|
123
|
+
const spent = units.find((unit) => {
|
|
124
|
+
if (!isPlainObject(unit)) return false;
|
|
125
|
+
const unitId = unit.unit_id || unit.assurance_unit;
|
|
126
|
+
const id = typeof unitId === "string" && unitId ? unitId : DIRECT;
|
|
127
|
+
if (id !== requested) return false;
|
|
128
|
+
if (typeof unit.journey === "string" && unit.journey !== input.journey) return false;
|
|
129
|
+
if (
|
|
130
|
+
typeof unit.unit_kind === "string" &&
|
|
131
|
+
typeof input.unit_kind === "string" &&
|
|
132
|
+
unit.unit_kind !== input.unit_kind
|
|
133
|
+
) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
});
|
|
138
|
+
const counter = (field) => {
|
|
139
|
+
const value = spent && spent[field];
|
|
140
|
+
return value === undefined || value === null ? 0 : value;
|
|
141
|
+
};
|
|
142
|
+
const rounds = counter("assurance_rounds");
|
|
143
|
+
const repairs = counter("assurance_repairs");
|
|
144
|
+
// A corrupt counter is not a spent-zero budget.
|
|
145
|
+
if (!isCount(rounds) || !isCount(repairs)) {
|
|
146
|
+
return verdict("NEEDS_MORE_EVIDENCE", "invalid_assurance_counter");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (repairs > POLICY.aggregated_repairs_max) {
|
|
150
|
+
return verdict("HALT", "assurance_repair_limit");
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
repairs === POLICY.aggregated_repairs_max &&
|
|
154
|
+
(spent && spent.deterministic_gate) !== POLICY.post_repair_gate.split("_")[1]
|
|
155
|
+
) {
|
|
156
|
+
return verdict("HALT", "deterministic_post_repair_gate_required");
|
|
157
|
+
}
|
|
158
|
+
if (rounds >= POLICY.review_rounds) {
|
|
159
|
+
return verdict("HALT", "assurance_round_limit");
|
|
160
|
+
}
|
|
161
|
+
return verdict("PASS", "assurance_round_available");
|
|
162
|
+
} catch {
|
|
163
|
+
return verdict("NEEDS_MORE_EVIDENCE", "assurance_budget_unavailable");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { POLICY, evaluateAssuranceBudget };
|
package/hooks/closeout.cjs
CHANGED
|
@@ -10,25 +10,42 @@
|
|
|
10
10
|
const HOOK_CLASS = "closeout";
|
|
11
11
|
const OUTCOMES = Object.freeze(["ADVISE", "REROUTE", "BLOCK", "UNAVAILABLE"]);
|
|
12
12
|
const ENFORCEMENT = "procedural";
|
|
13
|
+
const { evaluatePlanningReview } = require("./planning-review.cjs");
|
|
13
14
|
|
|
14
15
|
const HANDOFF_FIELDS = Object.freeze([
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"authority",
|
|
21
|
-
"outcome",
|
|
22
|
-
"evidence",
|
|
16
|
+
"verdict",
|
|
17
|
+
"candidate_ref",
|
|
18
|
+
"changed_paths",
|
|
19
|
+
"tests",
|
|
20
|
+
"findings",
|
|
23
21
|
"blocker",
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/** Closed role-return tokens. Task-block `outcome` is intent and cannot satisfy this. */
|
|
25
|
+
const VERDICT_VALUES = new Set([
|
|
26
|
+
"ACCEPT",
|
|
27
|
+
"ACCEPT_WITH_FINDINGS",
|
|
28
|
+
"BLOCK",
|
|
29
|
+
"CANDIDATE_READY",
|
|
30
|
+
"FAIL",
|
|
31
|
+
"GAPS",
|
|
32
|
+
"NEEDS_MORE_EVIDENCE",
|
|
33
|
+
"OWNER_DECISION_REQUIRED",
|
|
34
|
+
"PARTIAL",
|
|
35
|
+
"PASS",
|
|
36
|
+
"READY",
|
|
37
|
+
"REPAIR_REQUIRED",
|
|
38
|
+
"REROUTED",
|
|
39
|
+
"WAITING_ON",
|
|
26
40
|
]);
|
|
27
41
|
|
|
28
42
|
const RECOVERY_UNAVAILABLE =
|
|
29
43
|
"Report UNAVAILABLE, complete the handoff checklist manually, and do not request protected completion until required fields and assurance are present";
|
|
30
44
|
const RECOVERY_HANDOFF =
|
|
31
|
-
"Complete the
|
|
45
|
+
"Complete the compact receipt (verdict, candidate_ref, changed_paths, tests, findings, blocker) in the project plan";
|
|
46
|
+
const RECOVERY_INVALID_VERDICT =
|
|
47
|
+
"Replace verdict with a closed role-return token: " +
|
|
48
|
+
[...VERDICT_VALUES].join(", ");
|
|
32
49
|
const RECOVERY_COMPLETE =
|
|
33
50
|
"Handoff is complete; parent coordinator may advance using the project plan only";
|
|
34
51
|
const RECOVERY_BLOCK_COMPLETION =
|
|
@@ -75,9 +92,10 @@ function normalizeAssurance(value) {
|
|
|
75
92
|
return [String(value)].filter((s) => s && s !== "none");
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
function
|
|
95
|
+
function inspectHandoffFields(input) {
|
|
79
96
|
const handoff = isPlainObject(input.handoff) ? input.handoff : input;
|
|
80
97
|
const missing = [];
|
|
98
|
+
const invalid = [];
|
|
81
99
|
for (const field of HANDOFF_FIELDS) {
|
|
82
100
|
const value = handoff[field];
|
|
83
101
|
if (value === undefined || value === null) {
|
|
@@ -86,10 +104,14 @@ function missingHandoffFields(input) {
|
|
|
86
104
|
}
|
|
87
105
|
if (typeof value === "string" && value.trim() === "") {
|
|
88
106
|
missing.push(field);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (field === "verdict" && (typeof value !== "string" || !VERDICT_VALUES.has(value.trim()))) {
|
|
110
|
+
invalid.push(field);
|
|
89
111
|
}
|
|
90
112
|
}
|
|
91
113
|
// blocker may be the string "none"; that is present and valid.
|
|
92
|
-
return missing;
|
|
114
|
+
return { missing, invalid };
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
/**
|
|
@@ -124,7 +146,11 @@ function evaluate(input) {
|
|
|
124
146
|
? "protected_completion"
|
|
125
147
|
: "advisory";
|
|
126
148
|
|
|
127
|
-
const
|
|
149
|
+
const planningVerdict = input.planning_review
|
|
150
|
+
? evaluatePlanningReview(input.planning_review)
|
|
151
|
+
: null;
|
|
152
|
+
|
|
153
|
+
const { missing, invalid } = inspectHandoffFields(input);
|
|
128
154
|
const required = normalizeAssurance(input.required_assurance);
|
|
129
155
|
const accepted = Array.isArray(input.assurance_accepted)
|
|
130
156
|
? input.assurance_accepted.map(String)
|
|
@@ -141,12 +167,16 @@ function evaluate(input) {
|
|
|
141
167
|
|
|
142
168
|
if (mode === "protected_completion") {
|
|
143
169
|
const blockers = [];
|
|
144
|
-
|
|
170
|
+
const handoffProblems = [...missing, ...invalid];
|
|
171
|
+
if (handoffProblems.length > 0) blockers.push("handoff:" + handoffProblems.join(","));
|
|
145
172
|
if (missingAssurance.length > 0) {
|
|
146
173
|
blockers.push("assurance:" + missingAssurance.join(","));
|
|
147
174
|
}
|
|
148
175
|
if (!candidateMatched) blockers.push("candidate_mismatch");
|
|
149
176
|
if (unresolvedBlocker) blockers.push("unresolved_blocker");
|
|
177
|
+
if (planningVerdict && planningVerdict.outcome !== "PASS") {
|
|
178
|
+
blockers.push("planning_review:" + planningVerdict.outcome);
|
|
179
|
+
}
|
|
150
180
|
|
|
151
181
|
if (blockers.length > 0) {
|
|
152
182
|
return result(
|
|
@@ -165,6 +195,16 @@ function evaluate(input) {
|
|
|
165
195
|
);
|
|
166
196
|
}
|
|
167
197
|
|
|
198
|
+
if (planningVerdict) {
|
|
199
|
+
return result(
|
|
200
|
+
planningVerdict.outcome === "PASS" ? "ADVISE" : "REROUTE",
|
|
201
|
+
"planning_review:" + planningVerdict.outcome + ":" + planningVerdict.reason,
|
|
202
|
+
planningVerdict.outcome === "PASS"
|
|
203
|
+
? "Planning-review record is complete; continue to the integrated owner gate"
|
|
204
|
+
: "Keep dispatch closed and complete, repair, or amend the planning-review record"
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
168
208
|
// Advisory closeout: never BLOCK.
|
|
169
209
|
if (missing.length > 0) {
|
|
170
210
|
return result(
|
|
@@ -174,6 +214,14 @@ function evaluate(input) {
|
|
|
174
214
|
);
|
|
175
215
|
}
|
|
176
216
|
|
|
217
|
+
if (invalid.length > 0) {
|
|
218
|
+
return result(
|
|
219
|
+
"ADVISE",
|
|
220
|
+
"handoff_invalid:" + invalid.join(","),
|
|
221
|
+
RECOVERY_INVALID_VERDICT
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
177
225
|
if (missingAssurance.length > 0) {
|
|
178
226
|
return result(
|
|
179
227
|
"ADVISE",
|
|
@@ -234,6 +282,7 @@ module.exports = {
|
|
|
234
282
|
OUTCOMES,
|
|
235
283
|
ENFORCEMENT,
|
|
236
284
|
HANDOFF_FIELDS,
|
|
285
|
+
VERDICT_VALUES,
|
|
237
286
|
evaluate,
|
|
238
287
|
};
|
|
239
288
|
|
|
@@ -52,7 +52,9 @@ const SKIP_DIRS = new Set([
|
|
|
52
52
|
const TASK_MARK = /(?:^|\n)###\s*task_id:\s*\S+/;
|
|
53
53
|
const TASK_LINE = /^###\s*task_id:\s*(\S+)\s*$/;
|
|
54
54
|
const FIELD_LINE =
|
|
55
|
-
/^-\s*(assigned_role|next_action|status|required_assurance|blocker|evidence|candidate_ref|scope|authority|outcome|depends_on|receiving_role|plan_ref|role|subject):\s*(.*)$/;
|
|
55
|
+
/^-\s*(assigned_role|next_action|status|required_assurance|blocker|evidence|candidate_ref|scope|authority|outcome|verdict|depends_on|receiving_role|plan_ref|role|subject|changed_paths|tests|findings):\s*(.*)$/;
|
|
56
|
+
// Matches both the plan-level Journey type and the lease-nested Journey
|
|
57
|
+
// identity; either non-placeholder value marks the Router as invoked.
|
|
56
58
|
const JOURNEY_LINE = /^\s*-\s*journey:\s*(.+)$/i;
|
|
57
59
|
const LEASE_MARK = /^\s*-\s*checkout_lease:/m;
|
|
58
60
|
|
|
@@ -325,17 +327,12 @@ function evaluateForHost(eventName, derived) {
|
|
|
325
327
|
if (classForEvent(eventName) === "closeout") {
|
|
326
328
|
const task = derived.active_task || {};
|
|
327
329
|
return closeout.evaluate({
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
authority: presentString(task.authority),
|
|
334
|
-
outcome: presentString(task.outcome),
|
|
335
|
-
evidence: presentString(task.evidence),
|
|
330
|
+
verdict: presentString(task.verdict),
|
|
331
|
+
candidate_ref: presentString(task.candidate_ref),
|
|
332
|
+
changed_paths: presentString(task.changed_paths),
|
|
333
|
+
tests: presentString(task.tests),
|
|
334
|
+
findings: presentString(task.findings),
|
|
336
335
|
blocker: presentString(task.blocker) || "none",
|
|
337
|
-
next_action: presentString(task.next_action),
|
|
338
|
-
receiving_role: presentString(task.receiving_role),
|
|
339
336
|
required_assurance: task.required_assurance,
|
|
340
337
|
});
|
|
341
338
|
}
|