wicked-crew-api-types 0.10.0 → 0.18.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/index.d.ts +1193 -11
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -190,6 +190,47 @@ export interface AgentSession {
|
|
|
190
190
|
* pre-populate the steer textarea when a gate arrives.
|
|
191
191
|
*/
|
|
192
192
|
guidance?: string;
|
|
193
|
+
/**
|
|
194
|
+
* The run's delivery state (crew#393; api-types 0.18.0) — derived at DTO assembly on BOTH
|
|
195
|
+
* `GET /runs` and `GET /runs/:id`, so a run whose reviewable work is sitting uncommitted in
|
|
196
|
+
* its worktree can no longer look identical to one that delivered or had nothing to deliver:
|
|
197
|
+
*
|
|
198
|
+
* - `'delivered'` — a PR was opened for this run (by the deliver phase, or post-hoc via
|
|
199
|
+
* `POST /runs/:id/deliver`); `deliverUrl` carries the PR URL.
|
|
200
|
+
* - `'stranded'` — a COMPLETED repo-scoped run with no recorded PR whose worktree still
|
|
201
|
+
* exists on disk AND carries work: reviewable work nobody lifted. Derived honestly for
|
|
202
|
+
* OLD runs too — records written before this field existed strand exactly the same way.
|
|
203
|
+
* - `'vacuous'` — a COMPLETED repo-scoped run whose surviving worktree carries NO
|
|
204
|
+
* contribution at all (crew#311: nothing uncommitted, no run-branch commit — units all
|
|
205
|
+
* reached "done" while producing nothing). There is nothing to deliver; the recovery is a
|
|
206
|
+
* retry launch (`POST /runs {"retryOf":"<id>"}`). Never silently green: this is the loud
|
|
207
|
+
* spelling of the vacuous-completion class. Derived with the same two read-only git
|
|
208
|
+
* instruments the engine's own evidence floor uses; any probe failure keeps `'stranded'`
|
|
209
|
+
* (vacuity is only ever asserted on positive reads).
|
|
210
|
+
* - `'none'` — everything else: repo-less runs, non-terminal runs, failed/cancelled
|
|
211
|
+
* runs, and completed runs whose worktree is gone.
|
|
212
|
+
*
|
|
213
|
+
* Always present on runs served by a 0.18.0+ daemon; absent only from an older server.
|
|
214
|
+
*
|
|
215
|
+
* ⚠ WIRE RESHAPE (0.17.0 → 0.18.0, NOT additive): 0.11.0–0.17.0 spelled this field as
|
|
216
|
+
* `delivery?: { kind: 'pull_request'; url: string }`. The object form is GONE — the state
|
|
217
|
+
* moved into this string and the URL into `deliverUrl`. A client reading `delivery?.url`
|
|
218
|
+
* must move to `deliverUrl`.
|
|
219
|
+
*
|
|
220
|
+
* FAILURE of a deliver phase is not spelled here; it is `units[].status === 'rejected'`
|
|
221
|
+
* plus `denial_reason` (crew#318's message), both already on the list wire.
|
|
222
|
+
*/
|
|
223
|
+
delivery?: 'delivered' | 'stranded' | 'vacuous' | 'none';
|
|
224
|
+
/**
|
|
225
|
+
* The delivered PR's URL (crew#393; api-types 0.18.0) — present exactly when
|
|
226
|
+
* `delivery === 'delivered'`; absent otherwise (absence is the one spelling, never `null`).
|
|
227
|
+
*
|
|
228
|
+
* KNOWN LIMIT (inherited from 0.11.0): runs that delivered before a 0.11.0 daemon first saw
|
|
229
|
+
* their terminal frame have no durable `run.delivered` audit entry — they read as
|
|
230
|
+
* `'stranded'` while their worktree survives (post-hoc `POST /runs/:id/deliver` is
|
|
231
|
+
* idempotent against the existing branch) and `'none'` after it is cleaned up.
|
|
232
|
+
*/
|
|
233
|
+
deliverUrl?: string;
|
|
193
234
|
}
|
|
194
235
|
|
|
195
236
|
/** An ordered unit of work within a run (`WorkUnit`). */
|
|
@@ -218,6 +259,14 @@ export interface WorkUnit {
|
|
|
218
259
|
gate?: GateSpec | string;
|
|
219
260
|
/** True when a pinned deterministic validator is attached to this unit. */
|
|
220
261
|
has_validator_pin?: boolean;
|
|
262
|
+
/**
|
|
263
|
+
* True when this unit's backing phase declared `executes_code` (crew#311 / core#297 §2) —
|
|
264
|
+
* carried from the def at plan time. The engine's code-evidence floor holds such a unit to a
|
|
265
|
+
* worktree diff: a governed Creator unit with this flag cannot fold "done" over an untouched
|
|
266
|
+
* worktree. Additive and skip-if-false on the wire: absent on non-code units, on units planned
|
|
267
|
+
* before the field existed, and on an older engine.
|
|
268
|
+
*/
|
|
269
|
+
executes_code?: boolean;
|
|
221
270
|
}
|
|
222
271
|
|
|
223
272
|
/** A run plus its ordered units (`SessionView`) — the shape `GET /runs` returns. */
|
|
@@ -359,7 +408,10 @@ export interface GateInfo {
|
|
|
359
408
|
receivedAt: string;
|
|
360
409
|
}
|
|
361
410
|
|
|
362
|
-
/** Approve / reject payload for the steering gate (`POST /runs/:id/gate`).
|
|
411
|
+
/** Approve / reject payload for the steering gate (`POST /runs/:id/gate`). On a
|
|
412
|
+
* `steering-author` propose gate, an approve — amend note or not — also LANDS the proposal
|
|
413
|
+
* into the rules store and the 200 body carries a {@link SteeringLandingResult}; the amend
|
|
414
|
+
* steers the RUN's continuation, never the landed rule text (crew#388). */
|
|
363
415
|
export interface GateDecision {
|
|
364
416
|
approve: boolean;
|
|
365
417
|
amend?: string;
|
|
@@ -503,10 +555,39 @@ export interface CoreEvent {
|
|
|
503
555
|
latencyMs?: number;
|
|
504
556
|
/** workerStalled: silent seconds before the stall event fired. */
|
|
505
557
|
stalledSecs?: number;
|
|
558
|
+
/**
|
|
559
|
+
* `workerStalled` / `workerStallEscalated` (DAEMON-SYNTHETIC, crew#287/crew#341): how long the
|
|
560
|
+
* run had been silent on the daemon's CoreEvent relay when the frame fired, ms. Distinguishes
|
|
561
|
+
* the daemon's synthetic `workerStalled` frame (this field) from the engine's PTY-path
|
|
562
|
+
* `workerStalled` event (`stalledSecs` above) — same `type` tag, different producers.
|
|
563
|
+
*/
|
|
564
|
+
quietForMs?: number;
|
|
565
|
+
/** `workerStallEscalated` (crew#341): what the watchdog did — `'reassign'` | `'notify'`. */
|
|
566
|
+
action?: string;
|
|
567
|
+
/** `workerStallEscalated`: how the action ended — `'ok'` | `'failed'` | `'exhausted'`. */
|
|
568
|
+
outcome?: string;
|
|
569
|
+
/**
|
|
570
|
+
* `workerStallEscalated`: `true` when a human should look — the action failed, the automatic
|
|
571
|
+
* recovery budget is exhausted, or the configured action is `'notify'` (surface, don't touch).
|
|
572
|
+
* `false` means the platform recovered on its own; the narrator shows it, nobody is paged.
|
|
573
|
+
*/
|
|
574
|
+
needsYou?: boolean;
|
|
575
|
+
/** `workerStallEscalated` (action `'reassign'`): automatic reassigns consumed for this run, this one included. */
|
|
576
|
+
escalations?: number;
|
|
577
|
+
/** `workerStallEscalated` (outcome `'failed'`): bounded excerpt of what the recovery call threw. */
|
|
578
|
+
error?: string;
|
|
506
579
|
/** failureTriaged: the triage judge's decision. */
|
|
507
580
|
decision?: string;
|
|
508
581
|
/** failureTriaged: the judge's bounded reasoning. */
|
|
509
582
|
analysis?: string;
|
|
583
|
+
// campaign* frames (DES-CAMPAIGN-001 / TH-9) — camelCase per event_to_json; the exact
|
|
584
|
+
// per-variant shapes are the CampaignEvent union below.
|
|
585
|
+
/** campaign* frames: the campaign id. */
|
|
586
|
+
campaign?: string;
|
|
587
|
+
/** campaignNode* frames: the node id within the campaign. */
|
|
588
|
+
node?: string;
|
|
589
|
+
/** campaignNodeStarted / campaignNodeAwaitingHuman: the node's attempt-keyed Run id. */
|
|
590
|
+
runId?: string;
|
|
510
591
|
// assumptionRecorded (external-transform convention) — camelCase per event_to_json
|
|
511
592
|
kind?: string;
|
|
512
593
|
library?: string;
|
|
@@ -526,6 +607,63 @@ export interface CoreEvent {
|
|
|
526
607
|
* named live, and a bundle assembled after a run cannot describe it in different
|
|
527
608
|
* words than the operator watched it happen in. (FINDING-014)
|
|
528
609
|
*/
|
|
610
|
+
/**
|
|
611
|
+
* The stall watchdog's DETECTION frame (crew#287; api-types 0.18.0 — previously daemon-local).
|
|
612
|
+
* DAEMON-SYNTHETIC: broadcast straight to `/ws` when a run in `executing` has been silent on the
|
|
613
|
+
* daemon's CoreEvent relay past `SystemSettings.workerStallMinutes` — never re-relayed through
|
|
614
|
+
* the engine, one frame per quiet period, re-armed by any new event for the run. Detection only:
|
|
615
|
+
* the run is not touched. (The engine's PTY-path event shares the `workerStalled` tag but
|
|
616
|
+
* carries `stalledSecs`; this frame carries `quietForMs`.)
|
|
617
|
+
*
|
|
618
|
+
* `type` aliases on purpose, not `interface`s: only anonymous object types satisfy `CoreEvent`'s
|
|
619
|
+
* index signature, which is what lets these frames flow through CoreEvent-typed broadcast seams.
|
|
620
|
+
*/
|
|
621
|
+
export type WorkerStalledFrame = {
|
|
622
|
+
type: 'workerStalled';
|
|
623
|
+
/** The stalled run's id. */
|
|
624
|
+
session: string;
|
|
625
|
+
/** The current unit ord, when any observed event or the run header named one. */
|
|
626
|
+
ord?: number;
|
|
627
|
+
/** How long the run has been silent when the frame fired, ms. */
|
|
628
|
+
quietForMs: number;
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* The stall watchdog's ESCALATION frame (crew#341; api-types 0.18.0). DAEMON-SYNTHETIC, emitted
|
|
633
|
+
* only when escalation is armed (`SystemSettings.workerStallEscalateMinutes` > 0 — OFF by
|
|
634
|
+
* default) and a detected stall stays silent past that threshold. Reports what the watchdog DID
|
|
635
|
+
* and how it ended, one escalation per quiet period:
|
|
636
|
+
*
|
|
637
|
+
* - `action: 'reassign'` — the wedged cursor unit was recycled in place (engine `reassignUnit`:
|
|
638
|
+
* the stale turn is superseded, the session closed, the unit re-dispatched; queued operator
|
|
639
|
+
* injects survive into the fresh turn). `outcome: 'ok' | 'failed' | 'exhausted'`.
|
|
640
|
+
* - `action: 'notify'` — surface loudly without touching the run (the fail-loud rung).
|
|
641
|
+
*
|
|
642
|
+
* `needsYou: true` marks the frames a human should act on (failed / exhausted / notify).
|
|
643
|
+
*/
|
|
644
|
+
export type WorkerStallEscalatedFrame = {
|
|
645
|
+
type: 'workerStallEscalated';
|
|
646
|
+
/** The stalled run's id. */
|
|
647
|
+
session: string;
|
|
648
|
+
/** The cursor unit's ord, when known. */
|
|
649
|
+
ord?: number;
|
|
650
|
+
/** How long the run had been silent when the watchdog escalated, ms. */
|
|
651
|
+
quietForMs: number;
|
|
652
|
+
/** What the watchdog did. */
|
|
653
|
+
action: 'reassign' | 'notify';
|
|
654
|
+
/** How it ended: `ok` (acted / surfaced), `failed` (the recovery call threw), `exhausted`
|
|
655
|
+
* (the per-run automatic budget was already spent — nothing was attempted). */
|
|
656
|
+
outcome: 'ok' | 'failed' | 'exhausted';
|
|
657
|
+
/** True when a human should look; false = the platform recovered on its own. */
|
|
658
|
+
needsYou: boolean;
|
|
659
|
+
/** action `reassign`: the seat the unit was re-dispatched to, when known. */
|
|
660
|
+
cli?: string;
|
|
661
|
+
/** action `reassign`: automatic reassigns consumed for this run, this one included. */
|
|
662
|
+
escalations?: number;
|
|
663
|
+
/** outcome `failed`: bounded excerpt of what the recovery call threw. */
|
|
664
|
+
error?: string;
|
|
665
|
+
};
|
|
666
|
+
|
|
529
667
|
export interface RecordedEvent extends CoreEvent {
|
|
530
668
|
/** Capture time, epoch millis — when core emitted the frame, not when it was read. */
|
|
531
669
|
ts: number;
|
|
@@ -885,9 +1023,30 @@ export interface GovernancePolicy {
|
|
|
885
1023
|
retired?: boolean;
|
|
886
1024
|
}
|
|
887
1025
|
|
|
1026
|
+
/**
|
|
1027
|
+
* The steering-type vocabulary (STEERING program) — one sub-page per type in the studio's
|
|
1028
|
+
* Steering surface. Enum-as-string on the wire; the engine's serde default is `architecture`,
|
|
1029
|
+
* so a rule row written before the field existed reads back as an architecture rule.
|
|
1030
|
+
*/
|
|
1031
|
+
export type SteeringType =
|
|
1032
|
+
| 'architecture'
|
|
1033
|
+
| 'development'
|
|
1034
|
+
| 'security'
|
|
1035
|
+
| 'testing'
|
|
1036
|
+
| 'operations'
|
|
1037
|
+
| 'compliance'
|
|
1038
|
+
| 'design-ux';
|
|
1039
|
+
|
|
888
1040
|
/**
|
|
889
1041
|
* A prescriptive conformance rule (`wicked-governance::ConformanceRule`).
|
|
890
1042
|
* `rule_type`: `pattern` | `policy`. `severity`: `info` | `warn` | `error` | `critical`.
|
|
1043
|
+
*
|
|
1044
|
+
* STEERING (unified model): the wiki/rules model and the old policy model merged into ONE
|
|
1045
|
+
* steering-rule model. The steering fields below are all optional on the wire — additive for
|
|
1046
|
+
* older consumers, and ABSENT on rows served by a pre-steering engine (wicked-core-ts < 0.7.5),
|
|
1047
|
+
* where absence reads as the serde defaults (`steering_type` architecture, `weight` 1.0, no
|
|
1048
|
+
* enforcement half). A rule WITHOUT `effect` is recall-only, exactly as before the merge; a rule
|
|
1049
|
+
* WITH one participates in decide()/select() the way a `GovernancePolicy` used to.
|
|
891
1050
|
*/
|
|
892
1051
|
export interface ConformanceRule {
|
|
893
1052
|
id: string;
|
|
@@ -898,9 +1057,25 @@ export interface ConformanceRule {
|
|
|
898
1057
|
targets: { language?: string; layer?: string; framework?: string };
|
|
899
1058
|
symbol_ref?: string;
|
|
900
1059
|
compliance?: { framework: string; control_id: string };
|
|
1060
|
+
/** Where the rule came from. Doc-ingested rules keep `path@sha#id` in `ref`; UI/chat-authored
|
|
1061
|
+
* rules carry `source: "ui"` / `source: "chat"` — first-class provenance, not second-class. */
|
|
901
1062
|
provenance: { source: string; ref?: string; source_kinds: string[] };
|
|
902
1063
|
/** Withdrawn from recall. Same contract as {@link GovernancePolicy.retired}. */
|
|
903
1064
|
retired?: boolean;
|
|
1065
|
+
/** Which Steering sub-page this rule belongs to. Engine default: `architecture`. */
|
|
1066
|
+
steering_type?: SteeringType;
|
|
1067
|
+
/** Phases / tools this rule is selected for (inclusion — {@link GovernancePolicy.applies_to} semantics). */
|
|
1068
|
+
applies_to?: string[];
|
|
1069
|
+
/** Exclusion twin of `applies_to`: phases / tools this rule is NEVER selected for. */
|
|
1070
|
+
excludes?: string[];
|
|
1071
|
+
/** Ordering within a severity band + gate priority. Engine default: 1.0. */
|
|
1072
|
+
weight?: number;
|
|
1073
|
+
/** Enforcement half (from the retired policy model). Absent ⇒ the rule is recall-only. */
|
|
1074
|
+
effect?: 'deny' | 'allow_with_conditions' | 'allow';
|
|
1075
|
+
trigger?: { contains?: string };
|
|
1076
|
+
obligations?: string[];
|
|
1077
|
+
/** The frozen acceptance-criteria text (becomes a claim's `criteria` when the rule decides a gate). */
|
|
1078
|
+
criteria?: string;
|
|
904
1079
|
}
|
|
905
1080
|
|
|
906
1081
|
/** Facet query for `GET /governance/rules/preview`. All fields are optional. */
|
|
@@ -912,6 +1087,368 @@ export interface RulePreviewQuery {
|
|
|
912
1087
|
rule_type?: string;
|
|
913
1088
|
}
|
|
914
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* Facet query for `GET /governance/rules` (the wiki BROWSE surface). Unlike
|
|
1092
|
+
* {@link RulePreviewQuery} — whose recall semantics treat an absent rule facet as a wildcard
|
|
1093
|
+
* match, enforcement's question — browse filters are EXACT matches over the listed rows
|
|
1094
|
+
* ("show me the rules tagged `layer=api`"), and `status` keeps retired rules reachable so the
|
|
1095
|
+
* kill switch stays visible (`all` is the default).
|
|
1096
|
+
*/
|
|
1097
|
+
export interface RuleBrowseQuery {
|
|
1098
|
+
severity?: 'info' | 'warn' | 'error' | 'critical';
|
|
1099
|
+
layer?: string;
|
|
1100
|
+
rule_type?: 'pattern' | 'policy';
|
|
1101
|
+
status?: 'active' | 'retired' | 'all';
|
|
1102
|
+
/**
|
|
1103
|
+
* Steering-type facet (the Steering sub-page's filter). Answers 501 on a pre-steering engine
|
|
1104
|
+
* (wicked-core-ts < 0.7.5): rows there carry no `steering_type`, so an empty answer would
|
|
1105
|
+
* impersonate "no rules of that type".
|
|
1106
|
+
*/
|
|
1107
|
+
type?: SteeringType;
|
|
1108
|
+
/**
|
|
1109
|
+
* Boolean spelling of the retire filter: `true` ⇒ `status=all`, `false` ⇒ `status=active`.
|
|
1110
|
+
* Mutually exclusive with `status` — sending both is a 400 (two spellings of one filter can
|
|
1111
|
+
* contradict, and picking a winner silently would answer a question the caller didn't ask).
|
|
1112
|
+
*/
|
|
1113
|
+
include_retired?: 'true' | 'false';
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// ── Steering management (STEERING program) ──────────────────────────────────────
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* One entry of a `POST /governance/steering/import` batch: either a frontmattered markdown
|
|
1120
|
+
* document (`kind: 'doc'` — the same format `rules ingest --dir` consumes) or a ready rule
|
|
1121
|
+
* object (`kind: 'rule'`). Every entry runs through the engine's ingest normalize/validate
|
|
1122
|
+
* path, fail-closed PER ENTRY: one bad entry rejects alone, the rest still land.
|
|
1123
|
+
*/
|
|
1124
|
+
export type SteeringImportEntry =
|
|
1125
|
+
| { kind: 'doc'; name?: string; content: string }
|
|
1126
|
+
| { kind: 'rule'; rule: ConformanceRule };
|
|
1127
|
+
|
|
1128
|
+
/** The `POST /governance/steering/import` request body (JSON — this daemon speaks no multipart). */
|
|
1129
|
+
export interface SteeringImportBody {
|
|
1130
|
+
/** The page's inferred steering type, applied as the DEFAULT `steering_type` for entries that omit one. */
|
|
1131
|
+
type?: SteeringType;
|
|
1132
|
+
entries: SteeringImportEntry[];
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/** Per-entry outcome of a steering import (same order as the submitted batch). */
|
|
1136
|
+
export interface SteeringImportResult {
|
|
1137
|
+
/** Index into the submitted `entries` array. */
|
|
1138
|
+
index: number;
|
|
1139
|
+
/** The doc entry's `name`, when one was given. */
|
|
1140
|
+
name?: string;
|
|
1141
|
+
status: 'imported' | 'rejected';
|
|
1142
|
+
/** Rule ids the entry minted (a doc can mint several; a rejected entry mints none). */
|
|
1143
|
+
ids?: string[];
|
|
1144
|
+
/** Why the entry was rejected (present only on `rejected`). */
|
|
1145
|
+
error?: string;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** The `POST /governance/steering/import` 200 body — 200 even with rejections: per-entry results ARE the answer. */
|
|
1149
|
+
export interface SteeringImportResponse {
|
|
1150
|
+
results: SteeringImportResult[];
|
|
1151
|
+
imported: number;
|
|
1152
|
+
rejected: number;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* The `POST /governance/steering/author` request body — "add with chat". Launches a governed
|
|
1157
|
+
* authoring run (the `steering-author` workflow) that analyzes the named files/directories plus
|
|
1158
|
+
* the operator's intent and emits PROPOSED steering rules at a human gate (the TH-12
|
|
1159
|
+
* propose-as-gate pattern): the run PAUSES `awaiting_human` after the propose phase, and the
|
|
1160
|
+
* operator approves/amends/rejects through the standard `POST /runs/:id/gate`. On APPROVE the
|
|
1161
|
+
* daemon lands the proposal into the rules store with `provenance.source: "chat"` and the gate
|
|
1162
|
+
* response carries a {@link SteeringLandingResult} — the run itself writes nothing to any store.
|
|
1163
|
+
*/
|
|
1164
|
+
export interface SteeringAuthorBody {
|
|
1165
|
+
/** The operator's conversational intent — what steering to author, and why. */
|
|
1166
|
+
instructions: string;
|
|
1167
|
+
/** Default `steering_type` for the proposed rules (the page's inferred type). */
|
|
1168
|
+
type?: SteeringType;
|
|
1169
|
+
/** ABSOLUTE paths on the daemon host (files or directories) the run should analyze. */
|
|
1170
|
+
paths?: string[];
|
|
1171
|
+
/** Inline documents — written into the run's steering inbox on the daemon host and analyzed like `paths`. */
|
|
1172
|
+
documents?: { name: string; content: string }[];
|
|
1173
|
+
/** Registered repo to run within (the run analyzes that worktree's context too). */
|
|
1174
|
+
repoRef?: string;
|
|
1175
|
+
/** Stable run id; minted when omitted. */
|
|
1176
|
+
sessionId?: string;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** Where a landed steering proposal was read from, most-preferred first: the propose phase's
|
|
1180
|
+
* machine-readable artifact file, the propose unit's stored transcript, the cached gate prompt. */
|
|
1181
|
+
export type SteeringProposalSource = 'deliverable' | 'transcript' | 'gate-prompt';
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* The `landing` field on the `POST /runs/:id/gate` (and gated `POST /runs/:id/resume`) 200 body
|
|
1185
|
+
* — present ONLY when the approved gate was a `steering-author` propose gate (crew#388). It is
|
|
1186
|
+
* how the skin learns the approved chat proposal actually reached the rules store — or, loudly,
|
|
1187
|
+
* that it did not: on `failed` the run still advanced (the approve stands) but `error` carries
|
|
1188
|
+
* the operator-readable reason, mirrored on the audit trail as
|
|
1189
|
+
* `governance.steering.landing_failed`. Landed rules are audited per rule as
|
|
1190
|
+
* `governance.rule.upserted` with the chat provenance. Idempotent: a replayed approve reports
|
|
1191
|
+
* `alreadyLanded: true` and re-lands nothing. An approve WITH an amend note lands the proposal
|
|
1192
|
+
* UNCHANGED — the amend steers the RUN, not the rule text. A reject lands nothing.
|
|
1193
|
+
*/
|
|
1194
|
+
export interface SteeringLandingResult {
|
|
1195
|
+
outcome: 'landed' | 'failed';
|
|
1196
|
+
/** Rule ids actually upserted (all of them on `landed`; the partial set on a partial `failed`). */
|
|
1197
|
+
ruleIds: string[];
|
|
1198
|
+
/** Where the proposal was read from. Absent when no source yielded one. */
|
|
1199
|
+
source?: SteeringProposalSource;
|
|
1200
|
+
/** `true` when a replayed approve found the durable landing marker — nothing was re-landed. */
|
|
1201
|
+
alreadyLanded?: boolean;
|
|
1202
|
+
/** Present iff `outcome: "failed"` — the loud, operator-readable reason. */
|
|
1203
|
+
error?: string;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// ── Governance wiki management (scoreboard + meta) ─────────────────────────────
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Typing-coverage half of the wiki scoreboard — % of doctrine statements typed into
|
|
1210
|
+
* enforcement classes. Doc-side by construction (the class lives in doc frontmatter, never on
|
|
1211
|
+
* the rule node), so it is measurable only when the daemon was pointed at the same docs root
|
|
1212
|
+
* `rules ingest --dir` used; otherwise `available` is `false` and `reason` says why.
|
|
1213
|
+
*/
|
|
1214
|
+
export interface WikiTypingCoverage {
|
|
1215
|
+
available: boolean;
|
|
1216
|
+
/** Why typing could not be measured (present only when `available` is `false`). */
|
|
1217
|
+
reason?: string;
|
|
1218
|
+
docs_scanned: number;
|
|
1219
|
+
statements_total: number;
|
|
1220
|
+
statements_typed: number;
|
|
1221
|
+
/** `statements_typed / statements_total` in [0,100]; absent when the corpus mints no statements. */
|
|
1222
|
+
percent?: number;
|
|
1223
|
+
/** Typed statements per class (`policy` / `validator` / `guidance`). */
|
|
1224
|
+
by_class: Record<string, number>;
|
|
1225
|
+
/** Docs that mint statements but declare no class — the actionable backlog. */
|
|
1226
|
+
docs_untyped: string[];
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/** Connection-coverage half — do the active rules' `symbol_ref`s resolve, and are the links live? */
|
|
1230
|
+
export interface WikiConnectionCoverage {
|
|
1231
|
+
rules_with_ref: number;
|
|
1232
|
+
refs_resolving: number;
|
|
1233
|
+
refs_unresolvable: number;
|
|
1234
|
+
/** `refs_resolving / rules_with_ref` in [0,100]; absent when no rule carries a ref. */
|
|
1235
|
+
percent?: number;
|
|
1236
|
+
/** Active rules with at least one live `Governs` edge. */
|
|
1237
|
+
rules_linked: number;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/** One rule's enforcement evidence (only rules with any evidence appear). */
|
|
1241
|
+
export interface WikiRuleEvidenceRow {
|
|
1242
|
+
rule_id: string;
|
|
1243
|
+
/** Distinct deny claims citing this rule (`evidenced_by` edges in). */
|
|
1244
|
+
denial_claims: number;
|
|
1245
|
+
/** Accumulated `evidence_count` across the rule's `Governs` edges. */
|
|
1246
|
+
governs_evidence: number;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/** Enforcement evidence — gate denials citing wiki rules (retired rules included: a past denial
|
|
1250
|
+
* stays explicable after its rule retires, so its evidence stays countable). */
|
|
1251
|
+
export interface WikiEnforcementEvidence {
|
|
1252
|
+
denial_claims: number;
|
|
1253
|
+
rules_evidenced: number;
|
|
1254
|
+
evidenced_by_edges: number;
|
|
1255
|
+
governs_evidence_total: number;
|
|
1256
|
+
/** Per-rule breakdown, most-evidenced first — the "which rules actually fire" list. */
|
|
1257
|
+
per_rule: WikiRuleEvidenceRow[];
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/** Recall volume — documented UNAVAILABLE in-band (an honest "cannot measure" beats a
|
|
1261
|
+
* fabricated zero); `reason` says why. */
|
|
1262
|
+
export interface WikiRecallVolume {
|
|
1263
|
+
available: boolean;
|
|
1264
|
+
reason: string;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* The wiki population/connection scoreboard (`wicked-governance::Scoreboard`, AW-23/arch-R23) —
|
|
1269
|
+
* the report that tells a populated wiki from an ingested-once-and-decaying one.
|
|
1270
|
+
* Served by `GET /governance/wiki/scoreboard`; 501 when the installed engine addon predates the
|
|
1271
|
+
* `governanceScoreboard` binding (wicked-core-ts ≥ 0.7.4).
|
|
1272
|
+
*/
|
|
1273
|
+
export interface GovernanceScoreboard {
|
|
1274
|
+
rules_total: number;
|
|
1275
|
+
rules_active: number;
|
|
1276
|
+
rules_retired: number;
|
|
1277
|
+
typing: WikiTypingCoverage;
|
|
1278
|
+
connection: WikiConnectionCoverage;
|
|
1279
|
+
evidence: WikiEnforcementEvidence;
|
|
1280
|
+
recall_volume: WikiRecallVolume;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* `GET /governance/wiki/meta` — the wiki's honest empty-state signal, cheap enough for the UI
|
|
1285
|
+
* to call on mount. `ruleset_count` is `null` (not 0) when the installed engine build cannot
|
|
1286
|
+
* count `RuleSet` rows: "no rulesets" is a real answer about a seeded store, and "cannot count"
|
|
1287
|
+
* must never impersonate it.
|
|
1288
|
+
*/
|
|
1289
|
+
export interface GovernanceWikiMeta {
|
|
1290
|
+
/** Any doctrine on the store at all (rules listable, or RuleSet rows counted). */
|
|
1291
|
+
seeded: boolean;
|
|
1292
|
+
ruleset_count: number | null;
|
|
1293
|
+
/** Rules the engine lists (pre-0.7.4 addons list active rules only — the recall funnel). */
|
|
1294
|
+
rule_count: number;
|
|
1295
|
+
/** Whether `GET /governance/wiki/scoreboard` will answer 200 on this deployment. */
|
|
1296
|
+
scoreboard_available: boolean;
|
|
1297
|
+
/** The authoring guide / seed runbook the empty state points at. */
|
|
1298
|
+
doc: string;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// ── Testing surface (crew-testing) — governance evals + eval corpora ───────────
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* The behavior signals of one eval sample — the facts the governance decide path is asked to
|
|
1305
|
+
* judge (mirrors the engine's decision inputs: run phase, tool invoked, files touched, content).
|
|
1306
|
+
* All optional: a sample carries only the signals its behavior is about.
|
|
1307
|
+
*/
|
|
1308
|
+
export interface GovernanceEvalSignals {
|
|
1309
|
+
phase?: string;
|
|
1310
|
+
tool?: string;
|
|
1311
|
+
files?: string[];
|
|
1312
|
+
content?: string;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* One eval sample: a described behavior (`kind: 'bad'` ⇒ the steering corpus SHOULD deny it,
|
|
1317
|
+
* `'good'` ⇒ it should pass) tagged with the steering type it exercises. Field names are the
|
|
1318
|
+
* engine's serde spelling (snake_case) — the report echoes them back verbatim.
|
|
1319
|
+
*/
|
|
1320
|
+
export interface GovernanceEvalSample {
|
|
1321
|
+
id: string;
|
|
1322
|
+
description: string;
|
|
1323
|
+
kind: 'good' | 'bad';
|
|
1324
|
+
steering_type: string;
|
|
1325
|
+
signals: GovernanceEvalSignals;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* The `POST /testing/evals/run` request body. `type` narrows the run to one steering type;
|
|
1330
|
+
* `corpus` names an estate scope (`evals:<name>`, as minted by `POST /testing/corpora/import`);
|
|
1331
|
+
* omitted, the engine runs its built-in default corpus.
|
|
1332
|
+
*/
|
|
1333
|
+
export interface RunGovernanceEvalsBody {
|
|
1334
|
+
type?: SteeringType;
|
|
1335
|
+
corpus?: string;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/** A near-miss rule on a `gap` verdict — the rule that ALMOST fired, with its similarity score. */
|
|
1339
|
+
export interface GovernanceEvalNearestRule {
|
|
1340
|
+
rule_id: string;
|
|
1341
|
+
similarity: number;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* One sample's outcome. `expected` is derived from the sample's `kind` (`bad` ⇒ `deny`);
|
|
1346
|
+
* `fired` lists the rule ids that actually fired; `verdict` is the comparison: `caught` (a bad
|
|
1347
|
+
* behavior denied), `gap` (a bad behavior that sailed through), `false_positive` (a good
|
|
1348
|
+
* behavior denied). `nearest_rules` is present on gaps (an empty array is allowed) — the
|
|
1349
|
+
* remediation pointer for "which rule needs sharpening".
|
|
1350
|
+
*/
|
|
1351
|
+
export interface GovernanceEvalResult {
|
|
1352
|
+
sample: Pick<GovernanceEvalSample, 'id' | 'description' | 'kind' | 'steering_type'>;
|
|
1353
|
+
expected: 'deny' | 'allow';
|
|
1354
|
+
fired: string[];
|
|
1355
|
+
verdict: 'caught' | 'gap' | 'false_positive';
|
|
1356
|
+
nearest_rules?: GovernanceEvalNearestRule[];
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/** The report's roll-up counts (snake_case — the engine's serde output, passed through verbatim). */
|
|
1360
|
+
export interface GovernanceEvalSummary {
|
|
1361
|
+
total: number;
|
|
1362
|
+
caught: number;
|
|
1363
|
+
gaps: number;
|
|
1364
|
+
false_positives: number;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* The `POST /testing/evals/run` 200 body — the engine's serde report passed through VERBATIM
|
|
1369
|
+
* (snake_case field names, `degraded` spelled `null` when the run was full-fidelity;
|
|
1370
|
+
* `'facet-only'` when the embedding side was unavailable and only facet matching ran).
|
|
1371
|
+
*/
|
|
1372
|
+
export interface GovernanceEvalReport {
|
|
1373
|
+
results: GovernanceEvalResult[];
|
|
1374
|
+
summary: GovernanceEvalSummary;
|
|
1375
|
+
degraded: 'facet-only' | null;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/** The `POST /testing/corpora/import` request body — a named eval corpus for later runs. */
|
|
1379
|
+
export interface ImportEvalCorpusBody {
|
|
1380
|
+
name: string;
|
|
1381
|
+
samples: GovernanceEvalSample[];
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* The `POST /testing/corpora/import` 200 body. `scope` is the estate scope the samples landed
|
|
1386
|
+
* under (`evals:<name>` — the string `RunGovernanceEvalsBody.corpus` names); `embedded` reports
|
|
1387
|
+
* whether the samples were embedded for similarity matching (false ⇒ later runs degrade to
|
|
1388
|
+
* facet-only).
|
|
1389
|
+
*/
|
|
1390
|
+
export interface ImportEvalCorpusResponse {
|
|
1391
|
+
imported: number;
|
|
1392
|
+
scope: string;
|
|
1393
|
+
embedded: boolean;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* The `POST /testing/recon` request body (api-types 0.15.0) — the Testing page's campaign-recon
|
|
1398
|
+
* trigger. `problem` is the recon brief, passed to every launched run VERBATIM (the client owns
|
|
1399
|
+
* the framing). The two optional fields are the multiscope wire, shared with
|
|
1400
|
+
* {@link LaunchCampaignBody}:
|
|
1401
|
+
*
|
|
1402
|
+
* - `repoRefs` — explicit codebase attachments: registered repo refs (ids; a unique repo NAME
|
|
1403
|
+
* also resolves), deduped; a ref that does not resolve fails the whole request with a 400
|
|
1404
|
+
* naming it.
|
|
1405
|
+
* - `projectId` — crew resolves the project's `crew.repo` members server-side (404 unknown
|
|
1406
|
+
* project; 400 when the project has zero repo members and no `repoRefs` cover for it) AND
|
|
1407
|
+
* files every launched run into the project (the `POST /runs` §2.2 semantics: atomic
|
|
1408
|
+
* membership + project-graph binding).
|
|
1409
|
+
*
|
|
1410
|
+
* BOTH ⇒ the union (`repoRefs` order first). NEITHER ⇒ one unscoped recon run — the launch the
|
|
1411
|
+
* Testing page sent before this wire existed, unchanged.
|
|
1412
|
+
*
|
|
1413
|
+
* One engine run carries ONE repo, so a multi-repo recon FANS: one governed run per resolved
|
|
1414
|
+
* repo, all under one shared campaign label (`TestingReconResponse.campaign`) — and a real fan
|
|
1415
|
+
* (>= 2 repos) registers an ENGINE campaign under that same id, so `GET /campaigns` serves it
|
|
1416
|
+
* (api-types 0.17.0, crew#390).
|
|
1417
|
+
*
|
|
1418
|
+
* Every launched sibling pauses at its INTAKE GATE (`human_confirm: before:1` — the launch
|
|
1419
|
+
* banner's promise) by default; `ungated: true` is the EXPLICIT opt-out for an unattended fan
|
|
1420
|
+
* (api-types 0.17.0, crew#391). It is never the silent default, and it is audited.
|
|
1421
|
+
*/
|
|
1422
|
+
export interface TestingReconBody {
|
|
1423
|
+
problem: string;
|
|
1424
|
+
projectId?: string;
|
|
1425
|
+
repoRefs?: string[];
|
|
1426
|
+
/** EXPLICITLY launch the siblings unattended (no intake gate). Default `false`: each sibling
|
|
1427
|
+
* pauses at its intake gate before any unit runs. */
|
|
1428
|
+
ungated?: boolean;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* The `POST /testing/recon` 201 body. `runIds` is the source of truth (`length >= 1` always —
|
|
1433
|
+
* one run per resolved repo in the caller's order; exactly one for an unscoped recon); `runId`
|
|
1434
|
+
* is its first entry, kept as the single-run spelling existing launch readers expect;
|
|
1435
|
+
* `campaign` is the shared label every fanned run's `run.launched` audit entry carries — and,
|
|
1436
|
+
* when `campaignRegistered` is `true`, the id of the ENGINE campaign the fan was filed under
|
|
1437
|
+
* (`GET /campaigns/:id` serves it; the `runIds` are its nodes' attempt-0 run ids,
|
|
1438
|
+
* `{campaign}:{node}:a0`). `campaignRegistered: false` = a single/unscoped recon, or an engine
|
|
1439
|
+
* addon without the campaign bindings — the launch will NOT appear on `GET /campaigns`.
|
|
1440
|
+
*/
|
|
1441
|
+
export interface TestingReconResponse {
|
|
1442
|
+
runId: string;
|
|
1443
|
+
runIds: string[];
|
|
1444
|
+
campaign: string;
|
|
1445
|
+
/** Whether an engine campaign was registered for this launch (api-types 0.17.0). */
|
|
1446
|
+
campaignRegistered: boolean;
|
|
1447
|
+
/** Present only when the fan was campaign-registered with a `projectId` and filing a sibling
|
|
1448
|
+
* into the project failed — the runs are LIVE; re-attach via POST /projects/:id/members. */
|
|
1449
|
+
projectAttachError?: string;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
915
1452
|
// ── Governance claims (crew#40/43) ─────────────────────────────────────────────
|
|
916
1453
|
|
|
917
1454
|
/**
|
|
@@ -949,17 +1486,29 @@ export interface LaunchRunBody {
|
|
|
949
1486
|
*/
|
|
950
1487
|
projectId?: string;
|
|
951
1488
|
/**
|
|
952
|
-
* Delivery mode (crew#293
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
1489
|
+
* Delivery mode (crew#293, defaulting reworked by crew#393 / api-types 0.18.0).
|
|
1490
|
+
*
|
|
1491
|
+
* `"pr"` ⇒ the daemon appends a hardened deliver Tool phase (push the run's branch, rebase
|
|
1492
|
+
* onto origin's default branch, `gh pr create`; the PR URL is the phase output's last line)
|
|
1493
|
+
* to a PER-RUN copy of the selected workflow def — the shared def is never mutated, and
|
|
1494
|
+
* merge stays human. Requires `workflow`; sending `deliver: "pr"` without one is a 400.
|
|
957
1495
|
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
1496
|
+
* `"none"` ⇒ explicitly no delivery phase (the run's work stays in its worktree and the run
|
|
1497
|
+
* will read `delivery: 'stranded'` on the wire once completed — recoverable any time via
|
|
1498
|
+
* `POST /runs/:id/deliver`).
|
|
1499
|
+
*
|
|
1500
|
+
* OMITTED ⇒ the daemon decides (crew#393): a repo-scoped launch (`repoRef` set) that names a
|
|
1501
|
+
* CODE-WORK `workflow` — a def carrying at least one `executes_code` phase (feature, bug,
|
|
1502
|
+
* migration; not chat/onboarding/recon defs, whose clean worktree the deliver script would
|
|
1503
|
+
* fail loudly) — DEFAULTS to `"pr"`: a completed code run must end with a reviewable
|
|
1504
|
+
* deliverable or an explicit decision not to. The daemon setting `deliverDefault: 'none'`
|
|
1505
|
+
* flips that default off. Repo-less launches, free-text launches (no `workflow`, so there is
|
|
1506
|
+
* no def to append the phase to), and read-only-workflow launches default to `"none"`.
|
|
1507
|
+
*
|
|
1508
|
+
* `"none"` is additive (0.18.0): an older daemon's launch schema rejects it with a 400 —
|
|
1509
|
+
* omit the field entirely against pre-0.18 servers.
|
|
961
1510
|
*/
|
|
962
|
-
deliver?: 'pr';
|
|
1511
|
+
deliver?: 'pr' | 'none';
|
|
963
1512
|
/**
|
|
964
1513
|
* Retry lineage (DES-UX-001 §8.3, CREW-UX-3; api-types 0.8.0): the id of the run this
|
|
965
1514
|
* launch retries. Must name an EXISTING run id — an unknown id fails the launch (400 with
|
|
@@ -988,6 +1537,45 @@ export interface SetGuidanceResult {
|
|
|
988
1537
|
guidance: string;
|
|
989
1538
|
}
|
|
990
1539
|
|
|
1540
|
+
/**
|
|
1541
|
+
* Response of `POST /runs/:id/deliver` (crew#393; api-types 0.18.0) — post-hoc delivery: lift a
|
|
1542
|
+
* COMPLETED repo-scoped run's stranded worktree into a PR with the SAME hardened script the
|
|
1543
|
+
* deliver phase runs (crew#293/#317: commit the run's work, refuse the default branch, rebase
|
|
1544
|
+
* onto origin's default branch — a conflict fails LOUDLY with nothing pushed — push, `gh pr
|
|
1545
|
+
* create`, and re-derive success from a real PR URL). Takes no body.
|
|
1546
|
+
*
|
|
1547
|
+
* Idempotent: a run that already delivered answers 200 with the SAME recorded `prUrl` — a second
|
|
1548
|
+
* call never opens a second PR. Failure is loud, never silent: 404 (unknown run), 409 (run not
|
|
1549
|
+
* completed / repo-less / worktree gone / a delivery already in flight / the script itself failed
|
|
1550
|
+
* — the body's `error` carries the script's own words, e.g. the rebase-conflict message), 500
|
|
1551
|
+
* (the script produced no verifiable PR URL, or could not be spawned).
|
|
1552
|
+
*/
|
|
1553
|
+
export interface DeliverRunResult {
|
|
1554
|
+
prUrl: string;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* The 409 body of `POST /runs/:id/resume` on a TERMINAL run (crew#311; api-types 0.18.0).
|
|
1559
|
+
*
|
|
1560
|
+
* The engine's `resume_run` no-ops on a completed/cancelled run and answers the status token, so
|
|
1561
|
+
* the route used to reply `200 {"status":"cancelled"}` — on the exact runs an operator was trying
|
|
1562
|
+
* to rescue, the recovery affordance read as "resume destroyed my run". A terminal run is now
|
|
1563
|
+
* refused with 409 and the ACTUAL recovery named machine-readably:
|
|
1564
|
+
*
|
|
1565
|
+
* - `'retry'` — relaunch as a new run with `POST /runs {"retryOf":"<id>"}` (cancelled runs,
|
|
1566
|
+
* and completed runs with nothing liftable — including a `delivery: 'vacuous'` run, whose
|
|
1567
|
+
* units produced no work at all);
|
|
1568
|
+
* - `'deliver'` — the run completed with unlifted work in its worktree (`delivery:
|
|
1569
|
+
* 'stranded'`): lift it with `POST /runs/:id/deliver`.
|
|
1570
|
+
*
|
|
1571
|
+
* A FAILED run is not terminal for resume: it re-enters at the cursor as before (200 + status).
|
|
1572
|
+
*/
|
|
1573
|
+
export interface ResumeRefusal {
|
|
1574
|
+
/** Human-readable refusal naming the run's terminal status and the recovery, verbatim. */
|
|
1575
|
+
error: string;
|
|
1576
|
+
recovery: 'retry' | 'deliver';
|
|
1577
|
+
}
|
|
1578
|
+
|
|
991
1579
|
// ── Governance types (crew#40/41) ──────────────────────────────────────────────
|
|
992
1580
|
|
|
993
1581
|
/** Per-app breakdown within a `CoverageReport`. */
|
|
@@ -1177,6 +1765,47 @@ export interface SystemSettings {
|
|
|
1177
1765
|
* the engine default `~/.wicked-worker`.
|
|
1178
1766
|
*/
|
|
1179
1767
|
worker_config_root?: string;
|
|
1768
|
+
/**
|
|
1769
|
+
* The repo-scoped launch delivery DEFAULT (crew#393; api-types 0.18.0, additive). What a
|
|
1770
|
+
* `POST /runs` with `repoRef` + a CODE-WORK `workflow` (a def with at least one
|
|
1771
|
+
* `executes_code` phase) and NO explicit `deliver` resolves to: `'pr'` (the shipped default —
|
|
1772
|
+
* completed code runs open their PR) or `'none'` (work stays in the worktree, surfaced as
|
|
1773
|
+
* `delivery: 'stranded'`). Absent reads as `'pr'`. Never consulted when the launch body sets
|
|
1774
|
+
* `deliver` explicitly, and never applied to repo-less, free-text, or read-only-workflow
|
|
1775
|
+
* launches (those are always `'none'`).
|
|
1776
|
+
*/
|
|
1777
|
+
deliverDefault?: 'pr' | 'none';
|
|
1778
|
+
/**
|
|
1779
|
+
* The stall watchdog's DETECTION threshold (crew#287; api-types 0.18.0 — previously a
|
|
1780
|
+
* daemon-local extension): minutes a run in `executing` may go without ANY engine event on the
|
|
1781
|
+
* daemon's relay before one synthetic `workerStalled` frame per quiet period is broadcast on
|
|
1782
|
+
* `/ws`. Integer 1..1440; absent = the daemon default (15). Detection never touches the run.
|
|
1783
|
+
*/
|
|
1784
|
+
workerStallMinutes?: number;
|
|
1785
|
+
/**
|
|
1786
|
+
* The stall watchdog's ESCALATION threshold (crew#341; api-types 0.18.0): quiet minutes before
|
|
1787
|
+
* the watchdog ACTS on a detected stall (see {@link WorkerStallEscalatedFrame}). Integer
|
|
1788
|
+
* 0..1440; **absent or 0 = escalation OFF — the shipped default** (the issue's design: automatic
|
|
1789
|
+
* recovery on a run that is merely slow would be worse than the wedge). Values below
|
|
1790
|
+
* `workerStallMinutes` escalate at the detection threshold — the ladder never acts before it
|
|
1791
|
+
* has notified.
|
|
1792
|
+
*/
|
|
1793
|
+
workerStallEscalateMinutes?: number;
|
|
1794
|
+
/**
|
|
1795
|
+
* What an armed escalation DOES (crew#341): `'reassign'` (default) recycles the wedged cursor
|
|
1796
|
+
* unit in place — the engine supersedes the stale turn, closes the worker session, bumps the
|
|
1797
|
+
* attempt, and re-dispatches (queued operator injects survive into the fresh turn);
|
|
1798
|
+
* `'notify'` is the fail-loud rung — a `needsYou` `workerStallEscalated` frame + an audit
|
|
1799
|
+
* entry, the run untouched.
|
|
1800
|
+
*/
|
|
1801
|
+
workerStallEscalateAction?: 'reassign' | 'notify';
|
|
1802
|
+
/**
|
|
1803
|
+
* Per-run budget of AUTOMATIC reassigns (crew#341): integer 1..10, default 2. Once spent, a
|
|
1804
|
+
* still-silent run gets `outcome: 'exhausted'` `needsYou` frames instead of further recovery —
|
|
1805
|
+
* a worker that wedges deterministically must reach a human, not loop through seats forever.
|
|
1806
|
+
* Ignored when `workerStallEscalateAction` is `'notify'`.
|
|
1807
|
+
*/
|
|
1808
|
+
workerStallMaxEscalations?: number;
|
|
1180
1809
|
/**
|
|
1181
1810
|
* SKIN-OWNED preference blobs (crew#323). The settings store is shared: alongside the
|
|
1182
1811
|
* engine's keys it carries the studio's own state — `studio.appearance` (accent/logo/theme),
|
|
@@ -1226,6 +1855,14 @@ export interface RequirementsPage {
|
|
|
1226
1855
|
/** Which source served the corpus: the live estate store, or the evidence-gated
|
|
1227
1856
|
* `requirements_graph.json` snapshot, which can lag it by hours (FINDING-065). */
|
|
1228
1857
|
source: 'store' | 'artifact';
|
|
1858
|
+
/**
|
|
1859
|
+
* Keys in `requirements_overrides.json` that matched NO requirement in the corpus. Operator
|
|
1860
|
+
* edits are keyed by SymbolId (store) or `domain::reqId` (artifact); an estate id-scheme
|
|
1861
|
+
* migration re-mints method/field SymbolIds, so overrides keyed by the old ids stop matching and
|
|
1862
|
+
* would otherwise vanish silently. Non-zero means edits exist that no row is showing — re-run
|
|
1863
|
+
* the annotation workflow, then re-apply or delete the stale keys.
|
|
1864
|
+
*/
|
|
1865
|
+
orphanedOverrides?: number;
|
|
1229
1866
|
}
|
|
1230
1867
|
|
|
1231
1868
|
export interface RequirementPatch {
|
|
@@ -1239,7 +1876,11 @@ export interface RequirementPatch {
|
|
|
1239
1876
|
export interface BlastRadius {
|
|
1240
1877
|
target: string;
|
|
1241
1878
|
dependents: Array<{ id: string; name: string; kind: string; file: string; line: number }>;
|
|
1242
|
-
/**
|
|
1879
|
+
/**
|
|
1880
|
+
* References to the target that no resolver bound (ENGINE-CONTRACT §2.1). Repeat call-sites of
|
|
1881
|
+
* an already-bound relationship are NOT counted, so 0 is a legitimate value for a fully-resolved
|
|
1882
|
+
* hot symbol. When non-zero, an empty `dependents` still never means "safe".
|
|
1883
|
+
*/
|
|
1243
1884
|
unresolved: number;
|
|
1244
1885
|
}
|
|
1245
1886
|
|
|
@@ -1349,6 +1990,57 @@ export interface AttachMemberBody {
|
|
|
1349
1990
|
attachedBy?: 'studio' | 'interactive' | 'cli' | 'api';
|
|
1350
1991
|
}
|
|
1351
1992
|
|
|
1993
|
+
/**
|
|
1994
|
+
* The crew half of a governed doc delete (crew#338) — what
|
|
1995
|
+
* `DELETE /projects/:projectId/interactive/docs/:doc` did to crew's four handoff ledgers
|
|
1996
|
+
* (`interactive-{draft,edit,chat,demo}-ledger.json`). Present on every answer of that route so
|
|
1997
|
+
* a partial result can never hide.
|
|
1998
|
+
*/
|
|
1999
|
+
export interface InteractiveDocDeleteLedgerReport {
|
|
2000
|
+
/** True iff every ledger was swept without error. On the 502 refusal paths this is false with
|
|
2001
|
+
* `skipped: true` — the sweep deliberately did not run because interactive's half did not
|
|
2002
|
+
* happen (nothing diverged). */
|
|
2003
|
+
ok: boolean;
|
|
2004
|
+
/** Every replay-dedup row key actually dropped (`<doc>`, `<doc>:v<n>`, `<doc>:m:<id>`, …).
|
|
2005
|
+
* Empty ⇒ nothing was there — deleting a never-drafted doc is a clean no-op on this side. */
|
|
2006
|
+
removed_keys: string[];
|
|
2007
|
+
/** The ledgers that could NOT be swept, by seam name (`draft`|`edit`|`chat`|`demo`) and cause.
|
|
2008
|
+
* Present only when `ok` is false and the sweep actually ran. */
|
|
2009
|
+
errors?: { ledger: string; error: string }[];
|
|
2010
|
+
/** True ⇒ the sweep was deliberately skipped (interactive refused/failed the retire, so
|
|
2011
|
+
* crew's rows are still doing their job and nothing diverged). */
|
|
2012
|
+
skipped?: boolean;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* `DELETE /projects/:projectId/interactive/docs/:doc` (crew#338) — 200: the bridge's own retire
|
|
2017
|
+
* answer (interactive#189's wire, relayed verbatim) plus crew's ledger report. The route's
|
|
2018
|
+
* non-200 answers carry `error` plus the same `ledger` report (404 unknown doc — the ledger is
|
|
2019
|
+
* STILL swept, cleaning ghosts of hand-deleted workspaces, but ONLY on the retire wire's own
|
|
2020
|
+
* 404 body `{"error":"unknown doc"}`; 500 partial — interactive retired but the sweep failed,
|
|
2021
|
+
* the body names both halves; 502 — interactive did not retire (5xx, unreachable, or a 404
|
|
2022
|
+
* without the wire's body from a bridge predating the retire route), nothing swept; 409 — the
|
|
2023
|
+
* bridge's build-in-flight refusal relayed verbatim, no `ledger` field).
|
|
2024
|
+
*/
|
|
2025
|
+
export interface InteractiveDocDeleteResponse {
|
|
2026
|
+
/** The doc name (slug). */
|
|
2027
|
+
name: string;
|
|
2028
|
+
kind: 'doc' | 'html' | 'source' | 'demo';
|
|
2029
|
+
retired: true;
|
|
2030
|
+
/** True on a repeat delete — idempotent, with the ORIGINAL `retired_at` and no `event_id`. */
|
|
2031
|
+
already_retired: boolean;
|
|
2032
|
+
/** ISO-8601 retirement timestamp. */
|
|
2033
|
+
retired_at: string;
|
|
2034
|
+
/** Head version at retirement. */
|
|
2035
|
+
head: number;
|
|
2036
|
+
/** Lineage size at retirement. */
|
|
2037
|
+
versions: number;
|
|
2038
|
+
/** The `wicked.interactive.doc.retired` bus event id — first retire only (no re-emit). */
|
|
2039
|
+
event_id?: number;
|
|
2040
|
+
/** What crew dropped from its handoff ledgers. */
|
|
2041
|
+
ledger: InteractiveDocDeleteLedgerReport;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1352
2044
|
/**
|
|
1353
2045
|
* One normalized entry of the merged project activity feed
|
|
1354
2046
|
* (`GET /projects/:id/activity`, ADR §5.2): core events of member runs/chats
|
|
@@ -1419,3 +2111,493 @@ export interface ChatOpenBody {
|
|
|
1419
2111
|
/** File the chat into a project (`crew.chat` membership, attached on open). */
|
|
1420
2112
|
projectId?: string;
|
|
1421
2113
|
}
|
|
2114
|
+
|
|
2115
|
+
// ── Project code graph (DES-PROJECT-001; the co-located multi-repo graph) ──────
|
|
2116
|
+
//
|
|
2117
|
+
// A project's `crew.repo` members indexed into ONE wicked-estate database, each under a stable
|
|
2118
|
+
// label, so one query answers over every repo the project contains.
|
|
2119
|
+
//
|
|
2120
|
+
// THE LIMIT, stated on the wire because it is not obvious from the results: this is CO-LOCATION,
|
|
2121
|
+
// NOT LINKAGE. estate resolves edges within a labelled repo's own nodes only, exactly as if each
|
|
2122
|
+
// repo sat in its own database. `studio → wicked-crew-api-types → crew` does NOT traverse; what
|
|
2123
|
+
// these shapes carry is per-repo results gathered in one place, with the repo named on every hit.
|
|
2124
|
+
// Every response repeats it in `linkage` + `note` so a consumer cannot read a cross-repo answer as
|
|
2125
|
+
// a cross-repo TRACE.
|
|
2126
|
+
|
|
2127
|
+
/** Co-location, never linkage — a single-valued field so a future linked mode is additive. */
|
|
2128
|
+
export type ProjectGraphLinkage = 'co-located';
|
|
2129
|
+
|
|
2130
|
+
/**
|
|
2131
|
+
* Why a project graph cannot answer, or the terms on which it can.
|
|
2132
|
+
*
|
|
2133
|
+
* - `ready` — indexed, two or more repos.
|
|
2134
|
+
* - `ready-single-repo` — indexed, exactly one repo: answers are correct but can never be
|
|
2135
|
+
* cross-repo, which a caller asking a project-scoped question needs told.
|
|
2136
|
+
* - `no-repo-members` — the project has no `crew.repo` member. Not an error and not an empty
|
|
2137
|
+
* graph: there is nothing to build one from.
|
|
2138
|
+
* - `not-indexed` — it has repo members and no database yet. `POST /projects/:id/graph/refresh`
|
|
2139
|
+
* is the fix, and the `detail` says so.
|
|
2140
|
+
* - `engine-too-old` — the running wicked-core addon does not publish `code_graph_db` on the repo
|
|
2141
|
+
* record (wicked-core#170), so its repo-record contract predates this surface.
|
|
2142
|
+
*/
|
|
2143
|
+
export type ProjectGraphState =
|
|
2144
|
+
| 'ready'
|
|
2145
|
+
| 'ready-single-repo'
|
|
2146
|
+
| 'no-repo-members'
|
|
2147
|
+
| 'not-indexed'
|
|
2148
|
+
| 'engine-too-old';
|
|
2149
|
+
|
|
2150
|
+
/** One member repo's standing in the project graph. */
|
|
2151
|
+
export interface ProjectGraphRepo {
|
|
2152
|
+
/** Registry id (`RepoEntry.id`) — the member ref this came from. */
|
|
2153
|
+
repoId: string;
|
|
2154
|
+
/** The estate label its rows carry, and the prefix on every `file` path it contributes. */
|
|
2155
|
+
label: string;
|
|
2156
|
+
rootPath: string;
|
|
2157
|
+
/** `false` ⇒ its symbols are NOT in the graph; `reason` says why and results stay partial. */
|
|
2158
|
+
indexed: boolean;
|
|
2159
|
+
/** Commit indexed, when git could answer. */
|
|
2160
|
+
head?: string;
|
|
2161
|
+
/** Unix millis of the index that put these rows in. */
|
|
2162
|
+
indexedAt?: number;
|
|
2163
|
+
/**
|
|
2164
|
+
* Present when `indexed` is false: registry miss, index failure, never refreshed, or the rows
|
|
2165
|
+
* under this label were indexed from a DIFFERENT root than the registry now names (a repo that
|
|
2166
|
+
* moved — wicked-estate binds a label to the root it first saw and refuses to rebind it, so the
|
|
2167
|
+
* graph keeps the old checkout's symbols until the project graph is rebuilt).
|
|
2168
|
+
*/
|
|
2169
|
+
reason?: string;
|
|
2170
|
+
/**
|
|
2171
|
+
* The action that actually resolves THIS cause, when one action does. Carried per-row because
|
|
2172
|
+
* the causes do not share a remedy: "never refreshed" and "attached since the last refresh" are
|
|
2173
|
+
* fixed by a refresh, a DANGLING member ref is not (the registry no longer knows the ref, so a
|
|
2174
|
+
* refresh indexes nothing new), and a MOVED root needs a rebuild when estate refuses to rebind
|
|
2175
|
+
* the label. Absent ⇒ no single action fixes it, or `reason` already spells the remedy out.
|
|
2176
|
+
* Never assume a refresh: telling an operator mid-incident to run one that cannot work costs
|
|
2177
|
+
* them the time it takes to run it and the trust to believe the next message.
|
|
2178
|
+
*/
|
|
2179
|
+
remedy?: string;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/** `GET /projects/:id/graph` — what the project graph holds and what it cannot answer. */
|
|
2183
|
+
export interface ProjectGraphStatus {
|
|
2184
|
+
projectId: string;
|
|
2185
|
+
state: ProjectGraphState;
|
|
2186
|
+
/** One sentence naming the state's cause and its remedy. Always present, never empty. */
|
|
2187
|
+
detail: string;
|
|
2188
|
+
/** Absolute path of the co-located database; `null` when there is none to point at. */
|
|
2189
|
+
dbPath: string | null;
|
|
2190
|
+
repos: ProjectGraphRepo[];
|
|
2191
|
+
/** Member repos absent from the graph — any answer is PARTIAL while this is non-empty. */
|
|
2192
|
+
missingRepos: string[];
|
|
2193
|
+
/** Labels still in the database whose repo is no longer a member; excluded from results. */
|
|
2194
|
+
staleRepos: string[];
|
|
2195
|
+
linkage: ProjectGraphLinkage;
|
|
2196
|
+
note: string;
|
|
2197
|
+
/** Unix millis of the last refresh; `null` when never refreshed. */
|
|
2198
|
+
updatedAt: number | null;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
/**
|
|
2202
|
+
* `POST /projects/:id/graph/refresh` body. Optional — an empty body is the plain incremental
|
|
2203
|
+
* refresh.
|
|
2204
|
+
*
|
|
2205
|
+
* `force: true` exists for MIGRATION, not for routine use: the plain refresh skips any member repo
|
|
2206
|
+
* whose clean checkout is still at the HEAD the manifest recorded, so after a wicked-estate upgrade
|
|
2207
|
+
* an UNCHANGED repo never re-indexes and its rows keep the old binary's id scheme and unresolved
|
|
2208
|
+
* accounting (ENGINE-CONTRACT §2.1) indefinitely. Force bypasses that skip for every member repo
|
|
2209
|
+
* AND passes `--force` to `wicked-estate index`, so estate performs a full re-extract even when its
|
|
2210
|
+
* own incremental digest would skip. Expect it to take as long as the project's first build.
|
|
2211
|
+
*/
|
|
2212
|
+
export interface RefreshProjectGraphBody {
|
|
2213
|
+
/** Re-index every member repo, bypassing the clean-HEAD skip and estate's digest skip. */
|
|
2214
|
+
force?: boolean;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* One member repo's outcome in a refresh — why it did or did not index. `skipped-head-unchanged`
|
|
2219
|
+
* is the row an operator reads when asking why a repo did not migrate after an estate upgrade:
|
|
2220
|
+
* the plain refresh trusts a clean checkout at the manifest's HEAD, so only `force` (or a new
|
|
2221
|
+
* commit) makes that repo re-index.
|
|
2222
|
+
*/
|
|
2223
|
+
export interface ProjectGraphRefreshRepo {
|
|
2224
|
+
repoId: string;
|
|
2225
|
+
/** The estate label its rows carry. */
|
|
2226
|
+
label: string;
|
|
2227
|
+
/** What this refresh did with the repo. */
|
|
2228
|
+
action: 'indexed' | 'skipped-head-unchanged' | 'failed';
|
|
2229
|
+
/**
|
|
2230
|
+
* Bounded tail (last 2 KiB) of `wicked-estate index` stderr, present when the binary said
|
|
2231
|
+
* anything — notably estate's id_scheme migration notice explaining why a refresh that used to
|
|
2232
|
+
* skip in a second ran a full re-extract for minutes.
|
|
2233
|
+
*/
|
|
2234
|
+
stderrTail?: string;
|
|
2235
|
+
/** Present when `action` is `failed` — the same message the `failed` array carries. */
|
|
2236
|
+
error?: string;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
/** `POST /projects/:id/graph/refresh` — an incremental (re)build, repo by repo. */
|
|
2240
|
+
export interface ProjectGraphRefreshResult {
|
|
2241
|
+
status: ProjectGraphStatus;
|
|
2242
|
+
/** Labels re-indexed this run. */
|
|
2243
|
+
indexed: string[];
|
|
2244
|
+
/** Labels skipped because the checkout is clean and its HEAD is already in the graph. */
|
|
2245
|
+
skipped: string[];
|
|
2246
|
+
/** Repos that failed to index, with the reason; the graph keeps whatever it already had. */
|
|
2247
|
+
failed: Array<{ repoId: string; label: string; error: string }>;
|
|
2248
|
+
/** Per-repo outcomes with the WHY (and any index stderr) attached — one row per member repo. */
|
|
2249
|
+
repos?: ProjectGraphRefreshRepo[];
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/** One hit, always attributed: a cross-repo answer that does not say WHERE is not an answer. */
|
|
2253
|
+
export interface ProjectGraphHit {
|
|
2254
|
+
/** The repo's registry id. */
|
|
2255
|
+
repoId: string;
|
|
2256
|
+
/** The repo's estate label. */
|
|
2257
|
+
repo: string;
|
|
2258
|
+
/** estate SymbolId — carries the label, so it is unique across the project. */
|
|
2259
|
+
id: string;
|
|
2260
|
+
/**
|
|
2261
|
+
* The graph node's name, VERBATIM from estate. For a `kind: 'file'` node that name IS the
|
|
2262
|
+
* label-prefixed path (`wicked-vault/src/vault/vault.mjs`) — left as estate minted it, because
|
|
2263
|
+
* rewriting it would produce a name that resolves to nothing. Use `repo` + `file` to display it.
|
|
2264
|
+
*/
|
|
2265
|
+
name: string;
|
|
2266
|
+
kind: string;
|
|
2267
|
+
/** REPO-RELATIVE path, label stripped — the same spelling `/repos/:id/graph` returns. */
|
|
2268
|
+
file: string;
|
|
2269
|
+
line: number;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/** Per-repo hit counts, so "which repo does this touch" is answerable without scanning hits. */
|
|
2273
|
+
export interface ProjectGraphRepoCount {
|
|
2274
|
+
repoId: string;
|
|
2275
|
+
repo: string;
|
|
2276
|
+
count: number;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
/** `GET /projects/:id/graph/blast-radius?name=` — dependents across every member repo. */
|
|
2280
|
+
export interface ProjectBlastRadius {
|
|
2281
|
+
projectId: string;
|
|
2282
|
+
target: string;
|
|
2283
|
+
dependents: ProjectGraphHit[];
|
|
2284
|
+
byRepo: ProjectGraphRepoCount[];
|
|
2285
|
+
/**
|
|
2286
|
+
* References to the target that no resolver bound (ENGINE-CONTRACT §2.1). Repeat call-sites of
|
|
2287
|
+
* an already-bound relationship are NOT counted, so 0 is a legitimate value for a fully-resolved
|
|
2288
|
+
* hot symbol. When non-zero, an empty `dependents` still never means "safe".
|
|
2289
|
+
*/
|
|
2290
|
+
unresolved: number;
|
|
2291
|
+
/** Labels actually covered by this answer. */
|
|
2292
|
+
reposSearched: string[];
|
|
2293
|
+
/** Member repos NOT covered — the answer is partial while this is non-empty. */
|
|
2294
|
+
missingRepos: string[];
|
|
2295
|
+
linkage: ProjectGraphLinkage;
|
|
2296
|
+
note: string;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/** `GET /projects/:id/graph/search?name=` — exact-name symbol resolution across member repos. */
|
|
2300
|
+
export interface ProjectGraphSearch {
|
|
2301
|
+
projectId: string;
|
|
2302
|
+
query: string;
|
|
2303
|
+
matches: ProjectGraphHit[];
|
|
2304
|
+
byRepo: ProjectGraphRepoCount[];
|
|
2305
|
+
reposSearched: string[];
|
|
2306
|
+
missingRepos: string[];
|
|
2307
|
+
linkage: ProjectGraphLinkage;
|
|
2308
|
+
/**
|
|
2309
|
+
* Carries the co-location limit AND the matching rule: this is EXACT-NAME resolution, so an
|
|
2310
|
+
* empty `matches` means "no symbol by that exact name" and never "not in this project". A
|
|
2311
|
+
* partial or fuzzy name returns nothing even when the symbol exists.
|
|
2312
|
+
*/
|
|
2313
|
+
note: string;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
// ── Campaigns (DES-CAMPAIGN-001 / crew#342 slice 1 + TH-9) ───────────────────
|
|
2317
|
+
// The engine's Campaign scheduler — a durable, crash-resumable, dependency-aware parallel DAG of
|
|
2318
|
+
// governed Runs — exposed over `/api/v1/campaigns`. The GET routes return the ENGINE's persisted
|
|
2319
|
+
// shapes verbatim (wicked-core `src/campaign.rs` serde: snake_case fields, snake_case enum
|
|
2320
|
+
// tokens), same doctrine as `SessionView`: one producer of the wire shape, zero re-spelling in
|
|
2321
|
+
// the daemon. The POST body (`LaunchCampaignBody`) is the daemon's own camelCase request contract
|
|
2322
|
+
// — crew maps it onto the engine def, composing one Tool-executor workflow per deterministic
|
|
2323
|
+
// scenario (spec FILE PATHS on the argv, never scenario bodies — the 1022-byte PTY prompt trap).
|
|
2324
|
+
|
|
2325
|
+
/** Per-node lifecycle status. Terminal = `completed` | `failed` | `blocked` | `cancelled`. */
|
|
2326
|
+
export type CampaignNodeStatus =
|
|
2327
|
+
| 'pending'
|
|
2328
|
+
| 'ready'
|
|
2329
|
+
| 'running'
|
|
2330
|
+
| 'awaiting_human'
|
|
2331
|
+
| 'ready_to_resume'
|
|
2332
|
+
| 'completed'
|
|
2333
|
+
| 'failed'
|
|
2334
|
+
| 'blocked'
|
|
2335
|
+
| 'cancelled';
|
|
2336
|
+
|
|
2337
|
+
/** Campaign lifecycle status — also the token `POST /campaigns/:id/resume` / `/cancel` resolve to. */
|
|
2338
|
+
export type CampaignStatus =
|
|
2339
|
+
| 'running'
|
|
2340
|
+
| 'paused'
|
|
2341
|
+
| 'completed'
|
|
2342
|
+
| 'partially_completed'
|
|
2343
|
+
| 'failed'
|
|
2344
|
+
| 'cancelled';
|
|
2345
|
+
|
|
2346
|
+
/** When a dependency edge is satisfied: dep `completed` only, or any terminal outcome. */
|
|
2347
|
+
export type CampaignEdgeCondition = 'on_success' | 'on_terminal';
|
|
2348
|
+
|
|
2349
|
+
/** How a node failure propagates (DES-CAMPAIGN-001 §5.2). */
|
|
2350
|
+
export type CampaignFailurePolicy = 'fail_fast' | 'continue_independent' | 'human_gate_on_failure';
|
|
2351
|
+
|
|
2352
|
+
/**
|
|
2353
|
+
* What one campaign node runs — the engine's reusable Run specification (mirrors a launch minus
|
|
2354
|
+
* the session id, which the scheduler derives as `{campaign}:{node}:a{attempt}`). A node inherits
|
|
2355
|
+
* governance, worktree isolation, HITL gates, live output, and per-Run resume from the Run it is.
|
|
2356
|
+
*/
|
|
2357
|
+
export interface CampaignRunSpec {
|
|
2358
|
+
/** The free-text problem this node's Run decomposes — for a scenario node, a short human label
|
|
2359
|
+
* (the scenario body lives in its spec FILE; it is never inlined here). */
|
|
2360
|
+
problem: string;
|
|
2361
|
+
/** The council roster (`AgenticCli` seats) convened for this node's Run. */
|
|
2362
|
+
clis: unknown[];
|
|
2363
|
+
entity_mode: EntityMode;
|
|
2364
|
+
/** The node's internal human-confirm gate policy (engine serde shape); absent = none. */
|
|
2365
|
+
human_confirm?: unknown;
|
|
2366
|
+
/** The registered repo the node's Run targets, if any (creates an isolated worktree). */
|
|
2367
|
+
repo_ref?: string | null;
|
|
2368
|
+
/** The registered `WorkflowDef` id this node's Run follows — for a deterministic scenario node,
|
|
2369
|
+
* the per-node composed Tool workflow (`campaign-<campaign>-<node>`). */
|
|
2370
|
+
workflow_id?: string | null;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
/** A schedulable unit of the DAG — one node = one governed core Run. */
|
|
2374
|
+
export interface CampaignNode {
|
|
2375
|
+
/** Stable node id, unique within the campaign (never contains `:` — it keys the run id). */
|
|
2376
|
+
node_id: string;
|
|
2377
|
+
run_spec: CampaignRunSpec;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
/** A dependency edge `from -> to`: `to` becomes eligible once `from` satisfies `condition`. */
|
|
2381
|
+
export interface CampaignEdge {
|
|
2382
|
+
from: string;
|
|
2383
|
+
to: string;
|
|
2384
|
+
condition: CampaignEdgeCondition;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
/** The static campaign definition — validated at launch (cycle / empty / duplicate-edge /
|
|
2388
|
+
* unknown-endpoint rejects) and persisted verbatim inside the live {@link Campaign}. */
|
|
2389
|
+
export interface CampaignDef {
|
|
2390
|
+
id: string;
|
|
2391
|
+
name: string;
|
|
2392
|
+
nodes: CampaignNode[];
|
|
2393
|
+
edges: CampaignEdge[];
|
|
2394
|
+
policy: CampaignFailurePolicy;
|
|
2395
|
+
/** Global concurrency cap (>= 1) — a resource guard on parallel worktrees + CLI subprocesses. */
|
|
2396
|
+
max_concurrency: number;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
/**
|
|
2400
|
+
* The live campaign instance (`GET /campaigns`, `GET /campaigns/:id`) — durable and
|
|
2401
|
+
* crash-resumable; the scheduler re-derives the ready set from these persisted statuses on
|
|
2402
|
+
* resume, never re-running a completed node.
|
|
2403
|
+
*/
|
|
2404
|
+
export interface Campaign {
|
|
2405
|
+
id: string;
|
|
2406
|
+
def_id: string;
|
|
2407
|
+
status: CampaignStatus;
|
|
2408
|
+
/** The full definition, embedded so a resume needs no second store. */
|
|
2409
|
+
def: CampaignDef;
|
|
2410
|
+
node_status: Record<string, CampaignNodeStatus>;
|
|
2411
|
+
/** node_id -> live Run id, always `{campaign}:{node}:a{attempt}` (attempt-keyed, idempotent). */
|
|
2412
|
+
node_run_id: Record<string, string>;
|
|
2413
|
+
/** node_id -> 0-based attempt counter (a `human_gate_on_failure` Retry bumps it). */
|
|
2414
|
+
node_attempt: Record<string, number>;
|
|
2415
|
+
/** Persisted amend text of an approved-but-unslotted per-node gate decision (crash-safe). */
|
|
2416
|
+
pending_decision_amend: Record<string, string | null>;
|
|
2417
|
+
/** `human_gate_on_failure` queue — failed nodes awaiting a Retry/Skip/Abort decision. */
|
|
2418
|
+
pending_failure_gates: string[];
|
|
2419
|
+
/** Whether a fail-fast (or Abort) tripped — finalization lands on `failed`. */
|
|
2420
|
+
fail_fast_tripped: boolean;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* One scenario of `POST /campaigns` — the daemon maps each onto a {@link CampaignNode}.
|
|
2425
|
+
*
|
|
2426
|
+
* Exactly one of `tool` / `agent` per scenario:
|
|
2427
|
+
* - `tool` (deterministic): the daemon composes a single-phase Tool-executor workflow running
|
|
2428
|
+
* `cmd` verbatim (no council, no LLM). Every argv token must be a single line of ≤ 1022
|
|
2429
|
+
* bytes — pass the scenario/spec as a FILE PATH argument, never inline its body. A body
|
|
2430
|
+
* smuggled into the argv is rejected with a 400 naming the offending token.
|
|
2431
|
+
* - `agent` (exploratory/authoring): a governed agent run over `problem` — same byte
|
|
2432
|
+
* discipline, because the problem text reaches PTY workers as prompt material.
|
|
2433
|
+
*/
|
|
2434
|
+
export interface CampaignScenario {
|
|
2435
|
+
/** Node id, unique within the campaign (letters/digits/dots/hyphens/underscores; no `:`). */
|
|
2436
|
+
id: string;
|
|
2437
|
+
/** Short human label for the scoreboard; defaults to the id. NOT the scenario body. */
|
|
2438
|
+
title?: string;
|
|
2439
|
+
/** Ids of scenarios that must reach `depsCondition` before this one dispatches. */
|
|
2440
|
+
deps?: string[];
|
|
2441
|
+
/** Edge condition applied to every dep edge of this scenario (default `on_success`). */
|
|
2442
|
+
depsCondition?: CampaignEdgeCondition;
|
|
2443
|
+
/** Deterministic execution: argv run by the engine's Tool executor, file paths not bodies. */
|
|
2444
|
+
tool?: { cmd: string[] };
|
|
2445
|
+
/** Governed agent execution: the problem statement (short — reaches workers as a prompt). */
|
|
2446
|
+
agent?: { problem: string; workflow?: string };
|
|
2447
|
+
/** Registered repo this node runs within (isolated worktree). Omit for a repo-less node. */
|
|
2448
|
+
repoRef?: string;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
/** `POST /campaigns` request body → 201 {@link LaunchCampaignResponse}. */
|
|
2452
|
+
export interface LaunchCampaignBody {
|
|
2453
|
+
/** Campaign id (also the def id). Defaults to a generated `campaign-<uuid>`. */
|
|
2454
|
+
id?: string;
|
|
2455
|
+
name?: string;
|
|
2456
|
+
scenarios: CampaignScenario[];
|
|
2457
|
+
/** Failure propagation policy (default `continue_independent`). */
|
|
2458
|
+
policy?: CampaignFailurePolicy;
|
|
2459
|
+
/** Global concurrency cap, >= 1 (default 2). */
|
|
2460
|
+
maxConcurrency?: number;
|
|
2461
|
+
/** JSON array of `AgenticCli` seats for agent scenarios; defaults to the daemon roster. */
|
|
2462
|
+
clisJson?: string;
|
|
2463
|
+
/**
|
|
2464
|
+
* The multiscope wire (api-types 0.15.0), shared with `POST /testing/recon`: scope the launch
|
|
2465
|
+
* to a PROJECT — crew resolves the project's `crew.repo` members server-side. Unknown project
|
|
2466
|
+
* ⇒ 404; a project with zero repo members (and no `repoRefs` to fall back on) ⇒ 400 naming
|
|
2467
|
+
* the fix. Combined with `repoRefs` ⇒ the union. Because one campaign node runs against ONE
|
|
2468
|
+
* repo, every scenario that does not pin its own `repoRef` is FANNED — one node per resolved
|
|
2469
|
+
* repo, same campaign — and the 201 body carries the fanned nodes' run ids (`runIds`).
|
|
2470
|
+
* Omit both fields ⇒ the launch behaves exactly as before (backward compatible).
|
|
2471
|
+
*/
|
|
2472
|
+
projectId?: string;
|
|
2473
|
+
/**
|
|
2474
|
+
* The multiscope wire (api-types 0.15.0): explicit codebase attachments — registered repo
|
|
2475
|
+
* refs (ids; a unique repo NAME also resolves), deduped, min 1 when present. A ref that does
|
|
2476
|
+
* not resolve fails the WHOLE launch with a 400 naming it — never a silently narrowed scope.
|
|
2477
|
+
*/
|
|
2478
|
+
repoRefs?: string[];
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
/**
|
|
2482
|
+
* `POST /campaigns` 201 body. `runIds` is ADDITIVE (api-types 0.15.0): present exactly when
|
|
2483
|
+
* the launch used the multiscope fields (`projectId`/`repoRefs`), carrying the attempt-0 run
|
|
2484
|
+
* ids (`{campaign}:{node}:a0`) of the launch's nodes — repo-major over the fanned scenarios
|
|
2485
|
+
* (repos in resolved input order), then the scenarios that pinned their own `repoRef`.
|
|
2486
|
+
* Consumers treat `runIds.length >= 1` as the source of truth for what launched; a legacy
|
|
2487
|
+
* body keeps the legacy `{ campaignId }` answer.
|
|
2488
|
+
*/
|
|
2489
|
+
export interface LaunchCampaignResponse {
|
|
2490
|
+
campaignId: string;
|
|
2491
|
+
runIds?: string[];
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
/**
|
|
2495
|
+
* The 11 campaign frames of the `/ws` stream (wicked-core `event_to_json`, camelCase tags), as a
|
|
2496
|
+
* discriminated union for consumers that narrow on `type` — the TH-14 scoreboard renders node
|
|
2497
|
+
* status from exactly these. They also flow through the permissive {@link CoreEvent}; the daemon
|
|
2498
|
+
* relay is allowlist-free, so every frame below reaches `/ws` clients byte-for-byte.
|
|
2499
|
+
*/
|
|
2500
|
+
export type CampaignEvent =
|
|
2501
|
+
| { type: 'campaignLaunched'; campaign: string }
|
|
2502
|
+
| { type: 'campaignNodeReady'; campaign: string; node: string }
|
|
2503
|
+
| { type: 'campaignNodeStarted'; campaign: string; node: string; runId: string }
|
|
2504
|
+
| { type: 'campaignNodeAwaitingHuman'; campaign: string; node: string; runId: string; prompt: string }
|
|
2505
|
+
| { type: 'campaignNodeCompleted'; campaign: string; node: string }
|
|
2506
|
+
| { type: 'campaignNodeFailed'; campaign: string; node: string }
|
|
2507
|
+
| { type: 'campaignNodeBlocked'; campaign: string; node: string }
|
|
2508
|
+
| { type: 'campaignPaused'; campaign: string }
|
|
2509
|
+
| { type: 'campaignCompleted'; campaign: string }
|
|
2510
|
+
| { type: 'campaignFailed'; campaign: string }
|
|
2511
|
+
| { type: 'campaignCancelled'; campaign: string };
|
|
2512
|
+
|
|
2513
|
+
// ── Diagnostics (api-types 0.16.0) ──────────────────────────────────────────────
|
|
2514
|
+
|
|
2515
|
+
/**
|
|
2516
|
+
* Component versions of the running deployment (`GET /diagnostics`). The honesty rule of the
|
|
2517
|
+
* whole diagnostics surface: a version the daemon cannot determine is `null` — never guessed.
|
|
2518
|
+
*/
|
|
2519
|
+
export interface DiagnosticsComponents {
|
|
2520
|
+
/** The wicked-crew daemon's own package version (always known). */
|
|
2521
|
+
crew: string;
|
|
2522
|
+
/** The bundled studio SPA's version, read from the manifest the bundle ships
|
|
2523
|
+
* (`testid-inventory.json` → `studioVersion`); `null` on a headless daemon or a bundle
|
|
2524
|
+
* that predates the manifest. */
|
|
2525
|
+
studioBundle: string | null;
|
|
2526
|
+
/** The installed `wicked-core-ts` engine binding's version; `null` when unresolvable. */
|
|
2527
|
+
coreTs: string | null;
|
|
2528
|
+
/** Engine binary versions via `--version`, keyed by binary name (`wicked-core`,
|
|
2529
|
+
* `wicked-estate`) — probed ONLY through paths crew already resolves (`WICKED_CORE_EXE`,
|
|
2530
|
+
* bare PATH name); `null` per binary when unresolved or the probe fails. */
|
|
2531
|
+
engineBinaries: Record<string, string | null>;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
/** Daemon process runtime facts (`GET /diagnostics`). */
|
|
2535
|
+
export interface DiagnosticsDaemon {
|
|
2536
|
+
/** Milliseconds since this daemon process started. */
|
|
2537
|
+
uptimeMs: number;
|
|
2538
|
+
/** Epoch ms the process started (now − uptime). */
|
|
2539
|
+
startedAt: number;
|
|
2540
|
+
/** The actually-bound HTTP port (honours --port / CREW_PORT / port 0). */
|
|
2541
|
+
port: number;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
/** One store file (or dir, sized as a content total) under the daemon's state home. Paths and
|
|
2545
|
+
* sizes only — file contents never ride this wire. */
|
|
2546
|
+
export interface DiagnosticsStoreFile {
|
|
2547
|
+
/** Filename relative to the state home (`core.db`, `core.db-wal`, `core.db.events`, …). */
|
|
2548
|
+
name: string;
|
|
2549
|
+
/** Absolute path. */
|
|
2550
|
+
path: string;
|
|
2551
|
+
/** File size, or the total of a directory's contents (the events dir). */
|
|
2552
|
+
bytes: number;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
/** One captured error-level line from the daemon's own log ring (`GET /diagnostics`). */
|
|
2556
|
+
export interface DiagnosticsRecentError {
|
|
2557
|
+
/** Epoch ms the line was logged. */
|
|
2558
|
+
ts: number;
|
|
2559
|
+
/** Which record produced it (`daemon` = the in-process pino error ring). */
|
|
2560
|
+
source: string;
|
|
2561
|
+
/** The logged message, length-bounded. */
|
|
2562
|
+
line: string;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
/**
|
|
2566
|
+
* Per-CLI ACP health, folded from the durable run event logs
|
|
2567
|
+
* (`<state-home>/core.db.events/*.ndjson`, `acpSessionStarted` / `acpFallback` frames).
|
|
2568
|
+
* A CLI that never appears in the logs has no key — absence spells "no ACP traffic recorded",
|
|
2569
|
+
* and zero counts are never invented for it.
|
|
2570
|
+
*/
|
|
2571
|
+
export interface AcpCliDiagnostics {
|
|
2572
|
+
/** Count of `acpSessionStarted` events for this cliKey. */
|
|
2573
|
+
sessionsStarted: number;
|
|
2574
|
+
/** Count of `acpFallback` events (ACP unavailable/failed; run continued single-shot). */
|
|
2575
|
+
fallbacks: number;
|
|
2576
|
+
/** Fallback counts by `fallbackKind` (`session_died`, `auth_required`, `binary_unavailable`, …). */
|
|
2577
|
+
fallbackKinds: Record<string, number>;
|
|
2578
|
+
/** Epoch ms of the newest `acpSessionStarted`, or `null` when none recorded. */
|
|
2579
|
+
lastStartedTs: number | null;
|
|
2580
|
+
/** Epoch ms of the newest `acpFallback`, or `null` when none recorded. */
|
|
2581
|
+
lastFallbackTs: number | null;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
/** The ACP fold of `GET /diagnostics` — keyed by `cliKey` (`claude`, `pi`, `codex`, …). */
|
|
2585
|
+
export interface AcpDiagnostics {
|
|
2586
|
+
byCli: Record<string, AcpCliDiagnostics>;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
/**
|
|
2590
|
+
* `GET /diagnostics` (api-types 0.16.0) — the daemon's read-only self-knowledge surface:
|
|
2591
|
+
* what is deployed, what it stores, what has gone wrong recently, and whether ACP is really
|
|
2592
|
+
* working across the CLIs. Every field is derived from records the daemon already owns;
|
|
2593
|
+
* anything it cannot answer is `null` / empty, never fabricated.
|
|
2594
|
+
*/
|
|
2595
|
+
export interface DiagnosticsResponse {
|
|
2596
|
+
components: DiagnosticsComponents;
|
|
2597
|
+
daemon: DiagnosticsDaemon;
|
|
2598
|
+
/** `core.db` + sidecars + the events dir (as a total), sorted by name. */
|
|
2599
|
+
stores: DiagnosticsStoreFile[];
|
|
2600
|
+
/** Bounded tail of the daemon's own error-level log lines, newest first. */
|
|
2601
|
+
recentErrors: DiagnosticsRecentError[];
|
|
2602
|
+
acp: AcpDiagnostics;
|
|
2603
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wicked-crew-api-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "The wire contract of the wicked-crew daemon's /api/v1 REST surface and /ws CoreEvent frames \u2014 types only, zero runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|