@quantiya/codevibe-claude-plugin 2.0.17 → 2.0.19

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.
Files changed (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/server.js +18 -18
  3. package/node_modules/@quantiya/codevibe-core/dist/__tests__/mobile-prompt-input.test.d.ts +1 -0
  4. package/node_modules/@quantiya/codevibe-core/dist/appsync/__tests__/group-operation-result.test.d.ts +1 -0
  5. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +155 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +14 -0
  7. package/node_modules/@quantiya/codevibe-core/dist/crypto/__tests__/crypto-service.test.d.ts +1 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/crypto/crypto-service.d.ts +7 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +2 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/index.js +525 -304
  11. package/node_modules/@quantiya/codevibe-core/dist/local-executor/local-executor-impl.d.ts +34 -0
  12. package/node_modules/@quantiya/codevibe-core/dist/local-executor/snapshot-merge.d.ts +6 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/local-executor/types.d.ts +6 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/mobile-prompt-input.d.ts +13 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/decision-attempt-live-handoff.test.d.ts +1 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/decision-attempt-recovery.test.d.ts +1 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/decision-attempt-session-serialization.test.d.ts +1 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/decision-attempt.test.d.ts +1 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/decision-effect.test.d.ts +1 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/gate-resolution-event.test.d.ts +1 -0
  21. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/supervision-catch-up.test.d.ts +1 -0
  22. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/workspace-outcome.test.d.ts +1 -0
  23. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/workspace-terminal-coordinator.test.d.ts +1 -0
  24. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +3 -0
  25. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +5621 -1758
  26. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/ConversationPane.d.ts +2 -1
  27. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/GatePromptEntry.d.ts +1 -0
  28. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/decision-attempt-recovery.d.ts +70 -0
  29. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/decision-attempt.d.ts +211 -0
  30. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/decision-effect.d.ts +39 -0
  31. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/emit-shell-event.d.ts +20 -2
  32. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-decision-submit.d.ts +20 -2
  33. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-prompts.d.ts +4 -0
  34. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-resolution-event.d.ts +10 -0
  35. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +6 -2
  36. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +11 -0
  37. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/supervision-catch-up.d.ts +7 -0
  38. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +57 -0
  39. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/workspace-outcome.d.ts +20 -0
  40. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/workspace-terminal-coordinator.d.ts +114 -0
  41. package/node_modules/@quantiya/codevibe-core/dist/types/events.d.ts +7 -0
  42. package/node_modules/@quantiya/codevibe-core/dist/types/session.d.ts +2 -0
  43. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  44. package/node_modules/fs-ext/build/Makefile +1 -1
  45. package/node_modules/fs-ext/build/Release/fs_ext.node +0 -0
  46. package/node_modules/fs-ext/build/config.gypi +0 -2
  47. package/package.json +2 -2
@@ -8,6 +8,7 @@ export interface ConversationPaneProps {
8
8
  * decision. `null`/absent for single-implementor / Companion sessions.
9
9
  */
10
10
  trackLabelByTaskId?: Map<string, string> | null;
11
+ gateActionRecoveryBlocked?: boolean;
11
12
  }
12
- export declare function renderConversationEntry(entry: ConversationEntry, trackLabelByTaskId?: Map<string, string> | null): React.ReactElement;
13
+ export declare function renderConversationEntry(entry: ConversationEntry, trackLabelByTaskId?: Map<string, string> | null, gateActionRecoveryBlocked?: boolean): React.ReactElement;
13
14
  export declare function ConversationPane(props: ConversationPaneProps): React.ReactElement;
@@ -4,6 +4,7 @@ export interface GatePromptEntryProps {
4
4
  entry: Extract<ConversationEntry, {
5
5
  kind: 'gate-prompt';
6
6
  }>;
7
+ gateActionRecoveryBlocked?: boolean;
7
8
  /**
8
9
  * [#469 gate-pin] Best-effort "Track N" label for the gate's owning team
9
10
  * track, derived by `<OrchestrationApp>` from `state.team` (taskId → index).
@@ -0,0 +1,70 @@
1
+ import type { Event } from '../types/events';
2
+ import { DecisionAttemptReceiptStore, type DecisionAttemptReceipt, type DecisionAttemptResult, type DecisionSubmissionResult } from './decision-attempt';
3
+ import type { DurableGateResolution } from './types';
4
+ export interface DecisionAttemptRecoveryClient {
5
+ getDecisionAttempt(input: {
6
+ sessionId: string;
7
+ promptId: string;
8
+ decisionAttemptId: string;
9
+ }): Promise<DecisionAttemptResult>;
10
+ getGateResolution(input: {
11
+ sessionId: string;
12
+ promptId: string;
13
+ }): Promise<unknown>;
14
+ submitGateDecisionV2?(request: Extract<DecisionAttemptReceipt['request'], {
15
+ promptKind: 'GATE';
16
+ }>, canonicalRequestHash: string): Promise<DecisionSubmissionResult>;
17
+ submitGroupDecisionV2?(request: Extract<DecisionAttemptReceipt['request'], {
18
+ promptKind: 'TASK_GROUP';
19
+ }>, canonicalRequestHash: string): Promise<DecisionSubmissionResult>;
20
+ }
21
+ export interface GateResolutionQueryResult {
22
+ sessionId: string;
23
+ promptId: string;
24
+ state: 'NOT_FOUND' | 'CLAIMED' | 'RESOLVED';
25
+ actionable: false;
26
+ decisionClaimId: string | null;
27
+ resolutionEventId: string | null;
28
+ resolutionTimestamp: string | null;
29
+ resolutionEvent: Event | null;
30
+ resolution: DurableGateResolution | null;
31
+ }
32
+ /**
33
+ * Delete one local attempt fence only after a strict durable resolution has
34
+ * taken authority for the same prompt. A rejected race loser is deliberately
35
+ * eligible: its own attempt can never become RESOLVED, so the winning
36
+ * resolution is the handoff that makes deletion safe.
37
+ */
38
+ export declare function acknowledgeDecisionAttemptAfterResolution(args: {
39
+ store: DecisionAttemptReceiptStore;
40
+ receipt: DecisionAttemptReceipt;
41
+ attempt: DecisionAttemptResult;
42
+ resolution: DurableGateResolution;
43
+ }): Promise<boolean>;
44
+ /**
45
+ * Production live/catch-up composition: finish the receipt-backed desktop
46
+ * effect first, then hand every matching decision-attempt fence to the same
47
+ * classified durable authority. The returned callback is shared with focused
48
+ * integration tests so they execute the exact production ordering.
49
+ */
50
+ export interface DurableResolutionAttemptHandoff {
51
+ (resolution: DurableGateResolution): Promise<void>;
52
+ acknowledgeDelayedRejection(receipt: DecisionAttemptReceipt, resolution: DurableGateResolution): Promise<boolean>;
53
+ }
54
+ export declare function createDurableResolutionAttemptHandoff(args: {
55
+ store: DecisionAttemptReceiptStore;
56
+ client: Pick<DecisionAttemptRecoveryClient, 'getDecisionAttempt'>;
57
+ dispatchEffect: (resolution: DurableGateResolution) => Promise<void>;
58
+ }): DurableResolutionAttemptHandoff;
59
+ /**
60
+ * Reconcile every crash-durable receipt before the composer is enabled.
61
+ * Ambiguous states remain fenced. Only an explicit actionable terminal reject
62
+ * removes the receipt without a durable resolution Event.
63
+ */
64
+ export declare function recoverDecisionAttemptReceipts(args: {
65
+ sessionId: string;
66
+ client: DecisionAttemptRecoveryClient;
67
+ store?: DecisionAttemptReceiptStore;
68
+ onResolutionEvent: (event: Event) => Promise<DurableGateResolution | null> | DurableGateResolution | null;
69
+ onActionableRejection: (receipt: DecisionAttemptReceipt, rejectionCode: string) => void;
70
+ }): Promise<void>;
@@ -0,0 +1,211 @@
1
+ export declare const DECISION_ATTEMPT_DOMAIN = "codevibe.decision_attempt.v1\0";
2
+ export declare const DECISION_ATTEMPT_RECEIPT_VERSION: 1;
3
+ export type GateDecisionKind = 'ACCEPT' | 'ACCEPT_WITH_NOTES' | 'REJECT_WITH_NOTES' | 'REJECT_RESTART' | 'ABORT_TASK' | 'APPROVE' | 'CANCEL' | 'CONTINUE_WITH';
4
+ export type GroupDecisionKind = Exclude<GateDecisionKind, 'APPROVE' | 'CANCEL' | 'CONTINUE_WITH'>;
5
+ export interface DecisionEncryptedNotes {
6
+ ciphertextB64: string;
7
+ keyVersion: 1;
8
+ sessionId: string;
9
+ }
10
+ export interface CanonicalGateDecisionRequest {
11
+ decision: GateDecisionKind;
12
+ decisionAttemptId: string;
13
+ gateId: string;
14
+ promptKind: 'GATE';
15
+ currentRound: number;
16
+ sessionId: string;
17
+ taskId: string;
18
+ notes?: DecisionEncryptedNotes;
19
+ }
20
+ export interface CanonicalGroupDecisionRequest {
21
+ decision: GroupDecisionKind;
22
+ decisionAttemptId: string;
23
+ groupGeneration: number;
24
+ promptId: string;
25
+ promptKind: 'TASK_GROUP';
26
+ sessionId: string;
27
+ taskGroupId: string;
28
+ notes?: DecisionEncryptedNotes;
29
+ }
30
+ export type CanonicalDecisionRequest = CanonicalGateDecisionRequest | CanonicalGroupDecisionRequest;
31
+ export type DecisionAttemptTerminal = {
32
+ state: 'RESOLVED';
33
+ decisionClaimId: string;
34
+ resolutionEventId: string;
35
+ resolutionTimestamp: string;
36
+ } | {
37
+ state: 'REJECTED';
38
+ rejectionCode: string;
39
+ actionable: boolean;
40
+ };
41
+ export interface DecisionAttemptReceipt {
42
+ version: typeof DECISION_ATTEMPT_RECEIPT_VERSION;
43
+ request: CanonicalDecisionRequest;
44
+ canonicalRequestHash: string;
45
+ canonicalRequestBytesB64: string;
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ terminal?: DecisionAttemptTerminal;
49
+ }
50
+ export type DecisionRejectionCode = 'INVALID_PROMPT' | 'STALE_GENERATION' | 'DECISION_RACE_LOST' | 'ATTEMPT_LIMIT' | 'REQUEST_MISMATCH' | 'ACCOUNT_DELETING' | 'POLICY_REJECTED';
51
+ export type DecisionProgressStatus = 'PENDING' | 'PENDING_ERROR' | 'COMPLETE';
52
+ export type DecisionSubmissionResult = {
53
+ decisionAttemptId: string;
54
+ acceptanceStatus: 'SUBMITTED_PENDING';
55
+ promptId: string;
56
+ actionable: false;
57
+ decisionClaimId: null;
58
+ resolutionStatus: null;
59
+ workflowStatus: null;
60
+ resolutionEventId: null;
61
+ resolutionTimestamp: null;
62
+ rejectionCode: null;
63
+ } | {
64
+ decisionAttemptId: string;
65
+ acceptanceStatus: 'REJECTED';
66
+ promptId: string;
67
+ actionable: boolean;
68
+ decisionClaimId: null;
69
+ resolutionStatus: null;
70
+ workflowStatus: null;
71
+ resolutionEventId: null;
72
+ resolutionTimestamp: null;
73
+ rejectionCode: DecisionRejectionCode;
74
+ } | {
75
+ decisionAttemptId: string;
76
+ acceptanceStatus: 'ACCEPTED_PENDING';
77
+ promptId: string;
78
+ actionable: false;
79
+ decisionClaimId: string;
80
+ resolutionStatus: 'PENDING' | 'PENDING_ERROR';
81
+ workflowStatus: 'PENDING';
82
+ resolutionEventId: string;
83
+ resolutionTimestamp: null;
84
+ rejectionCode: null;
85
+ } | {
86
+ decisionAttemptId: string;
87
+ acceptanceStatus: 'ACCEPTED_RESOLVED';
88
+ promptId: string;
89
+ actionable: false;
90
+ decisionClaimId: string;
91
+ resolutionStatus: 'COMPLETE';
92
+ workflowStatus: DecisionProgressStatus;
93
+ resolutionEventId: string;
94
+ resolutionTimestamp: string;
95
+ rejectionCode: null;
96
+ };
97
+ export type DecisionAttemptResult = {
98
+ sessionId: string;
99
+ promptId: string;
100
+ decisionAttemptId: string;
101
+ state: 'NOT_FOUND';
102
+ canonicalRequestHash: null;
103
+ decisionClaimId: null;
104
+ resolutionEventId: null;
105
+ resolutionTimestamp: null;
106
+ rejectionCode: null;
107
+ actionable: false;
108
+ } | {
109
+ sessionId: string;
110
+ promptId: string;
111
+ decisionAttemptId: string;
112
+ state: 'SUBMITTED';
113
+ canonicalRequestHash: string;
114
+ decisionClaimId: null;
115
+ resolutionEventId: null;
116
+ resolutionTimestamp: null;
117
+ rejectionCode: null;
118
+ actionable: false;
119
+ } | {
120
+ sessionId: string;
121
+ promptId: string;
122
+ decisionAttemptId: string;
123
+ state: 'CLAIMED';
124
+ canonicalRequestHash: string;
125
+ decisionClaimId: string;
126
+ resolutionEventId: string;
127
+ resolutionTimestamp: null;
128
+ rejectionCode: null;
129
+ actionable: false;
130
+ } | {
131
+ sessionId: string;
132
+ promptId: string;
133
+ decisionAttemptId: string;
134
+ state: 'RESOLVED';
135
+ canonicalRequestHash: string;
136
+ decisionClaimId: string;
137
+ resolutionEventId: string;
138
+ resolutionTimestamp: string;
139
+ rejectionCode: null;
140
+ actionable: false;
141
+ } | {
142
+ sessionId: string;
143
+ promptId: string;
144
+ decisionAttemptId: string;
145
+ state: 'REJECTED';
146
+ canonicalRequestHash: string | null;
147
+ decisionClaimId: null;
148
+ resolutionEventId: null;
149
+ resolutionTimestamp: null;
150
+ rejectionCode: DecisionRejectionCode;
151
+ actionable: boolean;
152
+ };
153
+ export declare function canonicalizeGateDecisionRequest(input: Omit<CanonicalGateDecisionRequest, 'promptKind'> & {
154
+ promptKind?: 'GATE';
155
+ }): CanonicalGateDecisionRequest;
156
+ export declare function canonicalizeGroupDecisionRequest(input: Omit<CanonicalGroupDecisionRequest, 'promptKind' | 'promptId'> & {
157
+ promptKind?: 'TASK_GROUP';
158
+ promptId?: string;
159
+ }): CanonicalGroupDecisionRequest;
160
+ export declare function canonicalDecisionRequestBytes(request: CanonicalDecisionRequest): Buffer;
161
+ export declare function canonicalDecisionRequestHash(request: CanonicalDecisionRequest): string;
162
+ export declare function makeDecisionAttemptReceipt(request: CanonicalDecisionRequest, now?: Date): DecisionAttemptReceipt;
163
+ export declare function parseDecisionSubmissionResult(value: unknown): DecisionSubmissionResult;
164
+ export declare function parseDecisionAttemptResult(value: unknown): DecisionAttemptResult;
165
+ export declare class DecisionAttemptReceiptStore {
166
+ /**
167
+ * Every store instance targeting the same root shares one session queue.
168
+ * Production injects one store through submit, subscription/catch-up, and
169
+ * startup recovery; the process-wide map also keeps accidental sibling
170
+ * instances from exposing atomic-write temporaries to enumeration.
171
+ */
172
+ private static readonly sessionQueues;
173
+ private readonly rootDir;
174
+ private readonly syncDirectory;
175
+ constructor(options?: {
176
+ rootDir?: string;
177
+ directorySync?: (directory: string) => Promise<void>;
178
+ });
179
+ private receiptPath;
180
+ private sessionQueueKey;
181
+ private withSessionLock;
182
+ private assertNoSymlinkComponents;
183
+ private atomicWrite;
184
+ /**
185
+ * Remove one receipt name and persist that directory-entry transition before
186
+ * returning. ENOENT is idempotent only after the still-present directory is
187
+ * synced; an absent directory has no entry that can be resurrected.
188
+ */
189
+ private removeDirectoryEntryDurably;
190
+ private readUnlocked;
191
+ private sameImmutableIdentity;
192
+ write(receipt: DecisionAttemptReceipt): Promise<void>;
193
+ read(sessionId: string, promptId: string, decisionAttemptId: string): Promise<DecisionAttemptReceipt | null>;
194
+ /**
195
+ * Return the bounded set of unacknowledged receipts for one session. Paths
196
+ * are opaque hashes, regular files only, and every receipt is revalidated
197
+ * against its canonical bytes before it can participate in recovery.
198
+ */
199
+ private listForSessionUnlocked;
200
+ listForSession(sessionId: string): Promise<DecisionAttemptReceipt[]>;
201
+ markTerminal(receipt: DecisionAttemptReceipt, terminal: DecisionAttemptTerminal, now?: Date): Promise<DecisionAttemptReceipt | null>;
202
+ /**
203
+ * Remove an exact receipt only while its caller still owns the synchronous
204
+ * pre-submit boundary. This transition is intentionally separate from
205
+ * `acknowledge`: no server attempt exists yet, so inventing a terminal state
206
+ * would make later recovery contradict the server. The caller MUST invoke it
207
+ * before calling the submission transport.
208
+ */
209
+ abandonUnsubmitted(receipt: DecisionAttemptReceipt): Promise<void>;
210
+ acknowledge(receipt: DecisionAttemptReceipt): Promise<void>;
211
+ }
@@ -0,0 +1,39 @@
1
+ import type { DurableGateResolution, PostDecisionAction } from './types';
2
+ declare const RECEIPT_VERSION: 1;
3
+ export interface DecisionEffectReceipt {
4
+ version: typeof RECEIPT_VERSION;
5
+ sessionId: string;
6
+ promptId: string;
7
+ decisionClaimId: string;
8
+ resolutionEventId: string;
9
+ descriptorHash: string;
10
+ resolution: DurableGateResolution;
11
+ state: 'PENDING' | 'COMPLETE';
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ }
15
+ export type DecisionEffectHandler = (resolution: DurableGateResolution, postAction: PostDecisionAction | null) => Promise<void> | void;
16
+ export declare function durableResolutionDescriptorHash(resolution: DurableGateResolution): string;
17
+ export declare class DecisionEffectReceiptStore {
18
+ private readonly rootDir;
19
+ constructor(options?: {
20
+ rootDir?: string;
21
+ });
22
+ private receiptPath;
23
+ private assertNoSymlinkComponents;
24
+ private atomicWrite;
25
+ read(sessionId: string, decisionClaimId: string): Promise<DecisionEffectReceipt | null>;
26
+ reserve(resolution: DurableGateResolution, now?: Date): Promise<DecisionEffectReceipt>;
27
+ complete(receipt: DecisionEffectReceipt, now?: Date): Promise<DecisionEffectReceipt>;
28
+ listForSession(sessionId: string): Promise<DecisionEffectReceipt[]>;
29
+ }
30
+ export declare function postDecisionActionForResolution(resolution: DurableGateResolution): PostDecisionAction | null;
31
+ export declare class DecisionEffectDispatcher {
32
+ private readonly store;
33
+ private readonly handler;
34
+ private readonly afterComplete?;
35
+ constructor(store: DecisionEffectReceiptStore, handler: DecisionEffectHandler, afterComplete?: ((resolution: DurableGateResolution) => Promise<void> | void) | undefined);
36
+ dispatch(resolution: DurableGateResolution): Promise<void>;
37
+ replayPending(sessionId: string): Promise<void>;
38
+ }
39
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { AppSyncClient } from '../appsync';
2
- import { type Session } from '../types';
2
+ import { CreateEventInput, type Session } from '../types';
3
3
  import type { E1ProducerKind } from '../e1';
4
4
  /**
5
5
  * §3 LOCK lines 173-181. Names + per-type metadata shapes are part of
@@ -33,6 +33,18 @@ export interface ShellEventEmit {
33
33
  timestamp?: string;
34
34
  /** Pre-minted nonce when encrypted metadata must bind the response nonce. */
35
35
  writerEventNonce?: string;
36
+ /** P16/P20 encrypted terminal-result authority. All four fields are atomic. */
37
+ writerEventKind?: 'WORKSPACE_OUTCOME';
38
+ writerSessionGenerationId?: string;
39
+ writerOutcomeSubject?: {
40
+ subjectKind: 'task';
41
+ subjectId: string;
42
+ } | {
43
+ subjectKind: 'task_group';
44
+ subjectId: string;
45
+ groupGeneration: number;
46
+ };
47
+ writerCausalPredecessor?: Record<string, unknown>;
36
48
  /** Proven local key mismatch: emit the safe legacy joined fallback. */
37
49
  disableWriterAttestation?: boolean;
38
50
  ownerToken?: string;
@@ -51,6 +63,12 @@ export interface EmitShellEventDeps {
51
63
  appsyncClient: Pick<AppSyncClient, 'createEvent'>;
52
64
  getSessionKey: (sessionId: string) => Promise<string | null>;
53
65
  session?: Session;
66
+ /**
67
+ * Persist an exact encrypted/signed input without sending it. Reserved for
68
+ * crash-safe workspace-outcome drafts that must remain undiscoverable until
69
+ * their hosted COMMIT boundary is confirmed.
70
+ */
71
+ prepareOnly?: (input: CreateEventInput) => Promise<void>;
54
72
  }
55
73
  export type ShellEventEmitResult = {
56
74
  emitted: true;
@@ -66,7 +84,7 @@ export type ShellEventEmitResult = {
66
84
  };
67
85
  } | {
68
86
  emitted: false;
69
- reason: 'missing_session_key' | 'auth_required' | 'auth_unavailable' | 'create_failed' | 'definitive_rejection' | 'invalid_response' | 'writer_ineligible';
87
+ reason: 'missing_session_key' | 'auth_required' | 'auth_unavailable' | 'create_failed' | 'definitive_rejection' | 'invalid_response' | 'prepared_only' | 'writer_ineligible';
70
88
  error?: unknown;
71
89
  };
72
90
  /**
@@ -1,5 +1,6 @@
1
1
  import type { OrchestrationStore } from './store';
2
- import type { ConversationEntry, PostDecisionAction } from './types';
2
+ import type { ConversationEntry, DurableGateResolution, PostDecisionAction } from './types';
3
+ import { DecisionAttemptReceiptStore, type CanonicalGateDecisionRequest, type CanonicalGroupDecisionRequest, type DecisionAttemptReceipt, type DecisionSubmissionResult } from './decision-attempt';
3
4
  /**
4
5
  * Minimal SDK contract for the `applyUserDecision` wire. Structurally
5
6
  * compatible with the full `AppSyncClient.applyUserDecision` signature — keeps
@@ -7,6 +8,9 @@ import type { ConversationEntry, PostDecisionAction } from './types';
7
8
  * inject a stub). Mirrors `ApplyUserDecisionClient` in `<OrchestrationApp>`.
8
9
  */
9
10
  export interface ApplyUserDecisionClient {
11
+ submitGateDecisionV2?(request: CanonicalGateDecisionRequest, canonicalRequestHash: string): Promise<DecisionSubmissionResult>;
12
+ submitGroupDecisionV2?(request: CanonicalGroupDecisionRequest, canonicalRequestHash: string): Promise<DecisionSubmissionResult>;
13
+ /** Retained only as a structural compatibility member for older test/plugin clients. */
10
14
  applyUserDecision(input: {
11
15
  gateId: string;
12
16
  taskId: string;
@@ -18,7 +22,10 @@ export interface ApplyUserDecisionClient {
18
22
  decision: string;
19
23
  postAction: PostDecisionAction;
20
24
  }>;
25
+ /** Test-only structural injection; the production AppSync client omits it. */
26
+ decisionAttemptReceiptStore?: DecisionAttemptReceiptStoreSurface;
21
27
  }
28
+ type DecisionAttemptReceiptStoreSurface = Pick<DecisionAttemptReceiptStore, 'write' | 'markTerminal' | 'abandonUnsubmitted' | 'acknowledge'>;
22
29
  /** Dependencies common to both the TTY + non-TTY gate-resolution paths. */
23
30
  export interface GateDecisionSubmitDeps {
24
31
  store: OrchestrationStore;
@@ -26,6 +33,14 @@ export interface GateDecisionSubmitDeps {
26
33
  appsyncClient: ApplyUserDecisionClient;
27
34
  /** Just-in-time session-key resolver (only consulted when notes are present). */
28
35
  getSessionKey: (sessionId: string) => Promise<string | null>;
36
+ /** Optional override for isolated tests; production uses the durable 0600 store. */
37
+ decisionAttemptReceiptStore?: DecisionAttemptReceiptStoreSurface;
38
+ /**
39
+ * Production effect-completion barrier for a response that loses after the
40
+ * winner Event was already classified. It returns false when no matching
41
+ * completed/in-flight durable handoff exists, leaving the receipt fenced.
42
+ */
43
+ acknowledgeDelayedRejection?: (receipt: DecisionAttemptReceipt, resolution: DurableGateResolution) => Promise<boolean>;
29
44
  /**
30
45
  * Optional — notified after a successful `applyUserDecision` with the engine's
31
46
  * `postAction` (the QuorumLoop wires it to discard the shadow on a
@@ -47,7 +62,7 @@ export interface GateDecisionSubmitDeps {
47
62
  * 2026-06-11, option-4 "Reject and restart"). `undefined` when no shadow was
48
63
  * mapped at decision time (or no loop is wired).
49
64
  */
50
- onTerminalDecision?: (taskId: string, postAction: PostDecisionAction, expectedShadow?: unknown) => void;
65
+ onTerminalDecision?: (taskId: string, postAction: PostDecisionAction, expectedShadow?: unknown) => Promise<void> | void;
51
66
  /**
52
67
  * Optional — resolves the live shadow INSTANCE for a task at decision time. Read
53
68
  * ONCE at the TOP of `submitGateDecision` (BEFORE the `applyUserDecision` SDK
@@ -94,6 +109,7 @@ export declare function submitGateDecision(deps: GateDecisionSubmitDeps, promptE
94
109
  taskId: string;
95
110
  sessionId: string;
96
111
  currentRound: number;
112
+ decisionProtocolVersion?: 1 | 2;
97
113
  decision: string;
98
114
  notes?: string;
99
115
  }): Promise<void>;
@@ -157,6 +173,8 @@ export interface GroupDecisionSubmitDeps {
157
173
  * post-image proof before marking accepted or clearing reviewed bundles.
158
174
  */
159
175
  finalizeAcceptedGroup: (taskGroupId: string) => Promise<void>;
176
+ /** Count the exact distinct materialized paths before accepted-state GC. */
177
+ getRetainedFileCount?: (taskGroupId: string) => Promise<number>;
160
178
  /**
161
179
  * HIGH-1 / LOCK #589-J — persist the CLAIMED-but-not-COMMITted decision to the
162
180
  * durable record, called IMMEDIATELY after the CLAIM phase mints `claimToken`
@@ -99,6 +99,8 @@ export interface GatePromptOption {
99
99
  */
100
100
  export interface GatePromptEnvelope {
101
101
  readonly promptKind: GatePromptKind;
102
+ /** Immutable server stamp; absent only on legacy v1 prompt Events. */
103
+ readonly decisionProtocolVersion?: 1 | 2;
102
104
  readonly taskId: string;
103
105
  readonly gateId: string;
104
106
  readonly currentRound: number;
@@ -120,6 +122,8 @@ export interface GatePromptEnvelope {
120
122
  readonly packetHash?: string;
121
123
  readonly groupEscalation?: {
122
124
  readonly taskGroupId: string;
125
+ readonly groupGeneration?: number;
126
+ readonly protocolVersion: 1 | 2;
123
127
  };
124
128
  }
125
129
  /**
@@ -0,0 +1,10 @@
1
+ import type { Event } from '../types/events';
2
+ import type { DurableGateResolution } from './types';
3
+ /** A known decision may execute only through its exact closed recipe pair. */
4
+ export declare function supportedResolutionLabel(decision: unknown, effectRecipe: unknown): string | null;
5
+ /**
6
+ * Accept only the unforgeable server-authored P16/P20 resolution profile.
7
+ * Returning null is intentionally fail-open for prompt availability: an
8
+ * untrusted Notification remains generic and can never retire an action.
9
+ */
10
+ export declare function classifyDurableGateResolution(event: Event, displayedSessionId: string): DurableGateResolution | null;
@@ -5,6 +5,7 @@ import { OrchestrationStore } from './store';
5
5
  import { AdvisoryAttachmentJournal } from './advisory-attachment-journal';
6
6
  import type { ImageAttachment } from './types';
7
7
  import { type GateDecisionSubmitDeps, type GroupDecisionSubmitDeps } from './gate-decision-submit';
8
+ import { type WorkspaceOutcomeSinkTap } from './workspace-terminal-coordinator';
8
9
  import { type ContinuationCliResult, type ContinuationCliDeps } from './slash-routes/continuation';
9
10
  import { loadReviewerWizardData } from './slash-routes/reviewers';
10
11
  import { createShellEventEmitter } from './emit-shell-event';
@@ -13,7 +14,7 @@ import { type BrainstormPanelActiveAgent } from './brainstorm-quorum';
13
14
  import { type RunAuditBrowserResult } from './audit-runner';
14
15
  import { type RunFlagCommandResult } from './flag-command';
15
16
  import { Tier } from './types';
16
- import type { ConversationEntry, PendingClarification } from './types';
17
+ import type { ConversationEntry, DurableGateResolution, PendingClarification } from './types';
17
18
  import { type GatePromptEnvelope, type DecryptedReviewSummaryInput } from './gate-prompts';
18
19
  import type { Event, UpdateEventStatusInput } from '../types/events';
19
20
  import { type StructuralSummaryGenerator } from '../structural-summary';
@@ -116,6 +117,8 @@ export interface RunOrchestrationShellArgs {
116
117
  * subscription, no team mode, no status node (byte-identical behavior).
117
118
  */
118
119
  localExecutor?: LocalExecutor;
120
+ /** Shell-installed writer-outcome coordinator used by v2 team promotion. */
121
+ workspaceOutcomeTap?: WorkspaceOutcomeSinkTap;
119
122
  /**
120
123
  * CP-12 W3 §3.5 — store-tap installed by `cli.ts` so the team-mode LE's
121
124
  * per-track shell events (W2.b `emitTrackProgress` NOTIFICATIONs carrying
@@ -362,7 +365,7 @@ export declare const TASK_REVIEW_SUMMARY_FETCH_TIMEOUT_MS = 5000;
362
365
  * unit-testable with a mock).
363
366
  */
364
367
  export type ReviewSummaryFetcher = (taskId: string, gateId: string, sessionId: string) => Promise<DecryptedReviewSummaryInput | null>;
365
- export declare function handleSubscribedEvent(event: Event, store: OrchestrationStore, sessionKeyResolver?: (sessionId: string) => Promise<string | null>, reviewSummaryFetcher?: ReviewSummaryFetcher, maybeAutoContinue?: (envelope: GatePromptEnvelope) => boolean): void;
368
+ export declare function handleSubscribedEvent(event: Event, store: OrchestrationStore, sessionKeyResolver?: (sessionId: string) => Promise<string | null>, reviewSummaryFetcher?: ReviewSummaryFetcher, maybeAutoContinue?: (envelope: GatePromptEnvelope) => boolean, onDurableGateResolution?: (resolution: DurableGateResolution) => Promise<void> | void): void;
366
369
  /**
367
370
  * Route a `USER_PROMPT` sent from the paired MOBILE companion into the shell's
368
371
  * SERIALIZED submission pipeline so the planner processes it EXACTLY like a typed
@@ -427,6 +430,7 @@ export declare function createSubscribedEventDispatcher(deps: {
427
430
  sessionKeyResolver?: (sessionId: string) => Promise<string | null>;
428
431
  reviewSummaryFetcher?: ReviewSummaryFetcher;
429
432
  maybeAutoContinue?: (envelope: GatePromptEnvelope) => boolean;
433
+ onDurableGateResolution?: (resolution: DurableGateResolution) => Promise<void> | void;
430
434
  updateEventStatus?: (input: UpdateEventStatusInput) => Promise<unknown>;
431
435
  }): (event: Event) => void;
432
436
  /**
@@ -21,6 +21,7 @@ import type { SubstrateHandle } from '../substrate/types';
21
21
  import type { VerdictClassifier } from '../reviewer/verdict-classifier';
22
22
  import type { AgentKind as ReviewerAgentKind, ReviewerRole, VerdictKind } from '../reviewer/types';
23
23
  import type { OnProgress } from './task-progress';
24
+ import type { WorkspaceOutcomeSink } from './workspace-terminal-coordinator';
24
25
  import { runShellCommand } from '../local-executor/verification-gates/shell-runner';
25
26
  import type { PostDecisionAction, Tier } from './types';
26
27
  import type { ContinuationPacketWriter } from '../continuation/packet-writer';
@@ -552,6 +553,7 @@ export declare function teamRound0GateId(taskId: string, taskGroupId: string, tr
552
553
  export declare class QuorumLoop {
553
554
  private readonly deps;
554
555
  private readonly sleep;
556
+ private workspaceOutcomeSink?;
555
557
  /** The active task this desktop is driving (set at start_task). */
556
558
  private activeTaskId;
557
559
  /** Tasks whose next automatic revise round must start a fresh review baseline. */
@@ -1146,6 +1148,9 @@ export declare class QuorumLoop {
1146
1148
  */
1147
1149
  private lastWorkspaceOutcome;
1148
1150
  constructor(deps: QuorumLoopDeps);
1151
+ /** Installed by the session-owning shell after both objects are constructed. */
1152
+ setWorkspaceOutcomeSink(sink: WorkspaceOutcomeSink | undefined): void;
1153
+ isWorkspaceTaskTerminallyRetired(taskId: string): boolean;
1149
1154
  /** The current active task id (for the recovery poll). */
1150
1155
  get activeTask(): string | null;
1151
1156
  /** Arm an explicit, one-shot comprehensive baseline for the active task. */
@@ -1402,6 +1407,12 @@ export declare class QuorumLoop {
1402
1407
  private ensureShadowLocked;
1403
1408
  /** The shadow for `taskId` (introspection / the terminal promote/discard handlers). */
1404
1409
  shadowForTask(taskId: string): WorkspaceShadow | undefined;
1410
+ /**
1411
+ * Durable-decision restart guard. Once the deterministic successor gate has
1412
+ * entered execution, the task's mapped shadow belongs to that successor and
1413
+ * must never be discarded as the parent gate's rejected snapshot.
1414
+ */
1415
+ gateExecutionHasStarted(gateId: string): boolean;
1405
1416
  /**
1406
1417
  * Spawn the EXECUTE-mode implementor for one round in the task's SHADOW,
1407
1418
  * capture the shadow-vs-real diff, and submit it as the execute-first
@@ -0,0 +1,7 @@
1
+ import type { Event } from '../types/events';
2
+ /**
3
+ * `listSupervisionEvents` returns one globally newest-first DynamoDB walk.
4
+ * Reverse a copy only after every page has been drained so page boundaries do
5
+ * not invert causal prompt → resolution → workspace-outcome ordering.
6
+ */
7
+ export declare function chronologicalSupervisionEvents(newestFirstEvents: readonly Event[]): Event[];