@lumenflow/cli 5.1.0 → 5.2.1
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/capacity-snapshot-emitter.js +10 -12
- package/dist/capacity-snapshot-emitter.js.map +1 -1
- package/dist/docs-generate-pack-reference.js +54 -0
- package/dist/docs-generate-pack-reference.js.map +1 -1
- package/dist/flow-bottlenecks.js +55 -0
- package/dist/flow-bottlenecks.js.map +1 -1
- package/dist/kernel-event-sync/lifecycle-emitters.js +13 -14
- package/dist/kernel-event-sync/lifecycle-emitters.js.map +1 -1
- package/dist/kernel-event-sync/narrow-emissions.js +7 -4
- package/dist/kernel-event-sync/narrow-emissions.js.map +1 -1
- package/dist/kernel-event-sync/software-delivery-emitters.js +91 -0
- package/dist/kernel-event-sync/software-delivery-emitters.js.map +1 -1
- package/dist/lumenflow-upgrade.js +39 -10
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/metrics-snapshot.js +51 -0
- package/dist/metrics-snapshot.js.map +1 -1
- package/dist/orchestrate-init-status.js +24 -11
- package/dist/orchestrate-init-status.js.map +1 -1
- package/dist/pack-install.js +27 -20
- package/dist/pack-install.js.map +1 -1
- package/dist/pack-publish.js +34 -26
- package/dist/pack-publish.js.map +1 -1
- package/dist/release.js +308 -149
- package/dist/release.js.map +1 -1
- package/dist/strict-progress.js +49 -0
- package/dist/strict-progress.js.map +1 -1
- package/dist/temp-dir-cleanup.js +112 -0
- package/dist/temp-dir-cleanup.js.map +1 -0
- package/dist/validate-agent-sync.js +6 -5
- package/dist/validate-agent-sync.js.map +1 -1
- package/dist/wu-escalate.js +33 -0
- package/dist/wu-escalate.js.map +1 -1
- package/dist/wu-preflight.js +28 -0
- package/dist/wu-preflight.js.map +1 -1
- package/dist/wu-recover.js +35 -0
- package/dist/wu-recover.js.map +1 -1
- package/dist/wu-spawn-strategy-resolver.js +4 -0
- package/dist/wu-spawn-strategy-resolver.js.map +1 -1
- package/dist/wu-validate.js +63 -0
- package/dist/wu-validate.js.map +1 -1
- package/package.json +11 -11
- package/packs/agent-runtime/manifest.ts +55 -4
- package/packs/agent-runtime/manifest.yaml +16 -6
- package/packs/agent-runtime/orchestration.ts +26 -1
- package/packs/agent-runtime/package.json +1 -1
- package/packs/agent-runtime/remote-controls/operations.ts +6 -0
- package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +4 -0
- package/packs/agent-runtime/turn-lifecycle-events.ts +90 -1
- package/packs/sidekick/manifest.yaml +6 -0
- package/packs/sidekick/package.json +2 -1
- package/packs/sidekick/sidekick-events.ts +195 -18
- package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +18 -10
- package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +4 -0
- package/packs/sidekick/src/domain/channel.types.ts +34 -54
- package/packs/sidekick/src/ports/channel-bridge.port.ts +29 -12
- package/packs/sidekick/tool-impl/channel-tools.ts +47 -16
- package/packs/sidekick/tool-impl/system-tools.ts +4 -6
- package/packs/software-delivery/manifest.ts +94 -7
- package/packs/software-delivery/manifest.yaml +42 -5
- package/packs/software-delivery/package.json +1 -1
- package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +30 -0
|
@@ -2,23 +2,37 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* WU-2735 (INIT-060 WU-7a
|
|
6
|
-
*
|
|
5
|
+
* WU-2735 (INIT-060 WU-7a) + WU-2831 (INIT-062 WU-E):
|
|
6
|
+
* Domain types for the sidekick ChannelBridge adapters.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* As of WU-2831 the port-level identity, config, and send-result shapes are
|
|
9
|
+
* canonical in @lumenflow/conductor-sdk (Apache-2.0). This file re-exports
|
|
10
|
+
* those types so sidekick adapters implement the neutral port without
|
|
11
|
+
* parallel contract drift.
|
|
12
|
+
*
|
|
13
|
+
* The internal `EnvelopeKind` + `ChannelEnvelope` shape below remains the
|
|
14
|
+
* sidekick-pack wire format: it predates the canonical port and is retained
|
|
15
|
+
* for on-disk/control-plane compatibility of existing adapters. New
|
|
16
|
+
* implementers (e.g. the cloud phone bridge) SHOULD consume
|
|
17
|
+
* `@lumenflow/conductor-sdk`'s `ChannelEnvelope` instead.
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// Re-export canonical port types from the Apache-2.0 conductor SDK.
|
|
21
|
+
export type {
|
|
22
|
+
BackpressurePolicy,
|
|
23
|
+
BridgeConfig,
|
|
24
|
+
ChannelId,
|
|
25
|
+
ChannelIdentity,
|
|
26
|
+
DeliveryGuarantee,
|
|
27
|
+
IdentityResolver,
|
|
28
|
+
SendResult,
|
|
29
|
+
} from '@lumenflow/conductor-sdk';
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
|
-
* Envelope dispatch classification
|
|
32
|
+
* Envelope dispatch classification — sidekick-internal wire format.
|
|
33
|
+
*
|
|
34
|
+
* Predates the canonical `BackpressurePolicy` enum in conductor-sdk.
|
|
35
|
+
* Maps: `ephemeral` ↔ `ephemeral`, `queue` ↔ `buffered` (conductor-sdk).
|
|
22
36
|
*
|
|
23
37
|
* - `ephemeral` — observational; may fail-silent when transport is down.
|
|
24
38
|
* - `queue` — commands / approvals; must be queued and replayed on reconnect.
|
|
@@ -26,14 +40,18 @@ export type ChannelId = string & { readonly __brand: 'sidekick.ChannelId' };
|
|
|
26
40
|
export type EnvelopeKind = 'ephemeral' | 'queue';
|
|
27
41
|
|
|
28
42
|
/**
|
|
29
|
-
* A channel envelope
|
|
30
|
-
*
|
|
31
|
-
* without snooping the body.
|
|
43
|
+
* A channel envelope (sidekick-internal wire format).
|
|
44
|
+
* Transport-agnostic: `body` is an opaque JSON-serialisable payload,
|
|
45
|
+
* `content_type` labels it so receivers can dispatch without snooping the body.
|
|
46
|
+
*
|
|
47
|
+
* Note: the canonical port-level envelope is
|
|
48
|
+
* `@lumenflow/conductor-sdk`'s `ChannelEnvelope` (uses `policy` field).
|
|
49
|
+
* Adapters translate between the two when bridging across the Apache boundary.
|
|
32
50
|
*/
|
|
33
51
|
export interface ChannelEnvelope {
|
|
34
52
|
/** Unique id per envelope (content-hash or uuid — chosen by the emitter). */
|
|
35
53
|
id: string;
|
|
36
|
-
/**
|
|
54
|
+
/** Pack-internal backpressure classification. */
|
|
37
55
|
kind: EnvelopeKind;
|
|
38
56
|
/** MIME-like content descriptor, e.g. `application/json`, `text/plain`. */
|
|
39
57
|
content_type: string;
|
|
@@ -44,41 +62,3 @@ export interface ChannelEnvelope {
|
|
|
44
62
|
/** Optional emitter-supplied metadata (trace ids, correlation keys). */
|
|
45
63
|
metadata?: Readonly<Record<string, unknown>>;
|
|
46
64
|
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Bridge registration config — identity under which a channel is opened.
|
|
50
|
-
*
|
|
51
|
-
* The (`provider`, `name`) pair is the canonical identity: re-registering the
|
|
52
|
-
* same pair returns the same `ChannelId` (ADR-013 §ChannelBridge contract
|
|
53
|
-
* rule #3 — idempotent register).
|
|
54
|
-
*
|
|
55
|
-
* `options` is provider-specific bag. The port does NOT inspect it; adapters
|
|
56
|
-
* interpret.
|
|
57
|
-
*/
|
|
58
|
-
export interface BridgeConfig {
|
|
59
|
-
/** Provider slug — e.g. `filesystem`, `control-plane`. */
|
|
60
|
-
provider: string;
|
|
61
|
-
/** Human-readable channel name scoped within the provider. */
|
|
62
|
-
name: string;
|
|
63
|
-
/** Provider-specific options (paths, tokens, etc.). Opaque to the port. */
|
|
64
|
-
options?: Readonly<Record<string, unknown>>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Result of a `send` attempt. `accepted` reflects the port contract: for
|
|
69
|
-
* ephemeral envelopes with an unreachable transport, adapters may return
|
|
70
|
-
* `{ accepted: false, reason: '...' }` rather than throwing (§4 fail-silent).
|
|
71
|
-
*/
|
|
72
|
-
export interface SendResult {
|
|
73
|
-
accepted: boolean;
|
|
74
|
-
/** Transport-assigned id when the sink confirms receipt (optional). */
|
|
75
|
-
delivery_id?: string;
|
|
76
|
-
/** Reason when `accepted === false` (not meant for programmatic handling). */
|
|
77
|
-
reason?: string;
|
|
78
|
-
/**
|
|
79
|
-
* `true` when the sink recognised `envelope.id` on a registered channel as
|
|
80
|
-
* an at-least-once replay (WU-2737 §Idempotency; cloud contract surface).
|
|
81
|
-
* Adapters that cannot observe dedup SHOULD omit this field.
|
|
82
|
-
*/
|
|
83
|
-
deduped?: boolean;
|
|
84
|
-
}
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* WU-2735 (INIT-060 WU-7a, ADR-013 §ChannelBridge):
|
|
6
|
-
*
|
|
5
|
+
* WU-2735 (INIT-060 WU-7a, ADR-013 §ChannelBridge) + WU-2831 (INIT-062 WU-E):
|
|
6
|
+
*
|
|
7
|
+
* Sidekick-pack ChannelBridge port.
|
|
8
|
+
*
|
|
9
|
+
* As of WU-2831 the canonical neutral port lives in `@lumenflow/conductor-sdk`
|
|
10
|
+
* under Apache-2.0 — consumers that need an AGPL-free surface (e.g. the
|
|
11
|
+
* cloud phone bridge) MUST import from there. The sidekick port below reuses
|
|
12
|
+
* the conductor-sdk identity / config / result types (no parallel drift) but
|
|
13
|
+
* keeps the pack-internal wire-format envelope (`ChannelEnvelope` with
|
|
14
|
+
* `kind: 'ephemeral' | 'queue'`) for on-disk and control-plane compatibility
|
|
15
|
+
* of existing adapters.
|
|
7
16
|
*
|
|
8
17
|
* Port contract — load-bearing across adapters (ADR-013 §ChannelBridge):
|
|
9
18
|
*
|
|
@@ -11,14 +20,12 @@
|
|
|
11
20
|
* `ephemeral` envelopes (ADR-013 §4 backpressure split).
|
|
12
21
|
* - `receive` yields envelopes in emit order on a single channel (§3). No
|
|
13
22
|
* cross-channel ordering guarantee.
|
|
14
|
-
* - `register` is idempotent on `BridgeConfig` identity —
|
|
15
|
-
* `(provider, name)` returns the same `ChannelId`.
|
|
23
|
+
* - `connect` (alias: `register`) is idempotent on `BridgeConfig` identity —
|
|
24
|
+
* same `(provider, name)` returns the same `ChannelId`.
|
|
16
25
|
* - `disconnect` flushes queued envelopes before closing; in-flight ephemerals
|
|
17
26
|
* may drop.
|
|
18
27
|
*
|
|
19
|
-
* The port MUST NOT leak filesystem- or network-specific types.
|
|
20
|
-
* filesystem adapter (this WU) and the cloud adapter (WU-2737) implement this
|
|
21
|
-
* interface unmodified.
|
|
28
|
+
* The port MUST NOT leak filesystem- or network-specific types.
|
|
22
29
|
*/
|
|
23
30
|
|
|
24
31
|
import type {
|
|
@@ -29,6 +36,21 @@ import type {
|
|
|
29
36
|
} from '../domain/channel.types.js';
|
|
30
37
|
|
|
31
38
|
export interface ChannelBridge {
|
|
39
|
+
/**
|
|
40
|
+
* Open (or reuse) a channel. Idempotent on `(provider, name)`.
|
|
41
|
+
*
|
|
42
|
+
* Canonical method name per the neutral conductor-sdk port (WU-2831).
|
|
43
|
+
*/
|
|
44
|
+
connect(bridgeConfig: BridgeConfig): Promise<ChannelId>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Legacy alias for `connect`. Retained for pack-internal call sites that
|
|
48
|
+
* predate WU-2831.
|
|
49
|
+
*
|
|
50
|
+
* @deprecated Use `connect` instead (WU-2831 port-contract alignment).
|
|
51
|
+
*/
|
|
52
|
+
register(bridgeConfig: BridgeConfig): Promise<ChannelId>;
|
|
53
|
+
|
|
32
54
|
/**
|
|
33
55
|
* Send an envelope on a registered channel.
|
|
34
56
|
*
|
|
@@ -42,11 +64,6 @@ export interface ChannelBridge {
|
|
|
42
64
|
*/
|
|
43
65
|
receive(channelId: ChannelId): AsyncIterable<ChannelEnvelope>;
|
|
44
66
|
|
|
45
|
-
/**
|
|
46
|
-
* Register a channel and return its id. Idempotent on `(provider, name)`.
|
|
47
|
-
*/
|
|
48
|
-
register(bridgeConfig: BridgeConfig): Promise<ChannelId>;
|
|
49
|
-
|
|
50
67
|
/**
|
|
51
68
|
* Close the channel. Queued envelopes flush before this resolves; ephemerals
|
|
52
69
|
* in flight may be dropped.
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
buildChannelMessageReceivedEvent,
|
|
23
23
|
buildChannelMessageSentEvent,
|
|
24
24
|
emitSidekickEvent,
|
|
25
|
-
serializeLocalChannelMessage,
|
|
26
25
|
} from '../sidekick-events.js';
|
|
27
26
|
|
|
28
27
|
// ---------------------------------------------------------------------------
|
|
@@ -300,16 +299,18 @@ async function channelSendViaTransport(
|
|
|
300
299
|
};
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
302
|
+
// WU-2830 (INIT-062 WU-D): ChannelMessageSentEvent.message is a typed
|
|
303
|
+
// ChannelMessageRecord. Provider sends do not have a local storage row,
|
|
304
|
+
// so we synthesize a record using the external id when available.
|
|
305
|
+
const providerSentMessage: ChannelMessageRecord = {
|
|
306
|
+
id: transportResult.externalMessageId ?? createId('msg'),
|
|
307
|
+
channel_id: `${provider}:${channel}`,
|
|
308
|
+
sender: 'assistant',
|
|
309
|
+
content,
|
|
310
|
+
created_at: nowIso(),
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
await emitSidekickEvent(buildChannelMessageSentEvent(providerSentMessage));
|
|
313
314
|
|
|
314
315
|
return success(outputData);
|
|
315
316
|
}
|
|
@@ -389,9 +390,8 @@ async function channelSendTool(input: unknown, context?: ToolContextLike): Promi
|
|
|
389
390
|
);
|
|
390
391
|
});
|
|
391
392
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
);
|
|
393
|
+
// WU-2830: pass the typed ChannelMessageRecord directly.
|
|
394
|
+
await emitSidekickEvent(buildChannelMessageSentEvent(resolvedMessage));
|
|
395
395
|
|
|
396
396
|
return success({ message: resolvedMessage as unknown as Record<string, unknown> });
|
|
397
397
|
}
|
|
@@ -462,17 +462,47 @@ async function channelReceiveViaTransport(
|
|
|
462
462
|
};
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
// WU-2830: ChannelMessageReceivedEvent now carries typed
|
|
466
|
+
// ChannelMessageRecord[] (symmetric with the sent event). Coerce
|
|
467
|
+
// provider-opaque records into the canonical shape before emission.
|
|
468
|
+
const receivedRecords = (transportResult.records ?? []).map((record) =>
|
|
469
|
+
coerceProviderRecordToMessageRecord(record, `${provider}:${channel}`),
|
|
470
|
+
);
|
|
471
|
+
|
|
465
472
|
await emitSidekickEvent(
|
|
466
473
|
buildChannelMessageReceivedEvent({
|
|
467
474
|
provider,
|
|
468
475
|
channel,
|
|
469
|
-
|
|
476
|
+
messages: receivedRecords,
|
|
470
477
|
}),
|
|
471
478
|
);
|
|
472
479
|
|
|
473
480
|
return success(outputData);
|
|
474
481
|
}
|
|
475
482
|
|
|
483
|
+
function coerceProviderRecordToMessageRecord(
|
|
484
|
+
record: unknown,
|
|
485
|
+
fallbackChannelId: string,
|
|
486
|
+
): ChannelMessageRecord {
|
|
487
|
+
// WU-2830: provider transports declare `records?: unknown[]`. Type-narrow
|
|
488
|
+
// to build a ChannelMessageRecord without unsafe casts; missing fields
|
|
489
|
+
// fall back to sensible defaults so downstream consumers always see a
|
|
490
|
+
// complete, typed payload.
|
|
491
|
+
const source = record && typeof record === 'object' ? (record as Record<string, unknown>) : {};
|
|
492
|
+
const now = nowIso();
|
|
493
|
+
const id = typeof source.id === 'string' && source.id.length > 0 ? source.id : createId('msg');
|
|
494
|
+
const channel_id =
|
|
495
|
+
typeof source.channel_id === 'string' && source.channel_id.length > 0
|
|
496
|
+
? source.channel_id
|
|
497
|
+
: fallbackChannelId;
|
|
498
|
+
const sender =
|
|
499
|
+
typeof source.sender === 'string' && source.sender.length > 0 ? source.sender : 'external';
|
|
500
|
+
const content = typeof source.content === 'string' ? source.content : '';
|
|
501
|
+
const created_at =
|
|
502
|
+
typeof source.created_at === 'string' && source.created_at.length > 0 ? source.created_at : now;
|
|
503
|
+
return { id, channel_id, sender, content, created_at };
|
|
504
|
+
}
|
|
505
|
+
|
|
476
506
|
async function channelReceiveTool(input: unknown, _context?: ToolContextLike): Promise<ToolOutput> {
|
|
477
507
|
const parsed = toRecord(input);
|
|
478
508
|
const channelName = asNonEmptyString(parsed.channel);
|
|
@@ -511,7 +541,8 @@ async function channelReceiveTool(input: unknown, _context?: ToolContextLike): P
|
|
|
511
541
|
await emitSidekickEvent(
|
|
512
542
|
buildChannelMessageReceivedEvent({
|
|
513
543
|
channel: channelName ?? DEFAULT_CHANNEL_NAME,
|
|
514
|
-
|
|
544
|
+
// WU-2830: carry the full typed ChannelMessageRecord[] payload.
|
|
545
|
+
messages: items,
|
|
515
546
|
}),
|
|
516
547
|
);
|
|
517
548
|
|
|
@@ -12,11 +12,7 @@ import {
|
|
|
12
12
|
type ToolContextLike,
|
|
13
13
|
type ToolOutput,
|
|
14
14
|
} from './shared.js';
|
|
15
|
-
import {
|
|
16
|
-
buildStateRehydratedEvent,
|
|
17
|
-
emitSidekickEvent,
|
|
18
|
-
snapshotSidekickState,
|
|
19
|
-
} from '../sidekick-events.js';
|
|
15
|
+
import { emitSidekickStateRehydration, snapshotSidekickState } from '../sidekick-events.js';
|
|
20
16
|
|
|
21
17
|
// ---------------------------------------------------------------------------
|
|
22
18
|
// Constants
|
|
@@ -51,7 +47,9 @@ async function initTool(context?: ToolContextLike): Promise<ToolOutput> {
|
|
|
51
47
|
}),
|
|
52
48
|
);
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
// WU-2830 (INIT-062 WU-D): chunked rehydration replaces the monolithic
|
|
51
|
+
// `state_rehydrated` emission — unbounded snapshots cannot stream.
|
|
52
|
+
await emitSidekickStateRehydration(await snapshotSidekickState());
|
|
55
53
|
|
|
56
54
|
return success({
|
|
57
55
|
initialized: true,
|
|
@@ -31,6 +31,25 @@ export type {
|
|
|
31
31
|
} from './manifest-schema.js';
|
|
32
32
|
|
|
33
33
|
const FULL_WORKSPACE_SCOPE_PATTERN = '**';
|
|
34
|
+
// WU-2833 (INIT-062 WU-G): the canonical read-only workspace scope used by
|
|
35
|
+
// validation runners like gates / gates:docs. Exposed so remote callers
|
|
36
|
+
// cannot mis-declare a read-only runner with a broader write scope.
|
|
37
|
+
export const SOFTWARE_DELIVERY_READ_SCOPE_PATTERN = FULL_WORKSPACE_SCOPE_PATTERN;
|
|
38
|
+
/**
|
|
39
|
+
* WU-2833 (INIT-062 WU-G): tools whose runtime handlers perform only
|
|
40
|
+
* read-side inspection (no filesystem mutation, no git mutation). Any
|
|
41
|
+
* attempt to re-declare these tools with permission: write or admin MUST
|
|
42
|
+
* fail the pack:validate gate so the security posture established by
|
|
43
|
+
* WU-2810/2811/2816 cannot drift without an explicit ADR.
|
|
44
|
+
*/
|
|
45
|
+
export const SOFTWARE_DELIVERY_READ_ONLY_RUNNER_TOOLS = ['gates', 'gates:docs'] as const;
|
|
46
|
+
export type SoftwareDeliveryReadOnlyRunnerTool =
|
|
47
|
+
(typeof SOFTWARE_DELIVERY_READ_ONLY_RUNNER_TOOLS)[number];
|
|
48
|
+
// WU-2833: metrics:snapshot reads the workspace to compute DORA metrics
|
|
49
|
+
// but writes the computed snapshot back into workspace state. This narrow
|
|
50
|
+
// write scope keeps mobile/cloud tokens for metrics:snapshot from leaking
|
|
51
|
+
// full-tree write access (principle of least privilege).
|
|
52
|
+
const SOFTWARE_DELIVERY_WORKSPACE_STATE_WRITE_PATTERN = '.lumenflow/state/**';
|
|
34
53
|
const SOFTWARE_DELIVERY_WRITE_DIRECTORY_PATTERNS = [
|
|
35
54
|
'.changeset/**',
|
|
36
55
|
'.claude/**',
|
|
@@ -99,6 +118,10 @@ const WU_UNBLOCK_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#wuUnblockTool';
|
|
|
99
118
|
const WU_RELEASE_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#wuReleaseTool';
|
|
100
119
|
const WU_RECOVER_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#wuRecoverTool';
|
|
101
120
|
const WU_REPAIR_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#wuRepairTool';
|
|
121
|
+
// WU-2833 (INIT-062 WU-G): admin-mode wu:repair wrapper that forces the
|
|
122
|
+
// `--admin` flag; exposed as a separate manifest tool so approvals can
|
|
123
|
+
// be attached to the privileged surface independently of wu:repair.
|
|
124
|
+
const WU_REPAIR_ADMIN_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#wuRepairAdminTool';
|
|
102
125
|
const GATES_TOOL_ENTRY = 'tool-impl/wu-lifecycle-tools.ts#gatesTool';
|
|
103
126
|
// WU-2729 (INIT-060 Phase 2): gates:docs exposes docs-only gate runs via a
|
|
104
127
|
// dedicated manifest entry so remote callers can request the docs gate
|
|
@@ -210,6 +233,11 @@ const TOOL_PERMISSIONS = {
|
|
|
210
233
|
'wu:recover': 'write',
|
|
211
234
|
'wu:release': 'write',
|
|
212
235
|
'wu:repair': 'write',
|
|
236
|
+
// WU-2833 (INIT-062 WU-G): privileged recovery surface for cloud-team
|
|
237
|
+
// phone UX. Distinct tool name so an approval gate + admin permission
|
|
238
|
+
// can be declared without widening the scope of the default wu:repair
|
|
239
|
+
// implementer tool.
|
|
240
|
+
'wu:repair:admin': 'admin',
|
|
213
241
|
'wu:sandbox': 'write',
|
|
214
242
|
'wu:status': 'read',
|
|
215
243
|
'wu:unblock': 'write',
|
|
@@ -241,8 +269,12 @@ const TOOL_PERMISSIONS = {
|
|
|
241
269
|
'lane:suggest': 'write',
|
|
242
270
|
'flow:bottlenecks': 'read',
|
|
243
271
|
'flow:report': 'read',
|
|
244
|
-
gates:
|
|
245
|
-
|
|
272
|
+
// WU-2833 (INIT-062 WU-G): gates and gates:docs are read-only validation
|
|
273
|
+
// runners. Mobile/cloud tokens for these tools must not carry workspace
|
|
274
|
+
// write access (principle of least privilege; matches the security
|
|
275
|
+
// posture established by WU-2810/2811/2816).
|
|
276
|
+
gates: 'read',
|
|
277
|
+
'gates:docs': 'read',
|
|
246
278
|
'file:delete': 'write',
|
|
247
279
|
'file:edit': 'write',
|
|
248
280
|
'file:read': 'read',
|
|
@@ -278,7 +310,11 @@ const TOOL_PERMISSIONS = {
|
|
|
278
310
|
'lumenflow:release': 'write',
|
|
279
311
|
'lumenflow:upgrade': 'write',
|
|
280
312
|
metrics: 'read',
|
|
281
|
-
|
|
313
|
+
// WU-2833 (INIT-062 WU-G): metrics:snapshot reads the workspace and
|
|
314
|
+
// writes a DORA snapshot back into .lumenflow/state/. Permission role
|
|
315
|
+
// is "write" because it mutates state; the explicit SCOPE_OVERRIDE
|
|
316
|
+
// narrows the write path to .lumenflow/state/** (no full-tree write).
|
|
317
|
+
'metrics:snapshot': 'write',
|
|
282
318
|
'lumenflow:metrics': 'read',
|
|
283
319
|
'signal:cleanup': 'write',
|
|
284
320
|
'sync:templates': 'write',
|
|
@@ -320,6 +356,7 @@ const TOOL_ENTRY_OVERRIDES: Partial<Record<ToolName, string>> = {
|
|
|
320
356
|
'wu:release': WU_RELEASE_TOOL_ENTRY,
|
|
321
357
|
'wu:recover': WU_RECOVER_TOOL_ENTRY,
|
|
322
358
|
'wu:repair': WU_REPAIR_TOOL_ENTRY,
|
|
359
|
+
'wu:repair:admin': WU_REPAIR_ADMIN_TOOL_ENTRY,
|
|
323
360
|
'wu:infer-lane': WU_INFER_LANE_TOOL_ENTRY,
|
|
324
361
|
gates: GATES_TOOL_ENTRY,
|
|
325
362
|
'gates:docs': GATES_DOCS_TOOL_ENTRY,
|
|
@@ -415,9 +452,36 @@ function requiredScopesForPermission(permission: ToolPermission): PathScope[] {
|
|
|
415
452
|
return createPathScopes([FULL_WORKSPACE_SCOPE_PATTERN], TOOL_SCOPE_ACCESS.READ);
|
|
416
453
|
}
|
|
417
454
|
|
|
455
|
+
// WU-2833: admin permission inherits the same constrained write-scope
|
|
456
|
+
// set as write permission. The admin distinction is carried by the
|
|
457
|
+
// required_approvals gate, not by broader path access.
|
|
418
458
|
return createPathScopes(SOFTWARE_DELIVERY_WRITE_SCOPE_PATTERNS, TOOL_SCOPE_ACCESS.WRITE);
|
|
419
459
|
}
|
|
420
460
|
|
|
461
|
+
/**
|
|
462
|
+
* WU-2833 (INIT-062 WU-G): per-tool scope overrides for tools whose
|
|
463
|
+
* runtime semantics do not match the default read/write scope set. Used
|
|
464
|
+
* sparingly — only when a tool legitimately needs both read and a narrow
|
|
465
|
+
* write scope (or vice versa).
|
|
466
|
+
*/
|
|
467
|
+
const SCOPE_OVERRIDES: Partial<Record<string, PathScope[]>> = {
|
|
468
|
+
// metrics:snapshot reads the full workspace to compute DORA metrics,
|
|
469
|
+
// then writes the snapshot back into workspace state. The narrow write
|
|
470
|
+
// scope prevents mobile/cloud tokens from leaking full-tree write.
|
|
471
|
+
'metrics:snapshot': [
|
|
472
|
+
{
|
|
473
|
+
type: TOOL_SCOPE_TYPES.PATH,
|
|
474
|
+
pattern: FULL_WORKSPACE_SCOPE_PATTERN,
|
|
475
|
+
access: TOOL_SCOPE_ACCESS.READ,
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
type: TOOL_SCOPE_TYPES.PATH,
|
|
479
|
+
pattern: SOFTWARE_DELIVERY_WORKSPACE_STATE_WRITE_PATTERN,
|
|
480
|
+
access: TOOL_SCOPE_ACCESS.WRITE,
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
|
|
421
485
|
/**
|
|
422
486
|
* WU-2729 (INIT-060 Phase 2): the 10 software-delivery pack tools that are
|
|
423
487
|
* callable remotely via POST /tools/:name. The HTTP surface uses this list
|
|
@@ -460,17 +524,23 @@ const APPROVAL_OVERRIDES: Partial<Record<ToolName, readonly string[]>> = {
|
|
|
460
524
|
'plan:promote': [SOFTWARE_DELIVERY_APPROVAL_IDS.REMOTE_MUTATION],
|
|
461
525
|
'initiative:create': [SOFTWARE_DELIVERY_APPROVAL_IDS.REMOTE_MUTATION],
|
|
462
526
|
'initiative:add-wu': [SOFTWARE_DELIVERY_APPROVAL_IDS.REMOTE_MUTATION],
|
|
527
|
+
// WU-2833: privileged recovery MUST present an explicit approval gate
|
|
528
|
+
// to conductor/phone UX before dispatch. Without this the admin tool
|
|
529
|
+
// is indistinguishable from wu:repair from an authorisation standpoint.
|
|
530
|
+
'wu:repair:admin': [SOFTWARE_DELIVERY_APPROVAL_IDS.REMOTE_MUTATION],
|
|
463
531
|
};
|
|
464
532
|
|
|
465
533
|
function requiredApprovalsForTool(name: ToolName): string[] | undefined {
|
|
466
|
-
// WU-2729:
|
|
534
|
+
// WU-2729: the 10 remote-callable tools carry explicit
|
|
467
535
|
// required_approvals metadata (even if empty). Other tools leave the
|
|
468
536
|
// field undefined so the manifest stays minimally descriptive.
|
|
537
|
+
// WU-2833: admin-permission tools also carry explicit approvals so the
|
|
538
|
+
// privileged surface cannot be invoked without a visible approval gate.
|
|
469
539
|
const isRemoteCallable = (REMOTE_CALLABLE_TOOLS as readonly string[]).includes(name);
|
|
470
|
-
|
|
540
|
+
const override = APPROVAL_OVERRIDES[name];
|
|
541
|
+
if (!isRemoteCallable && override === undefined) {
|
|
471
542
|
return undefined;
|
|
472
543
|
}
|
|
473
|
-
const override = APPROVAL_OVERRIDES[name];
|
|
474
544
|
return override ? [...override] : [];
|
|
475
545
|
}
|
|
476
546
|
|
|
@@ -487,11 +557,15 @@ function createManifestTools(): SoftwareDeliveryManifestTool[] {
|
|
|
487
557
|
return (Object.keys(TOOL_PERMISSIONS) as ToolName[]).map((name) => {
|
|
488
558
|
const permission = TOOL_PERMISSIONS[name];
|
|
489
559
|
const approvals = requiredApprovalsForTool(name);
|
|
560
|
+
// WU-2833: per-tool scope overrides take priority over the default
|
|
561
|
+
// permission-derived scope set so read-plus-narrow-write tools like
|
|
562
|
+
// metrics:snapshot can declare both accesses on a single entry.
|
|
563
|
+
const scopeOverride = SCOPE_OVERRIDES[name];
|
|
490
564
|
const entry: SoftwareDeliveryManifestTool = {
|
|
491
565
|
name,
|
|
492
566
|
entry: resolveToolEntry(name),
|
|
493
567
|
permission,
|
|
494
|
-
required_scopes: requiredScopesForPermission(permission),
|
|
568
|
+
required_scopes: scopeOverride ? [...scopeOverride] : requiredScopesForPermission(permission),
|
|
495
569
|
};
|
|
496
570
|
if (approvals !== undefined) {
|
|
497
571
|
entry.required_approvals = approvals;
|
|
@@ -543,6 +617,19 @@ const SOFTWARE_DELIVERY_EMITTED_EVENT_KINDS = [
|
|
|
543
617
|
'software-delivery:plan_created',
|
|
544
618
|
'software-delivery:plan_linked',
|
|
545
619
|
'software-delivery:plan_promoted',
|
|
620
|
+
// WU-2832 (INIT-062 WU-F): close the cloud-team polling gap with 9
|
|
621
|
+
// additional ephemeral kinds. Validation pair (validated/invalid),
|
|
622
|
+
// recovery, preflight, escalation, ratchet, bottleneck, DORA snapshot,
|
|
623
|
+
// and replay-artifact addressable by event_id.
|
|
624
|
+
'software-delivery:wu_spec_validated',
|
|
625
|
+
'software-delivery:wu_spec_invalid',
|
|
626
|
+
'software-delivery:wu_recovered',
|
|
627
|
+
'software-delivery:wu_preflight_failed',
|
|
628
|
+
'software-delivery:wu_escalation_resolved',
|
|
629
|
+
'software-delivery:test_ratchet_adjusted',
|
|
630
|
+
'software-delivery:flow_bottleneck_detected',
|
|
631
|
+
'software-delivery:dora_metric_snapshot',
|
|
632
|
+
'software-delivery:replay_artifact_published',
|
|
546
633
|
] as const;
|
|
547
634
|
const SOFTWARE_DELIVERY_REQUIRED_SURFACES = ['http'] as const;
|
|
548
635
|
|
|
@@ -200,6 +200,15 @@ tools:
|
|
|
200
200
|
entry: tool-impl/wu-lifecycle-tools.ts#wuRepairTool
|
|
201
201
|
permission: write
|
|
202
202
|
required_scopes: *softwareDeliveryWriteScopes
|
|
203
|
+
# WU-2833 (INIT-062 WU-G): privileged wu:repair surface for cloud-team
|
|
204
|
+
# phone UX. Distinct tool name so approval gate + admin permission can
|
|
205
|
+
# be declared without widening the default wu:repair surface.
|
|
206
|
+
- name: wu:repair:admin
|
|
207
|
+
entry: tool-impl/wu-lifecycle-tools.ts#wuRepairAdminTool
|
|
208
|
+
permission: admin
|
|
209
|
+
required_scopes: *softwareDeliveryWriteScopes
|
|
210
|
+
required_approvals:
|
|
211
|
+
- software-delivery:remote_mutation
|
|
203
212
|
- name: wu:sandbox
|
|
204
213
|
entry: tool-impl/wu-lifecycle-tools.ts#wuSandboxTool
|
|
205
214
|
permission: write
|
|
@@ -377,15 +386,26 @@ tools:
|
|
|
377
386
|
access: read
|
|
378
387
|
required_approvals: []
|
|
379
388
|
# gate:*
|
|
389
|
+
# WU-2833 (INIT-062 WU-G): gates / gates:docs are read-only validation
|
|
390
|
+
# runners. Declaring them permission: write would grant mobile/cloud
|
|
391
|
+
# tokens full-tree write access via required_scopes (the security model
|
|
392
|
+
# hardened by WU-2810/2811/2816). Re-declare as permission: read and
|
|
393
|
+
# scope them to SOFTWARE_DELIVERY_READ_SCOPE (wildcard path, read).
|
|
380
394
|
- name: gates
|
|
381
395
|
entry: tool-impl/wu-lifecycle-tools.ts#gatesTool
|
|
382
|
-
permission:
|
|
383
|
-
required_scopes:
|
|
396
|
+
permission: read
|
|
397
|
+
required_scopes:
|
|
398
|
+
- type: path
|
|
399
|
+
pattern: '**'
|
|
400
|
+
access: read
|
|
384
401
|
required_approvals: []
|
|
385
402
|
- name: gates:docs
|
|
386
403
|
entry: tool-impl/wu-lifecycle-tools.ts#gatesDocsTool
|
|
387
|
-
permission:
|
|
388
|
-
required_scopes:
|
|
404
|
+
permission: read
|
|
405
|
+
required_scopes:
|
|
406
|
+
- type: path
|
|
407
|
+
pattern: '**'
|
|
408
|
+
access: read
|
|
389
409
|
required_approvals: []
|
|
390
410
|
# file:*
|
|
391
411
|
- name: file:delete
|
|
@@ -577,13 +597,20 @@ tools:
|
|
|
577
597
|
- type: path
|
|
578
598
|
pattern: '**'
|
|
579
599
|
access: read
|
|
600
|
+
# WU-2833 (INIT-062 WU-G): metrics:snapshot reads the workspace and
|
|
601
|
+
# writes a DORA snapshot to .lumenflow/state/. Declared permission:
|
|
602
|
+
# write with narrow write scope + full-read scope (principle of least
|
|
603
|
+
# privilege — no full-tree write).
|
|
580
604
|
- name: metrics:snapshot
|
|
581
605
|
entry: tool-impl/flow-metrics-tools.ts#metricsSnapshotTool
|
|
582
|
-
permission:
|
|
606
|
+
permission: write
|
|
583
607
|
required_scopes:
|
|
584
608
|
- type: path
|
|
585
609
|
pattern: '**'
|
|
586
610
|
access: read
|
|
611
|
+
- type: path
|
|
612
|
+
pattern: .lumenflow/state/**
|
|
613
|
+
access: write
|
|
587
614
|
- name: lumenflow:metrics
|
|
588
615
|
entry: tool-impl/runtime-native-tools.ts#lumenflowMetricsTool
|
|
589
616
|
permission: read
|
|
@@ -686,6 +713,16 @@ emitted_event_kinds:
|
|
|
686
713
|
- software-delivery:plan_created
|
|
687
714
|
- software-delivery:plan_linked
|
|
688
715
|
- software-delivery:plan_promoted
|
|
716
|
+
# WU-2832 (INIT-062 WU-F): close the cloud-team polling gap.
|
|
717
|
+
- software-delivery:wu_spec_validated
|
|
718
|
+
- software-delivery:wu_spec_invalid
|
|
719
|
+
- software-delivery:wu_recovered
|
|
720
|
+
- software-delivery:wu_preflight_failed
|
|
721
|
+
- software-delivery:wu_escalation_resolved
|
|
722
|
+
- software-delivery:test_ratchet_adjusted
|
|
723
|
+
- software-delivery:flow_bottleneck_detected
|
|
724
|
+
- software-delivery:dora_metric_snapshot
|
|
725
|
+
- software-delivery:replay_artifact_published
|
|
689
726
|
subscribed_event_kinds: []
|
|
690
727
|
required_approvals: []
|
|
691
728
|
surfaces_required:
|
|
@@ -698,6 +698,36 @@ export async function wuRepairTool(input: unknown): Promise<ToolOutput> {
|
|
|
698
698
|
return executeLifecycleTool(LIFECYCLE_TOOLS.WU_REPAIR, args);
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
/**
|
|
702
|
+
* WU-2833 (INIT-062 WU-G): privileged wu:repair variant registered as a
|
|
703
|
+
* distinct pack tool (wu:repair:admin) so cloud-team phone UX can expose
|
|
704
|
+
* admin recovery behind a remote_mutation approval gate without widening
|
|
705
|
+
* the default wu:repair surface. Forces `--admin` on every invocation.
|
|
706
|
+
*/
|
|
707
|
+
export async function wuRepairAdminTool(input: unknown): Promise<ToolOutput> {
|
|
708
|
+
const parsed = toRecord(input);
|
|
709
|
+
|
|
710
|
+
const args: string[] = ['--admin'];
|
|
711
|
+
const id = toStringValue(parsed.id);
|
|
712
|
+
if (id) {
|
|
713
|
+
args.push('--id', id);
|
|
714
|
+
}
|
|
715
|
+
if (parsed.check === true) {
|
|
716
|
+
args.push('--check');
|
|
717
|
+
}
|
|
718
|
+
if (parsed.all === true) {
|
|
719
|
+
args.push('--all');
|
|
720
|
+
}
|
|
721
|
+
if (parsed.claim === true) {
|
|
722
|
+
args.push('--claim');
|
|
723
|
+
}
|
|
724
|
+
if (parsed.repair_state === true) {
|
|
725
|
+
args.push('--repair-state');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return executeLifecycleTool(LIFECYCLE_TOOLS.WU_REPAIR, args);
|
|
729
|
+
}
|
|
730
|
+
|
|
701
731
|
export async function wuStatusTool(input: unknown): Promise<ToolOutput> {
|
|
702
732
|
const parsed = toRecord(input);
|
|
703
733
|
const id = toStringValue(parsed.id);
|