@aiwg/cli 2026.10.11 → 2026.10.12
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/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +90 -17
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +6 -24
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -546
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +1 -0
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +10 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/importer.js +8 -251
- package/dist/src/sessions/index.js +4 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +83 -7
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +1 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { stableJson } from './worker-contract-json.js';
|
|
2
|
+
// Pure Mission supervision trace and lease mapping codecs.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { assertValidWorkerPayload, validateWorkerPayload } from './codecs.js';
|
|
5
|
+
import { WorkerContractError, HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES, HEALTH_KNOWN_REASON_CODES } from './types.js';
|
|
6
|
+
// S1 health-to-lease and Mission reconciliation mapping (B-OG-54 / #2916,
|
|
7
|
+
// 05 §8, ADR-OG-10/13). Pure functions over validated shapes: no scheduler,
|
|
8
|
+
// store, Mission runtime or managed import. S2 owners (B-OG-12/16) wire these
|
|
9
|
+
// verdicts into actual renew/fence/stop/settle/release runtime.
|
|
10
|
+
const HEALTH_TERMINAL_LIKE = new Set(['completed', 'failed', 'cancelled', 'crashed', 'failed-to-start']);
|
|
11
|
+
/**
|
|
12
|
+
* Classify supervision evidence into a distinct shared-trace kind. Quiet,
|
|
13
|
+
* stalled and uncertain map distinctly. Heartbeat/lease-only observation
|
|
14
|
+
* yields activity-only, never productive work. A crashed or failed-to-start
|
|
15
|
+
* worker without confirmed termination is terminal-unconfirmed: fail-stop is
|
|
16
|
+
* claimed but a live descendant may survive, so it is never stop proof
|
|
17
|
+
* (ADR-OG-10). Precondition: schema-valid workerHealthSummary.
|
|
18
|
+
*/
|
|
19
|
+
export function classifyHealthTrace(health) {
|
|
20
|
+
if (health.termination === 'confirmed' && HEALTH_TERMINAL_LIKE.has(health.state))
|
|
21
|
+
return 'terminal-proof';
|
|
22
|
+
if (HEALTH_TERMINAL_LIKE.has(health.state))
|
|
23
|
+
return 'terminal-unconfirmed';
|
|
24
|
+
// Confirmed termination with a non-terminal state is contradictory owner
|
|
25
|
+
// evidence (finish() never produces it): downgrade, never productive.
|
|
26
|
+
if (health.termination === 'confirmed')
|
|
27
|
+
return 'uncertain';
|
|
28
|
+
if (health.termination === 'uncertain')
|
|
29
|
+
return 'uncertain';
|
|
30
|
+
switch (health.state) {
|
|
31
|
+
case 'launching':
|
|
32
|
+
case 'started':
|
|
33
|
+
return 'setup';
|
|
34
|
+
case 'active':
|
|
35
|
+
return health.lastProgressAt === null ? 'activity-only' : 'productive';
|
|
36
|
+
case 'quiet':
|
|
37
|
+
return 'quiet';
|
|
38
|
+
case 'stalled':
|
|
39
|
+
case 'hung':
|
|
40
|
+
return 'stalled';
|
|
41
|
+
default:
|
|
42
|
+
return 'uncertain';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const TRACE_TRUNCATION_MARKER = '…[truncated]';
|
|
46
|
+
/** Strip control/format characters and cap at maxUtf8Bytes (marker included). */
|
|
47
|
+
export function boundTraceText(text, maxUtf8Bytes) {
|
|
48
|
+
const clean = String(text).replace(/[\u0000-\u001F\u007F-\u009F\u2028\u2029]/g, '');
|
|
49
|
+
if (maxUtf8Bytes <= 0)
|
|
50
|
+
return '';
|
|
51
|
+
if (Buffer.byteLength(clean, 'utf8') <= maxUtf8Bytes)
|
|
52
|
+
return clean;
|
|
53
|
+
const markerBytes = Buffer.byteLength(TRACE_TRUNCATION_MARKER, 'utf8');
|
|
54
|
+
if (maxUtf8Bytes < markerBytes) {
|
|
55
|
+
// The budget cannot hold even the marker: return the longest
|
|
56
|
+
// character-boundary prefix of the marker that fits, so the output
|
|
57
|
+
// never exceeds the budget.
|
|
58
|
+
let bytes = 0;
|
|
59
|
+
let end = 0;
|
|
60
|
+
for (const char of TRACE_TRUNCATION_MARKER) {
|
|
61
|
+
const size = Buffer.byteLength(char, 'utf8');
|
|
62
|
+
if (bytes + size > maxUtf8Bytes)
|
|
63
|
+
break;
|
|
64
|
+
bytes += size;
|
|
65
|
+
end += char.length;
|
|
66
|
+
}
|
|
67
|
+
return TRACE_TRUNCATION_MARKER.slice(0, end);
|
|
68
|
+
}
|
|
69
|
+
let bytes = 0;
|
|
70
|
+
let end = 0;
|
|
71
|
+
for (const char of clean) {
|
|
72
|
+
const size = Buffer.byteLength(char, 'utf8');
|
|
73
|
+
if (bytes + size > maxUtf8Bytes - markerBytes)
|
|
74
|
+
break;
|
|
75
|
+
bytes += size;
|
|
76
|
+
end += char.length;
|
|
77
|
+
}
|
|
78
|
+
return clean.slice(0, end) + TRACE_TRUNCATION_MARKER;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Allowlisted reason label for the shared trace. Unlisted codes (including
|
|
82
|
+
* any free text carrying secret material) project as UNCLASSIFIED#digest:
|
|
83
|
+
* stable and bounded, never verbatim.
|
|
84
|
+
*/
|
|
85
|
+
export function healthReasonLabel(reasonCode) {
|
|
86
|
+
if (HEALTH_KNOWN_REASON_CODES.includes(reasonCode))
|
|
87
|
+
return reasonCode;
|
|
88
|
+
const digest = createHash('sha256').update(reasonCode, 'utf8').digest('hex').slice(0, 12);
|
|
89
|
+
return `UNCLASSIFIED#${digest}`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Digest-only lifecycle reference for the effect ledger. Only enum, counter,
|
|
93
|
+
* digest and timestamp fields enter the digest; no payload content crosses.
|
|
94
|
+
*/
|
|
95
|
+
export function healthLifecycleDigest(health) {
|
|
96
|
+
assertValidWorkerPayload('workerHealthSummary', health);
|
|
97
|
+
const lifecycle = {
|
|
98
|
+
version: health.version,
|
|
99
|
+
state: health.state,
|
|
100
|
+
coverage: health.coverage,
|
|
101
|
+
handshake: health.handshake,
|
|
102
|
+
termination: health.termination,
|
|
103
|
+
confidence: health.confidence,
|
|
104
|
+
observerGeneration: health.observerGeneration,
|
|
105
|
+
transitionCount: health.transitionCount,
|
|
106
|
+
restartCount: health.restartCount,
|
|
107
|
+
lastObservedAt: health.lastObservedAt,
|
|
108
|
+
lastActivityAt: health.lastActivityAt,
|
|
109
|
+
lastProgressAt: health.lastProgressAt,
|
|
110
|
+
};
|
|
111
|
+
const digest = `sha256:${createHash('sha256').update(stableJson(lifecycle), 'utf8').digest('hex')}`;
|
|
112
|
+
return { ref: `artifact:${digest}`, digest };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Closed observed-state vocabulary for the previous-state channel. Free
|
|
116
|
+
* text (including leaked bearer material) never interpolates into the
|
|
117
|
+
* shared trace.
|
|
118
|
+
*/
|
|
119
|
+
const HEALTH_OBSERVED_STATES = new Set([
|
|
120
|
+
'launching', 'started', 'active', 'quiet', 'stalled', 'hung',
|
|
121
|
+
'crashed', 'failed-to-start', 'completed', 'failed', 'cancelled',
|
|
122
|
+
]);
|
|
123
|
+
/**
|
|
124
|
+
* Parse an ISO timestamp into epoch milliseconds for chronological
|
|
125
|
+
* comparison. Comparison must never fall back to string ordering: distinct
|
|
126
|
+
* representations of the same instant (e.g. '.000Z' vs 'Z') must compare
|
|
127
|
+
* equal, not greater/lesser. Unparseable values reject as having no
|
|
128
|
+
* timestamp evidence (null), never throw.
|
|
129
|
+
*/
|
|
130
|
+
function parseProgressTimestampMs(value) {
|
|
131
|
+
if (value === null)
|
|
132
|
+
return null;
|
|
133
|
+
const ms = Date.parse(value);
|
|
134
|
+
return Number.isFinite(ms) ? ms : null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Fresh-progress gate: credit requires genuinely new progress evidence.
|
|
138
|
+
* The health shape carries no progress sequence/key, so freshness is a
|
|
139
|
+
* NONDECREASING credit watermark: `previousWatermark` is not merely "the
|
|
140
|
+
* last observation" but the highest-ever CREDITED lastProgressAt, compared
|
|
141
|
+
* by parsed epoch milliseconds (never raw strings). Out-of-order or
|
|
142
|
+
* replayed snapshots (older than, or equal to, the watermark) earn no
|
|
143
|
+
* credit and never move the watermark backward; heartbeats (which advance
|
|
144
|
+
* activity/observed timestamps while the watermark is frozen) never
|
|
145
|
+
* re-credit historical progress. Unparseable timestamps earn no credit.
|
|
146
|
+
* Callers thread the watermark forward with `advanceProgressWatermark`.
|
|
147
|
+
*/
|
|
148
|
+
function isFreshProgressEvidence(health, previousWatermark) {
|
|
149
|
+
const currentMs = parseProgressTimestampMs(health.lastProgressAt);
|
|
150
|
+
if (currentMs === null)
|
|
151
|
+
return false;
|
|
152
|
+
if (previousWatermark === null || previousWatermark.lastProgressAt === null)
|
|
153
|
+
return true;
|
|
154
|
+
const watermarkMs = parseProgressTimestampMs(previousWatermark.lastProgressAt);
|
|
155
|
+
if (watermarkMs === null)
|
|
156
|
+
return false;
|
|
157
|
+
return currentMs > watermarkMs;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Pure watermark reducer (no store): callers thread the returned value into
|
|
161
|
+
* the next observation's `previousHealth`/watermark argument instead of
|
|
162
|
+
* re-passing the raw last observation. The watermark advances ONLY when the
|
|
163
|
+
* current observation was actually credited; a non-credited observation
|
|
164
|
+
* (replay, heartbeat, or an out-of-order/older snapshot) leaves the
|
|
165
|
+
* watermark exactly where it was. This is what makes real A -> B -> A -> B
|
|
166
|
+
* delivery sequences credit true,true,false,false instead of re-crediting
|
|
167
|
+
* B the second time it arrives: a naive caller that re-threads "the last
|
|
168
|
+
* observation" (rather than this reducer's output) regresses to that bug.
|
|
169
|
+
*/
|
|
170
|
+
export function advanceProgressWatermark(previousWatermark, credited, health) {
|
|
171
|
+
if (credited)
|
|
172
|
+
return { lastProgressAt: health.lastProgressAt };
|
|
173
|
+
return previousWatermark ?? { lastProgressAt: null };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Project one health observation into the shared trace. The activity summary
|
|
177
|
+
* is built only from enum/counter/generation fields plus the allowlisted
|
|
178
|
+
* reason label; completionCredit is always false because Mission/Flow
|
|
179
|
+
* completion gates stay authoritative. previousState must be a closed
|
|
180
|
+
* observed state (or null); anything else is rejected, never interpolated.
|
|
181
|
+
* progressCredit requires fresh progress evidence relative to
|
|
182
|
+
* previousHealth, which callers must maintain as the nondecreasing credit
|
|
183
|
+
* watermark (see advanceProgressWatermark) rather than the raw last
|
|
184
|
+
* observation (null for a first observation / no watermark yet).
|
|
185
|
+
*/
|
|
186
|
+
export function projectHealthTrace(health, previousState = null, previousHealth = null) {
|
|
187
|
+
assertValidWorkerPayload('workerHealthSummary', health);
|
|
188
|
+
if (previousState !== null && !HEALTH_OBSERVED_STATES.has(previousState)) {
|
|
189
|
+
throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid previous health state.');
|
|
190
|
+
}
|
|
191
|
+
const kind = classifyHealthTrace(health);
|
|
192
|
+
const summary = `health.${health.state}:${health.termination}`
|
|
193
|
+
+ ` gen=${health.observerGeneration ?? 'none'} transitions=${health.transitionCount}`
|
|
194
|
+
+ ` prev=${previousState ?? 'none'} reason=${healthReasonLabel(health.reasonCode)}`;
|
|
195
|
+
return {
|
|
196
|
+
kind,
|
|
197
|
+
progressCredit: kind === 'productive' && isFreshProgressEvidence(health, previousHealth),
|
|
198
|
+
completionCredit: false,
|
|
199
|
+
activitySummary: boundTraceText(summary, HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES),
|
|
200
|
+
effectRef: healthLifecycleDigest(health),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Derive stop proof from owner-observed supervision evidence (ADR-OG-10).
|
|
205
|
+
* Only a terminal state with owner-confirmed termination is stop proof;
|
|
206
|
+
* terminal-unconfirmed and uncertain observations stay uncertain, and the
|
|
207
|
+
* absence of evidence is none. There is no free-form proof parameter:
|
|
208
|
+
* callers pass the health summary (or null) and the proof is derived.
|
|
209
|
+
* A late final after cancel-disconnect therefore never releases while
|
|
210
|
+
* termination is uncertain (ADR-OG-13: late results stay unaccepted
|
|
211
|
+
* evidence).
|
|
212
|
+
*/
|
|
213
|
+
export function stopProofFromHealth(health) {
|
|
214
|
+
if (health === null)
|
|
215
|
+
return 'none';
|
|
216
|
+
assertValidWorkerPayload('workerHealthSummary', health);
|
|
217
|
+
if (health.termination === 'confirmed') {
|
|
218
|
+
return HEALTH_TERMINAL_LIKE.has(health.state) ? 'confirmed' : 'uncertain';
|
|
219
|
+
}
|
|
220
|
+
if (HEALTH_TERMINAL_LIKE.has(health.state) || health.termination === 'uncertain')
|
|
221
|
+
return 'uncertain';
|
|
222
|
+
return 'none';
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The generation the stop-proof evidence actually belongs to, bridging a
|
|
226
|
+
* missing observerGeneration to 0 (the bootstrap generation) for fencing —
|
|
227
|
+
* mirroring the same bridge decideLeaseMapping already applies to the
|
|
228
|
+
* caller-supplied leaseGeneration/observedGeneration fields. This is bound
|
|
229
|
+
* to the health summary's OWN observerGeneration field, which the caller
|
|
230
|
+
* cannot set independently of the evidence: a caller cannot claim generation
|
|
231
|
+
* 3 while attaching generation-2 evidence and have it treated as current.
|
|
232
|
+
*/
|
|
233
|
+
function stopProofGeneration(health) {
|
|
234
|
+
if (health === null)
|
|
235
|
+
return null;
|
|
236
|
+
return health.observerGeneration ?? 0;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Pure renewal/reconcile/release verdict over a mocked scheduler boundary.
|
|
240
|
+
* Fencing binds every action to the current generation: a stale observation
|
|
241
|
+
* denies renew, cancel, settle and accept alike. Expiry or cancel-disconnect
|
|
242
|
+
* without derived confirmed stop proof retains the uncertain reservation
|
|
243
|
+
* and releases nothing. Confirmed stop proof settles exactly once — and only
|
|
244
|
+
* when that proof's OWN generation (not merely the caller-asserted
|
|
245
|
+
* observedGeneration) matches the current leaseGeneration; a confirmed
|
|
246
|
+
* proof carried over from a stale generation never releases.
|
|
247
|
+
*/
|
|
248
|
+
export function decideLeaseMapping(input) {
|
|
249
|
+
let record;
|
|
250
|
+
try {
|
|
251
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
252
|
+
throw new Error('Expected object');
|
|
253
|
+
record = input;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid lease mapping input.');
|
|
257
|
+
}
|
|
258
|
+
const leaseStates = ['admitted', 'expired', 'cancelled-disconnected'];
|
|
259
|
+
if (typeof record.leaseState !== 'string' || !leaseStates.includes(record.leaseState)
|
|
260
|
+
|| !Number.isSafeInteger(record.leaseGeneration) || record.leaseGeneration < 0
|
|
261
|
+
|| !Number.isSafeInteger(record.observedGeneration) || record.observedGeneration < 0
|
|
262
|
+
|| typeof record.settled !== 'boolean'
|
|
263
|
+
|| !('health' in record)
|
|
264
|
+
|| (record.health !== null && !validateWorkerPayload('workerHealthSummary', record.health).valid)) {
|
|
265
|
+
throw new WorkerContractError('OG_CONTRACT_INVALID', 'Invalid lease mapping input.');
|
|
266
|
+
}
|
|
267
|
+
const mapping = input;
|
|
268
|
+
if (mapping.observedGeneration !== mapping.leaseGeneration)
|
|
269
|
+
return 'deny-stale';
|
|
270
|
+
if (mapping.settled)
|
|
271
|
+
return 'deny-already-settled';
|
|
272
|
+
const stopProof = stopProofFromHealth(mapping.health);
|
|
273
|
+
const proofIsCurrent = stopProofGeneration(mapping.health) === mapping.leaseGeneration;
|
|
274
|
+
if (stopProof === 'confirmed' && proofIsCurrent)
|
|
275
|
+
return 'release-once';
|
|
276
|
+
if (stopProof === 'uncertain' || (stopProof === 'confirmed' && !proofIsCurrent) || mapping.leaseState !== 'admitted') {
|
|
277
|
+
return 'retain-uncertain';
|
|
278
|
+
}
|
|
279
|
+
return 'renew';
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=health-trace-codecs.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Bounded activity-log transition summary budget (UTF-8 bytes, marker included). */
|
|
2
|
+
export const HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES = 1024;
|
|
3
|
+
/**
|
|
4
|
+
* Closed observer reason-code allowlist for activity summaries. Mirrors the
|
|
5
|
+
* `reasons` set in tools/ralph-external/lib/worker-health.mjs: B-OG-50 owns
|
|
6
|
+
* the observer set, B-OG-54 owns this mapping allowlist; extend them together.
|
|
7
|
+
* Unlisted codes project as UNCLASSIFIED#digest so free text (and any secret
|
|
8
|
+
* material it carries) never crosses into the shared trace.
|
|
9
|
+
*/
|
|
10
|
+
export const HEALTH_KNOWN_REASON_CODES = [
|
|
11
|
+
'OK',
|
|
12
|
+
'AWAITING_HANDSHAKE',
|
|
13
|
+
'PROCESS_ONLY',
|
|
14
|
+
'ACCEPTED',
|
|
15
|
+
'PROGRESS',
|
|
16
|
+
'QUIET',
|
|
17
|
+
'NO_PROGRESS',
|
|
18
|
+
'STARTUP_DEADLINE',
|
|
19
|
+
'HARD_DEADLINE',
|
|
20
|
+
'STEP_LIMIT',
|
|
21
|
+
'AUTH',
|
|
22
|
+
'RATE_LIMIT',
|
|
23
|
+
'NETWORK',
|
|
24
|
+
'MODEL',
|
|
25
|
+
'TRUST',
|
|
26
|
+
'STDIN_WAIT',
|
|
27
|
+
'SANDBOX',
|
|
28
|
+
'NOT_FOUND',
|
|
29
|
+
'RESOURCE',
|
|
30
|
+
'PROVIDER_ERROR',
|
|
31
|
+
'CRASH',
|
|
32
|
+
'EXIT_ERROR',
|
|
33
|
+
'FINAL_INVALID',
|
|
34
|
+
'CANCELLED',
|
|
35
|
+
'DISCONNECTED',
|
|
36
|
+
'OBSERVER_GAP',
|
|
37
|
+
'CAPTURE_WRITE',
|
|
38
|
+
'OUTPUT_LIMIT',
|
|
39
|
+
'MALFORMED_FRAME',
|
|
40
|
+
'SOURCE_GAP',
|
|
41
|
+
'IDENTITY_CHANGED',
|
|
42
|
+
];
|
|
43
|
+
//# sourceMappingURL=health-trace-types.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './worker-review-types.js';
|
|
1
2
|
export const MISSION_API_VERSION = 'mission.aiwg.io/v1';
|
|
2
3
|
// Worker dispatch payloads merged from 05 §5.1 into schemas/mission-v1.schema.json.
|
|
3
4
|
// The schema file is the executable contract; these types mirror its shapes.
|
|
@@ -59,48 +60,7 @@ export const WORKER_DEFINITION_PRODUCERS = {
|
|
|
59
60
|
workerHealthSummary: 'shared-component',
|
|
60
61
|
workerEffectiveHealth: 'shared-component',
|
|
61
62
|
};
|
|
62
|
-
|
|
63
|
-
export const HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES = 1024;
|
|
64
|
-
/**
|
|
65
|
-
* Closed observer reason-code allowlist for activity summaries. Mirrors the
|
|
66
|
-
* `reasons` set in tools/ralph-external/lib/worker-health.mjs: B-OG-50 owns
|
|
67
|
-
* the observer set, B-OG-54 owns this mapping allowlist; extend them together.
|
|
68
|
-
* Unlisted codes project as UNCLASSIFIED#digest so free text (and any secret
|
|
69
|
-
* material it carries) never crosses into the shared trace.
|
|
70
|
-
*/
|
|
71
|
-
export const HEALTH_KNOWN_REASON_CODES = [
|
|
72
|
-
'OK',
|
|
73
|
-
'AWAITING_HANDSHAKE',
|
|
74
|
-
'PROCESS_ONLY',
|
|
75
|
-
'ACCEPTED',
|
|
76
|
-
'PROGRESS',
|
|
77
|
-
'QUIET',
|
|
78
|
-
'NO_PROGRESS',
|
|
79
|
-
'STARTUP_DEADLINE',
|
|
80
|
-
'HARD_DEADLINE',
|
|
81
|
-
'STEP_LIMIT',
|
|
82
|
-
'AUTH',
|
|
83
|
-
'RATE_LIMIT',
|
|
84
|
-
'NETWORK',
|
|
85
|
-
'MODEL',
|
|
86
|
-
'TRUST',
|
|
87
|
-
'STDIN_WAIT',
|
|
88
|
-
'SANDBOX',
|
|
89
|
-
'NOT_FOUND',
|
|
90
|
-
'RESOURCE',
|
|
91
|
-
'PROVIDER_ERROR',
|
|
92
|
-
'CRASH',
|
|
93
|
-
'EXIT_ERROR',
|
|
94
|
-
'FINAL_INVALID',
|
|
95
|
-
'CANCELLED',
|
|
96
|
-
'DISCONNECTED',
|
|
97
|
-
'OBSERVER_GAP',
|
|
98
|
-
'CAPTURE_WRITE',
|
|
99
|
-
'OUTPUT_LIMIT',
|
|
100
|
-
'MALFORMED_FRAME',
|
|
101
|
-
'SOURCE_GAP',
|
|
102
|
-
'IDENTITY_CHANGED',
|
|
103
|
-
];
|
|
63
|
+
export * from './health-trace-types.js';
|
|
104
64
|
export class WorkerContractError extends Error {
|
|
105
65
|
code;
|
|
106
66
|
constructor(code, message) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Stable JSON representation shared by Mission artifact and health digests.
|
|
2
|
+
export function stableJson(value) {
|
|
3
|
+
if (Array.isArray(value))
|
|
4
|
+
return `[${value.map(stableJson).join(',')}]`;
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(',')}}`;
|
|
7
|
+
}
|
|
8
|
+
return JSON.stringify(value) ?? 'null';
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=worker-contract-json.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Acceptance checks over conductor briefs, trusted head/lineage and verifier records.
|
|
2
|
+
import { workerReviewLineageDiagnostics } from '../models/worker-review-lineage.js';
|
|
3
|
+
import { WorkerContractError, } from './types.js';
|
|
4
|
+
function fail(message) { throw new WorkerContractError('OG_CONTRACT_INVALID', message); }
|
|
5
|
+
/** Additional relational checks after JSON Schema shape validation. */
|
|
6
|
+
export function workerReviewEvidenceErrors(verification) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
const rounds = verification.reviewRounds;
|
|
9
|
+
if (rounds?.some((review, index) => review.round !== index + 1)) {
|
|
10
|
+
errors.push('Review rounds must be contiguous and start at one');
|
|
11
|
+
}
|
|
12
|
+
const ledger = verification.findingsLedger ?? [];
|
|
13
|
+
if (new Set(ledger.map(finding => finding.id)).size !== ledger.length)
|
|
14
|
+
errors.push('Finding IDs must be unique');
|
|
15
|
+
if (rounds && ledger.some(finding => finding.round > rounds.length))
|
|
16
|
+
errors.push('Finding round has no review record');
|
|
17
|
+
if (verification.roundsToClean !== undefined && (verification.status !== 'accepted'
|
|
18
|
+
|| verification.roundsToClean !== rounds?.length))
|
|
19
|
+
errors.push('roundsToClean must count all review rounds on acceptance');
|
|
20
|
+
if (verification.status === 'accepted') {
|
|
21
|
+
if (ledger.some(finding => finding.class === 'A' && finding.status === 'open'
|
|
22
|
+
&& ['blocker', 'major'].includes(finding.severity)))
|
|
23
|
+
errors.push('Open class A blocker or major findings prevent acceptance');
|
|
24
|
+
if (rounds?.length && (verification.headSha !== rounds.at(-1).headSha
|
|
25
|
+
|| verification.roundsToClean !== rounds.length || !verification.findingsLedger)) {
|
|
26
|
+
errors.push('Accepted review needs the final headSha, findingsLedger and roundsToClean');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const commands = verification.oracleResults?.map(result => result.command) ?? [];
|
|
30
|
+
if (new Set(commands).size !== commands.length)
|
|
31
|
+
errors.push('Oracle result commands must be unique');
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
/** Derive telemetry, then validate it through the normal verifier payload codec. */
|
|
35
|
+
export function prepareWorkerAcceptance(brief, execution, supplied, context) {
|
|
36
|
+
const verification = structuredClone(supplied);
|
|
37
|
+
if (verification.status !== 'accepted')
|
|
38
|
+
return verification;
|
|
39
|
+
if (brief.designReviewRequired && !brief.designReviewArtifact) {
|
|
40
|
+
fail('Acceptance requires a design-review artifact in the implementation brief');
|
|
41
|
+
}
|
|
42
|
+
const oracle = brief.verificationOracle;
|
|
43
|
+
if (oracle) {
|
|
44
|
+
const results = verification.oracleResults ?? [];
|
|
45
|
+
if (new Set(oracle.map(check => check.command)).size !== oracle.length
|
|
46
|
+
|| results.length !== oracle.length || oracle.some(check => {
|
|
47
|
+
const matches = results.filter(result => result.command === check.command);
|
|
48
|
+
return matches.length !== 1 || matches[0].exitCode !== check.expectedExitCode;
|
|
49
|
+
}))
|
|
50
|
+
fail('Acceptance requires exact verificationOracle command outcomes');
|
|
51
|
+
}
|
|
52
|
+
const required = brief.intent.verification.independentReview === 'required'
|
|
53
|
+
|| brief.intent.verification.differentFamily === 'require';
|
|
54
|
+
const rounds = verification.reviewRounds ?? [];
|
|
55
|
+
if (!required && !rounds.length)
|
|
56
|
+
return verification;
|
|
57
|
+
const final = rounds.at(-1);
|
|
58
|
+
if (!final)
|
|
59
|
+
fail('Acceptance requires a final cross-model review record');
|
|
60
|
+
if (!context.headSha || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(context.headSha)
|
|
61
|
+
|| final.headSha !== context.headSha || verification.headSha !== undefined && verification.headSha !== context.headSha) {
|
|
62
|
+
fail('Final review is missing the current PR head SHA or is bound to a stale head');
|
|
63
|
+
}
|
|
64
|
+
const writer = context.reviewLineage?.writer;
|
|
65
|
+
const observations = context.reviewLineage?.reviewers;
|
|
66
|
+
const reviewers = Array.isArray(observations)
|
|
67
|
+
? observations.filter(observation => observation?.recordId === final.reviewerObservationRef) : [];
|
|
68
|
+
if (reviewers.length !== 1)
|
|
69
|
+
fail('Final review is missing unambiguous reviewer lineage');
|
|
70
|
+
const diagnostics = workerReviewLineageDiagnostics({ reviewOf: writer, reviewer: reviewers[0], requireDifferentFamily: true });
|
|
71
|
+
if (diagnostics.length)
|
|
72
|
+
fail(diagnostics.map(diagnostic => `${diagnostic.pointer}: ${diagnostic.message}`).join('; '));
|
|
73
|
+
if (writer.cliId !== brief.selected.cli || writer.family !== execution.observedFamily) {
|
|
74
|
+
fail('Review source lineage differs from the observed writer');
|
|
75
|
+
}
|
|
76
|
+
if (verification.roundsToClean !== undefined && verification.roundsToClean !== rounds.length) {
|
|
77
|
+
fail('roundsToClean differs from the retained review rounds');
|
|
78
|
+
}
|
|
79
|
+
verification.headSha = context.headSha;
|
|
80
|
+
verification.roundsToClean = rounds.length;
|
|
81
|
+
return verification;
|
|
82
|
+
}
|
|
83
|
+
/** Successors keep earlier rounds and findings; resolving a finding retains its ID. */
|
|
84
|
+
export function assertWorkerReviewSuccessor(prior, next) {
|
|
85
|
+
for (const [index, round] of (prior.reviewRounds ?? []).entries()) {
|
|
86
|
+
const successor = next.reviewRounds?.[index];
|
|
87
|
+
if (!successor || successor.round !== round.round || successor.headSha !== round.headSha
|
|
88
|
+
|| successor.reviewerObservationRef !== round.reviewerObservationRef
|
|
89
|
+
|| successor.artifact.ref !== round.artifact.ref || successor.artifact.digest !== round.artifact.digest) {
|
|
90
|
+
fail('Worker successor drops or rewrites a review round');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const finding of prior.findingsLedger ?? []) {
|
|
94
|
+
const successor = next.findingsLedger?.find(candidate => candidate.id === finding.id);
|
|
95
|
+
if (!successor || successor.class !== finding.class || successor.severity !== finding.severity || successor.round !== finding.round) {
|
|
96
|
+
fail('Worker successor drops or reclassifies a retained finding');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=worker-review-gate.js.map
|