wicked-crew 0.15.1 → 0.15.3
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/dist/api/gate-cache.d.ts +98 -17
- package/dist/api/gate-cache.d.ts.map +1 -1
- package/dist/api/gate-cache.js +136 -25
- package/dist/api/gate-cache.js.map +1 -1
- package/dist/api/mcp.d.ts.map +1 -1
- package/dist/api/mcp.js +32 -19
- package/dist/api/mcp.js.map +1 -1
- package/dist/api/recording.d.ts +3 -2
- package/dist/api/recording.d.ts.map +1 -1
- package/dist/api/recording.js +13 -1
- package/dist/api/recording.js.map +1 -1
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +8 -6
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +557 -353
- package/dist/api/routes.js.map +1 -1
- package/dist/api/seat-agents.d.ts.map +1 -1
- package/dist/api/seat-agents.js +19 -5
- package/dist/api/seat-agents.js.map +1 -1
- package/dist/api/seat-standing.d.ts +13 -0
- package/dist/api/seat-standing.d.ts.map +1 -1
- package/dist/api/seat-standing.js +50 -42
- package/dist/api/seat-standing.js.map +1 -1
- package/dist/api/seat-version.d.ts +47 -5
- package/dist/api/seat-version.d.ts.map +1 -1
- package/dist/api/seat-version.js +54 -7
- package/dist/api/seat-version.js.map +1 -1
- package/dist/api/server.js +1 -1
- package/dist/api/server.js.map +1 -1
- package/dist/cli/index.js +14 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +11 -0
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +15 -0
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/engine-roster.d.ts +12 -6
- package/dist/core/engine-roster.d.ts.map +1 -1
- package/dist/core/engine-roster.js +30 -13
- package/dist/core/engine-roster.js.map +1 -1
- package/dist/core/vault-bin.d.ts +69 -0
- package/dist/core/vault-bin.d.ts.map +1 -0
- package/dist/core/vault-bin.js +101 -0
- package/dist/core/vault-bin.js.map +1 -0
- package/dist/mcp/policies.d.ts +22 -1
- package/dist/mcp/policies.d.ts.map +1 -1
- package/dist/mcp/policies.js +56 -20
- package/dist/mcp/policies.js.map +1 -1
- package/dist/studio/assets/index-70ozkvqx.css +32 -0
- package/dist/studio/assets/{index-BYPgSjPo.js → index-CvH1l1tp.js} +217 -216
- package/dist/studio/editors/wicked-deck/editor.json +1 -1
- package/dist/studio/editors/wicked-deck/index.html +1 -1
- package/dist/studio/editors/wicked-doc/editor.json +1 -1
- package/dist/studio/editors/wicked-doc/index.html +1 -1
- package/dist/studio/editors/wicked-page/editor.json +1 -1
- package/dist/studio/editors/wicked-page/index.html +1 -1
- package/dist/studio/editors/wicked-video/editor.json +1 -1
- package/dist/studio/editors/wicked-video/index.html +1 -1
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +81 -27
- package/dist/team/routes.d.ts +11 -1
- package/dist/team/routes.d.ts.map +1 -1
- package/dist/team/routes.js +58 -48
- package/dist/team/routes.js.map +1 -1
- package/endpoint-manifest.json +1 -1
- package/package.json +4 -3
- package/dist/studio/assets/index-BEFOUR6c.css +0 -32
package/dist/api/gate-cache.d.ts
CHANGED
|
@@ -25,6 +25,14 @@ export interface GateCacheEntry {
|
|
|
25
25
|
* ABSENT when the engine did not say (an older engine, a row written before the field).
|
|
26
26
|
*/
|
|
27
27
|
gateKind?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The answer tokens the engine offered (`awaitingHuman.choices`; issue #969) — e.g. a
|
|
30
|
+
* `team_dispute` gate's `['approve', 'request_changes', 'reject']`. Without them a client that
|
|
31
|
+
* missed the live frame could draw only Approve/Reject. ABSENT when the engine offered none.
|
|
32
|
+
*/
|
|
33
|
+
choices?: string[];
|
|
34
|
+
/** The index into `choices` of the engine's recommended answer. ABSENT without `choices`. */
|
|
35
|
+
recommended?: number;
|
|
28
36
|
}
|
|
29
37
|
/**
|
|
30
38
|
* Detect whether a gate prompt is a PURE sandbox/tool refusal — the worker reporting it could not
|
|
@@ -41,6 +49,41 @@ export declare function detectRefusal(text: string): {
|
|
|
41
49
|
matched: boolean;
|
|
42
50
|
reason: string;
|
|
43
51
|
} | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* The answer set an `awaitingHuman` frame offered, kept only when well-formed (issue #969):
|
|
54
|
+
* `choices` must be a non-empty array of strings, and `recommended` an integer index inside it.
|
|
55
|
+
* A malformed value is dropped rather than served — a skin would render it as buttons. Spread the
|
|
56
|
+
* result, so a gate the engine offered nothing on stays byte-identical on the wire.
|
|
57
|
+
*/
|
|
58
|
+
export declare function offeredChoices(event: CoreEvent): {
|
|
59
|
+
choices?: string[];
|
|
60
|
+
recommended?: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The open gate a run's event history records, or `undefined` — the last `awaitingHuman` not
|
|
64
|
+
* followed by a resumed/terminal event. `events` is the run's log oldest-first. Pure: the same
|
|
65
|
+
* `fold` the live stream uses, over a scratch map, so a replay cannot disagree with what the live
|
|
66
|
+
* stream would have produced from the same events (FINDING-051).
|
|
67
|
+
*/
|
|
68
|
+
export declare function replayOpenGate(runId: string, events: CoreEvent[]): GateCacheEntry | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* What one read of a run's open gate from the record yields: the gate; `null` (the record holds
|
|
71
|
+
* no open gate); `'no-log'` (this build cannot read the log, so nothing can be said);
|
|
72
|
+
* `'ambiguous'` (the engine's open gate rows disagree on the ord, so the record cannot say which
|
|
73
|
+
* gate is open — crew#1083 round 5).
|
|
74
|
+
*/
|
|
75
|
+
export type GateRead = GateCacheEntry | null | 'no-log' | 'ambiguous';
|
|
76
|
+
/**
|
|
77
|
+
* What `GateCache.resolve` yields: a read's result, or `'unstable'` — live gate events kept landing
|
|
78
|
+
* while the record was read, so nothing could be verified (crew#1083 round 3). Retryable.
|
|
79
|
+
*/
|
|
80
|
+
export type GateResolution = GateRead | 'unstable';
|
|
81
|
+
/** Narrows a resolution to an open gate: `null`, `'no-log'`, `'ambiguous'` and `'unstable'` are not one. */
|
|
82
|
+
export declare function isOpenGate(resolution: GateResolution | undefined): resolution is GateCacheEntry;
|
|
83
|
+
/** The one log call the cache makes (`resolve`): `console` by default, the daemon's logger in `server.ts`. */
|
|
84
|
+
export interface GateCacheLog {
|
|
85
|
+
warn(message: string): void;
|
|
86
|
+
}
|
|
44
87
|
/**
|
|
45
88
|
* Gate-prompt cache over a durable log (DES-STUDIO-001 §3.3).
|
|
46
89
|
*
|
|
@@ -52,8 +95,9 @@ export declare function detectRefusal(text: string): {
|
|
|
52
95
|
* prune-only, so a daemon restart dropped the map and **nothing could put an entry back**. A parked
|
|
53
96
|
* run then kept holding the operator without being able to say what it was asking. The prompt was
|
|
54
97
|
* never actually lost — core records `awaitingHuman` to the event log (core#139) — it was simply
|
|
55
|
-
* never read. `
|
|
56
|
-
* latency optimisation, not the system of
|
|
98
|
+
* never read. `resolve`, fed the route's read of the durable row and the log, is that read, which
|
|
99
|
+
* demotes this map to what it was always meant to be: a latency optimisation, not the system of
|
|
100
|
+
* record.
|
|
57
101
|
*
|
|
58
102
|
* NOTE: the full HITL lifecycle from §3.3 (advisory / hard-expiry timers, append-only message log,
|
|
59
103
|
* auto-cancel-on-expiry, synthesized `gateLifecycle` frames) is a deliberate follow-on. This
|
|
@@ -61,30 +105,67 @@ export declare function detectRefusal(text: string): {
|
|
|
61
105
|
* always `open`.
|
|
62
106
|
*/
|
|
63
107
|
export declare class GateCache {
|
|
108
|
+
private readonly log;
|
|
64
109
|
private readonly entries;
|
|
110
|
+
/**
|
|
111
|
+
* Per-run count of the LIVE gate-opening and gate-closing events `ingest` has folded. `resolve`
|
|
112
|
+
* compares it across an async read of the record: a read some event overlapped is run again,
|
|
113
|
+
* never adopted. Never pruned — one number per run seen.
|
|
114
|
+
*/
|
|
115
|
+
private readonly revisions;
|
|
116
|
+
constructor(log?: GateCacheLog);
|
|
65
117
|
/** Fold one live CoreEvent into the cache. */
|
|
66
118
|
ingest(event: CoreEvent): void;
|
|
119
|
+
private revision;
|
|
67
120
|
/**
|
|
68
|
-
*
|
|
121
|
+
* The run's open gate: the cached entry, else what `read` finds in the record — the durable row,
|
|
122
|
+
* the log — adopted and returned once a read completes with no live gate event of the run's
|
|
123
|
+
* having landed meanwhile. `read` must not consult this cache: it is run again when one does.
|
|
124
|
+
*
|
|
125
|
+
* Neither side of such an overlap is known to be the newer (crew#1083 review). The record may
|
|
126
|
+
* have moved on since the read began — a live `awaitingHuman` for the next gate while an older
|
|
127
|
+
* row was being read — or the engine's pump may have delivered an OLDER frame late: an
|
|
128
|
+
* `awaitingHuman` for a gate the row already shows answered, or that gate's `resumed`. The
|
|
129
|
+
* revision counts arrivals, not order, so the cache cannot pick a winner; the first cut let the
|
|
130
|
+
* live side win and served a late ord-3 frame over a current ord-4 row, which let a decision
|
|
131
|
+
* naming ord 3 pass the gate check and answer ord 4. So no winner is picked. The read is run
|
|
132
|
+
* again, from scratch: the engine writes the row with the pause, so a re-read begun after the
|
|
133
|
+
* overlapping frame arrived reflects whatever that frame announced, and the first read that
|
|
134
|
+
* completes with nothing having landed is adopted.
|
|
69
135
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
136
|
+
* A clean read is the record's word in BOTH directions (crew#1083 round 3): a gate it finds
|
|
137
|
+
* replaces whatever an overlapped `ingest` folded meanwhile, and "no open gate" / "cannot read"
|
|
138
|
+
* EVICTS it — otherwise a late frame folded during attempt one outlived the clean attempt two
|
|
139
|
+
* that found its gate already answered, and was served as a cache hit, unread, from then on. A
|
|
140
|
+
* read that throws evicts the same way. After `RESOLVE_ATTEMPTS` overlapped reads nothing was
|
|
141
|
+
* verified, so nothing is served: the entry is evicted and the result is `'unstable'`, never the
|
|
142
|
+
* unverified entry — an ordinary ordered backlog (`awaitingHuman(2)`, `resumed(2)`,
|
|
143
|
+
* `awaitingHuman(3)`, one landing per read) left the cache holding ord 3 while every read found
|
|
144
|
+
* ord 4. The run is logged once.
|
|
72
145
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
146
|
+
* One read in flight per run (agy, round 4): while a read is running, an entry a live frame
|
|
147
|
+
* folds is unverified, so a concurrent `resolve` awaits the read in flight instead of taking the
|
|
148
|
+
* entry as a hit, and the entry is evicted before a re-read so a `get` meanwhile serves nothing
|
|
149
|
+
* unverified either.
|
|
150
|
+
*/
|
|
151
|
+
resolve(runId: string, read: () => Promise<GateRead>): Promise<GateResolution>;
|
|
152
|
+
/** In-flight resolutions per run: `resolve` hands every concurrent caller the same one. */
|
|
153
|
+
private readonly inflight;
|
|
154
|
+
private hydrate;
|
|
155
|
+
/**
|
|
156
|
+
* The cached open gate for a run, if any — and nothing while a read of it is in flight: an entry
|
|
157
|
+
* a live frame folded meanwhile is unverified until that read settles, so a sync caller (the GET
|
|
158
|
+
* route's hit path, a steering prompt) falls through to `resolve` and waits (agy, round 4).
|
|
78
159
|
*/
|
|
79
|
-
rebuild(runId: string, events: CoreEvent[]): GateCacheEntry | undefined;
|
|
80
|
-
/** The cached open gate for a run, if any. */
|
|
81
160
|
get(runId: string): GateCacheEntry | undefined;
|
|
82
161
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
162
|
+
* Drop the run's cached gate: the engine's record shows another gate open, or none (crew#1083
|
|
163
|
+
* round 7). The next read hydrates from the record.
|
|
164
|
+
*/
|
|
165
|
+
forget(runId: string): void;
|
|
166
|
+
/**
|
|
167
|
+
* Seed the run's gate synchronously. Nothing can land between the caller's decision and this
|
|
168
|
+
* set, so it needs no guard; a read that awaited anything goes through `resolve`.
|
|
88
169
|
*/
|
|
89
170
|
adopt(runId: string, entry: GateCacheEntry): void;
|
|
90
171
|
/** Drop cache entries whose run is no longer `awaiting_human` (self-healing on reconcile). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-cache.d.ts","sourceRoot":"","sources":["../../src/api/gate-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/D,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"gate-cache.d.ts","sourceRoot":"","sources":["../../src/api/gate-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/D,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAoB5F;AAcD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAM7F;AA4CD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,GAAG,SAAS,CAI7F;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnD,4GAA4G;AAC5G,wBAAgB,UAAU,CAAC,UAAU,EAAE,cAAc,GAAG,SAAS,GAAG,UAAU,IAAI,cAAc,CAE/F;AAED,8GAA8G;AAC9G,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,SAAS;IASR,OAAO,CAAC,QAAQ,CAAC,GAAG;IARhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAE1B,GAAG,GAAE,YAAsB;IAExD,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAO9B,OAAO,CAAC,QAAQ;IAIhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAYpF,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8C;YAEzD,OAAO;IA0BrB;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI9C;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,IAAI;IAIjD,8FAA8F;IAC9F,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,IAAI;CAQtC"}
|
package/dist/api/gate-cache.js
CHANGED
|
@@ -41,6 +41,20 @@ export function detectRefusal(text) {
|
|
|
41
41
|
function observedAt(event) {
|
|
42
42
|
return new Date(typeof event.ts === 'number' ? event.ts : Date.now()).toISOString();
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The answer set an `awaitingHuman` frame offered, kept only when well-formed (issue #969):
|
|
46
|
+
* `choices` must be a non-empty array of strings, and `recommended` an integer index inside it.
|
|
47
|
+
* A malformed value is dropped rather than served — a skin would render it as buttons. Spread the
|
|
48
|
+
* result, so a gate the engine offered nothing on stays byte-identical on the wire.
|
|
49
|
+
*/
|
|
50
|
+
export function offeredChoices(event) {
|
|
51
|
+
const choices = event['choices'];
|
|
52
|
+
if (!Array.isArray(choices) || choices.length === 0 || !choices.every((c) => typeof c === 'string'))
|
|
53
|
+
return {};
|
|
54
|
+
const recommended = event['recommended'];
|
|
55
|
+
const inBounds = Number.isInteger(recommended) && recommended >= 0 && recommended < choices.length;
|
|
56
|
+
return { choices: [...choices], ...(inBounds ? { recommended: recommended } : {}) };
|
|
57
|
+
}
|
|
44
58
|
/**
|
|
45
59
|
* The one rule that turns an event into a cache mutation.
|
|
46
60
|
*
|
|
@@ -49,6 +63,8 @@ function observedAt(event) {
|
|
|
49
63
|
* free to drift, and the drift would only ever show up after a restart — the exact condition nobody
|
|
50
64
|
* exercises.
|
|
51
65
|
*/
|
|
66
|
+
/** The event types `fold` acts on — the ones that open or close a run's gate. */
|
|
67
|
+
const GATE_EVENTS = new Set(['awaitingHuman', 'resumed', 'sessionCompleted', 'runCancelled', 'sessionFailed']);
|
|
52
68
|
function fold(entries, event) {
|
|
53
69
|
const session = typeof event.session === 'string' ? event.session : undefined;
|
|
54
70
|
if (session === undefined)
|
|
@@ -66,6 +82,7 @@ function fold(entries, event) {
|
|
|
66
82
|
// Spread only when matched, so a normal gate's entry is byte-identical (no `refusal` key).
|
|
67
83
|
...(refusal !== undefined ? { refusal } : {}),
|
|
68
84
|
...(gateKind !== undefined ? { gateKind } : {}),
|
|
85
|
+
...offeredChoices(event),
|
|
69
86
|
});
|
|
70
87
|
}
|
|
71
88
|
break;
|
|
@@ -79,6 +96,24 @@ function fold(entries, event) {
|
|
|
79
96
|
break;
|
|
80
97
|
}
|
|
81
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The open gate a run's event history records, or `undefined` — the last `awaitingHuman` not
|
|
101
|
+
* followed by a resumed/terminal event. `events` is the run's log oldest-first. Pure: the same
|
|
102
|
+
* `fold` the live stream uses, over a scratch map, so a replay cannot disagree with what the live
|
|
103
|
+
* stream would have produced from the same events (FINDING-051).
|
|
104
|
+
*/
|
|
105
|
+
export function replayOpenGate(runId, events) {
|
|
106
|
+
const replayed = new Map();
|
|
107
|
+
for (const event of events)
|
|
108
|
+
fold(replayed, event);
|
|
109
|
+
return replayed.get(runId);
|
|
110
|
+
}
|
|
111
|
+
/** Narrows a resolution to an open gate: `null`, `'no-log'`, `'ambiguous'` and `'unstable'` are not one. */
|
|
112
|
+
export function isOpenGate(resolution) {
|
|
113
|
+
return typeof resolution === 'object' && resolution !== null;
|
|
114
|
+
}
|
|
115
|
+
/** How many times `resolve` runs a read that a live gate event overlapped before it stops trying. */
|
|
116
|
+
const RESOLVE_ATTEMPTS = 3;
|
|
82
117
|
/**
|
|
83
118
|
* Gate-prompt cache over a durable log (DES-STUDIO-001 §3.3).
|
|
84
119
|
*
|
|
@@ -90,8 +125,9 @@ function fold(entries, event) {
|
|
|
90
125
|
* prune-only, so a daemon restart dropped the map and **nothing could put an entry back**. A parked
|
|
91
126
|
* run then kept holding the operator without being able to say what it was asking. The prompt was
|
|
92
127
|
* never actually lost — core records `awaitingHuman` to the event log (core#139) — it was simply
|
|
93
|
-
* never read. `
|
|
94
|
-
* latency optimisation, not the system of
|
|
128
|
+
* never read. `resolve`, fed the route's read of the durable row and the log, is that read, which
|
|
129
|
+
* demotes this map to what it was always meant to be: a latency optimisation, not the system of
|
|
130
|
+
* record.
|
|
95
131
|
*
|
|
96
132
|
* NOTE: the full HITL lifecycle from §3.3 (advisory / hard-expiry timers, append-only message log,
|
|
97
133
|
* auto-cancel-on-expiry, synthesized `gateLifecycle` frames) is a deliberate follow-on. This
|
|
@@ -99,42 +135,117 @@ function fold(entries, event) {
|
|
|
99
135
|
* always `open`.
|
|
100
136
|
*/
|
|
101
137
|
export class GateCache {
|
|
138
|
+
log;
|
|
102
139
|
entries = new Map();
|
|
140
|
+
/**
|
|
141
|
+
* Per-run count of the LIVE gate-opening and gate-closing events `ingest` has folded. `resolve`
|
|
142
|
+
* compares it across an async read of the record: a read some event overlapped is run again,
|
|
143
|
+
* never adopted. Never pruned — one number per run seen.
|
|
144
|
+
*/
|
|
145
|
+
revisions = new Map();
|
|
146
|
+
constructor(log = console) {
|
|
147
|
+
this.log = log;
|
|
148
|
+
}
|
|
103
149
|
/** Fold one live CoreEvent into the cache. */
|
|
104
150
|
ingest(event) {
|
|
151
|
+
if (typeof event.session === 'string' && GATE_EVENTS.has(event.type)) {
|
|
152
|
+
this.revisions.set(event.session, this.revision(event.session) + 1);
|
|
153
|
+
}
|
|
105
154
|
fold(this.entries, event);
|
|
106
155
|
}
|
|
156
|
+
revision(runId) {
|
|
157
|
+
return this.revisions.get(runId) ?? 0;
|
|
158
|
+
}
|
|
107
159
|
/**
|
|
108
|
-
*
|
|
160
|
+
* The run's open gate: the cached entry, else what `read` finds in the record — the durable row,
|
|
161
|
+
* the log — adopted and returned once a read completes with no live gate event of the run's
|
|
162
|
+
* having landed meanwhile. `read` must not consult this cache: it is run again when one does.
|
|
109
163
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
164
|
+
* Neither side of such an overlap is known to be the newer (crew#1083 review). The record may
|
|
165
|
+
* have moved on since the read began — a live `awaitingHuman` for the next gate while an older
|
|
166
|
+
* row was being read — or the engine's pump may have delivered an OLDER frame late: an
|
|
167
|
+
* `awaitingHuman` for a gate the row already shows answered, or that gate's `resumed`. The
|
|
168
|
+
* revision counts arrivals, not order, so the cache cannot pick a winner; the first cut let the
|
|
169
|
+
* live side win and served a late ord-3 frame over a current ord-4 row, which let a decision
|
|
170
|
+
* naming ord 3 pass the gate check and answer ord 4. So no winner is picked. The read is run
|
|
171
|
+
* again, from scratch: the engine writes the row with the pause, so a re-read begun after the
|
|
172
|
+
* overlapping frame arrived reflects whatever that frame announced, and the first read that
|
|
173
|
+
* completes with nothing having landed is adopted.
|
|
112
174
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
175
|
+
* A clean read is the record's word in BOTH directions (crew#1083 round 3): a gate it finds
|
|
176
|
+
* replaces whatever an overlapped `ingest` folded meanwhile, and "no open gate" / "cannot read"
|
|
177
|
+
* EVICTS it — otherwise a late frame folded during attempt one outlived the clean attempt two
|
|
178
|
+
* that found its gate already answered, and was served as a cache hit, unread, from then on. A
|
|
179
|
+
* read that throws evicts the same way. After `RESOLVE_ATTEMPTS` overlapped reads nothing was
|
|
180
|
+
* verified, so nothing is served: the entry is evicted and the result is `'unstable'`, never the
|
|
181
|
+
* unverified entry — an ordinary ordered backlog (`awaitingHuman(2)`, `resumed(2)`,
|
|
182
|
+
* `awaitingHuman(3)`, one landing per read) left the cache holding ord 3 while every read found
|
|
183
|
+
* ord 4. The run is logged once.
|
|
184
|
+
*
|
|
185
|
+
* One read in flight per run (agy, round 4): while a read is running, an entry a live frame
|
|
186
|
+
* folds is unverified, so a concurrent `resolve` awaits the read in flight instead of taking the
|
|
187
|
+
* entry as a hit, and the entry is evicted before a re-read so a `get` meanwhile serves nothing
|
|
188
|
+
* unverified either.
|
|
118
189
|
*/
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
190
|
+
async resolve(runId, read) {
|
|
191
|
+
const pending = this.inflight.get(runId);
|
|
192
|
+
if (pending !== undefined)
|
|
193
|
+
return pending;
|
|
194
|
+
const cached = this.entries.get(runId);
|
|
195
|
+
if (cached)
|
|
196
|
+
return cached;
|
|
197
|
+
const resolution = this.hydrate(runId, read).finally(() => {
|
|
198
|
+
this.inflight.delete(runId);
|
|
199
|
+
});
|
|
200
|
+
this.inflight.set(runId, resolution);
|
|
201
|
+
return resolution;
|
|
202
|
+
}
|
|
203
|
+
/** In-flight resolutions per run: `resolve` hands every concurrent caller the same one. */
|
|
204
|
+
inflight = new Map();
|
|
205
|
+
async hydrate(runId, read) {
|
|
206
|
+
for (let attempt = 1; attempt <= RESOLVE_ATTEMPTS; attempt++) {
|
|
207
|
+
const seen = this.revision(runId);
|
|
208
|
+
let found;
|
|
209
|
+
try {
|
|
210
|
+
found = await read();
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
this.entries.delete(runId);
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
if (this.revision(runId) !== seen) {
|
|
217
|
+
// Whatever a live frame folded during this read is unverified until the re-read settles.
|
|
218
|
+
this.entries.delete(runId);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (isOpenGate(found))
|
|
222
|
+
this.entries.set(runId, found);
|
|
223
|
+
else
|
|
224
|
+
this.entries.delete(runId);
|
|
225
|
+
return found;
|
|
226
|
+
}
|
|
227
|
+
this.entries.delete(runId);
|
|
228
|
+
this.log.warn(`gate cache: live gate events for run ${runId} overlapped ${RESOLVE_ATTEMPTS} reads of its open gate; nothing verified, serving no gate`);
|
|
229
|
+
return 'unstable';
|
|
127
230
|
}
|
|
128
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* The cached open gate for a run, if any — and nothing while a read of it is in flight: an entry
|
|
233
|
+
* a live frame folded meanwhile is unverified until that read settles, so a sync caller (the GET
|
|
234
|
+
* route's hit path, a steering prompt) falls through to `resolve` and waits (agy, round 4).
|
|
235
|
+
*/
|
|
129
236
|
get(runId) {
|
|
130
|
-
return this.entries.get(runId);
|
|
237
|
+
return this.inflight.has(runId) ? undefined : this.entries.get(runId);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Drop the run's cached gate: the engine's record shows another gate open, or none (crew#1083
|
|
241
|
+
* round 7). The next read hydrates from the record.
|
|
242
|
+
*/
|
|
243
|
+
forget(runId) {
|
|
244
|
+
this.entries.delete(runId);
|
|
131
245
|
}
|
|
132
246
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* latency layer over durable truth: the route reads the table on a miss and parks the result
|
|
136
|
-
* here so the next poll is a map hit. `rebuild` (event-log replay) remains the fallback for
|
|
137
|
-
* engines predating the table.
|
|
247
|
+
* Seed the run's gate synchronously. Nothing can land between the caller's decision and this
|
|
248
|
+
* set, so it needs no guard; a read that awaited anything goes through `resolve`.
|
|
138
249
|
*/
|
|
139
250
|
adopt(runId, entry) {
|
|
140
251
|
this.entries.set(runId, entry);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-cache.js","sourceRoot":"","sources":["../../src/api/gate-cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gate-cache.js","sourceRoot":"","sources":["../../src/api/gate-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/api/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/api/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/api/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,KAAK,EAAyF,MAAM,kBAAkB,CAAC;AAErI,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAkC,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAKlE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAuB3C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;IAc9B;;;;;OAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CH,CAAC;AACL,eAAO,MAAM,mBAAmB;;IAG5B,+FAA+F;;IAE/F,0FAA0F;;;;;;;;;;EAGnF,CAAC;AACZ,eAAO,MAAM,oBAAoB;;;;;;EAA8C,CAAC;AAChF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAG8F,CAAC;AAC9H,eAAO,MAAM,kBAAkB;;;;;;EAA4C,CAAC;AAE5E,eAAO,MAAM,aAAa;;;;;;;;;;;;EAMf,CAAC;AACZ,eAAO,MAAM,cAAc;;;;;;EAA2D,CAAC;AACvF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUiF,CAAC;AACrH,eAAO,MAAM,iBAAiB;;;;;;EAA6D,CAAC;AAC5F,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAOrB,CAAC;AAEZ,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;IACrE,qGAAqG;IACrG,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,yFAAyF;IACzF,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,KAAK,CAAC;CACzC;AAiED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAyYhF"}
|
package/dist/api/mcp.js
CHANGED
|
@@ -276,6 +276,9 @@ export function registerMcpRoutes(app, deps) {
|
|
|
276
276
|
config: { manifest: { statusCodes: [405] } },
|
|
277
277
|
handler: async (_req, reply) => reply.code(405).header('Allow', 'POST').send({ error: 'method_not_allowed' }),
|
|
278
278
|
});
|
|
279
|
+
// (crew#993) A withdraw-then-change of one server's registration runs under the policies' lock
|
|
280
|
+
// for that server, the one approve and revoke take. With no policies there are no approvals to race.
|
|
281
|
+
const exclusive = (name, fn) => (deps.policies === undefined ? fn() : deps.policies.exclusive(name, fn));
|
|
279
282
|
const registry = (reply) => {
|
|
280
283
|
if (deps.registry !== undefined)
|
|
281
284
|
return deps.registry;
|
|
@@ -342,16 +345,22 @@ export function registerMcpRoutes(app, deps) {
|
|
|
342
345
|
await r.checkEgress(parsed.data.previewHash, allowOrigin);
|
|
343
346
|
}
|
|
344
347
|
const held = parsed.data.previewHash === undefined ? null : r.peekPreview(parsed.data.previewHash);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
const commit = async () => {
|
|
349
|
+
if (held !== null && deps.policies !== undefined) {
|
|
350
|
+
const tokens = await deps.policies.withdrawnBySave(held);
|
|
351
|
+
if (tokens.length > 0) {
|
|
352
|
+
const rules = await deps.policies.withdraw(tokens).catch((e) => { auditPartial(deps, req, e, tokens); throw e; });
|
|
353
|
+
deps.audit.record('mcp.approval.withdrawn', deps.actorOf(req), { detail: { subjects: tokens, rules, why: 'schema_changed' } });
|
|
354
|
+
for (const id of rules)
|
|
355
|
+
deps.audit.record('governance.rule.upserted', deps.actorOf(req), { detail: { id, source: 'mcp-approval' } });
|
|
356
|
+
}
|
|
352
357
|
}
|
|
353
|
-
|
|
354
|
-
|
|
358
|
+
return r.save(parsed.data.previewHash, enabledWrites, allowOrigin);
|
|
359
|
+
};
|
|
360
|
+
// (crew#993) The withdrawal and the save are one step for the server: an approval for it
|
|
361
|
+
// waits until the save commits, then is judged against the new registration. With no held
|
|
362
|
+
// preview the save refuses (409) and nothing is withdrawn, so there is nothing to serialize.
|
|
363
|
+
const server = held === null ? await commit() : await exclusive(held.name, commit);
|
|
355
364
|
deps.audit.record('mcp.server.saved', deps.actorOf(req), {
|
|
356
365
|
detail: {
|
|
357
366
|
name: server.name,
|
|
@@ -400,17 +409,21 @@ export function registerMcpRoutes(app, deps) {
|
|
|
400
409
|
if (r === null)
|
|
401
410
|
return reply;
|
|
402
411
|
try {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
deps.
|
|
409
|
-
|
|
410
|
-
deps.
|
|
412
|
+
// (crew#993) One step for the server, like the save: no approval lands between the
|
|
413
|
+
// withdrawal and the removal.
|
|
414
|
+
await exclusive(req.params.name, async () => {
|
|
415
|
+
await r.get(req.params.name); // 404 before anything is withdrawn
|
|
416
|
+
if (deps.policies !== undefined) {
|
|
417
|
+
const tokens = await deps.policies.tokensOf(req.params.name);
|
|
418
|
+
if (tokens.length > 0) {
|
|
419
|
+
const rules = await deps.policies.withdraw(tokens).catch((e) => { auditPartial(deps, req, e, tokens); throw e; });
|
|
420
|
+
deps.audit.record('mcp.approval.withdrawn', deps.actorOf(req), { detail: { subjects: tokens, rules, why: 'server_removed' } });
|
|
421
|
+
for (const id of rules)
|
|
422
|
+
deps.audit.record('governance.rule.upserted', deps.actorOf(req), { detail: { id, source: 'mcp-approval' } });
|
|
423
|
+
}
|
|
411
424
|
}
|
|
412
|
-
|
|
413
|
-
|
|
425
|
+
await r.remove(req.params.name);
|
|
426
|
+
});
|
|
414
427
|
deps.audit.record('mcp.server.removed', deps.actorOf(req), { detail: { name: req.params.name } });
|
|
415
428
|
return { removed: req.params.name };
|
|
416
429
|
}
|