@atolis-hq/wake 0.3.20 → 0.3.22
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/src/bootstrap/composition-root.js +1 -3
- package/dist/src/bootstrap/index.js +0 -1
- package/dist/src/bootstrap/integration-runtime.js +3 -5
- package/dist/src/bootstrap/persistence-composition.js +1 -9
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/integrations/github/application/inbound-review-signals.js +4 -0
- package/dist/src/orchestration/application/resource-transition-matching.js +13 -2
- package/dist/src/orchestration/application/resource-transition-reactor.js +3 -3
- package/dist/src/orchestration/domain/signal-policy.js +9 -1
- package/package.json +1 -1
- package/prompts/refine.md +8 -0
- package/dist/src/bootstrap/resource-transition-ordering.js +0 -67
|
@@ -31,7 +31,7 @@ export async function createCompositionRoot(wakeRoot, options = {}) {
|
|
|
31
31
|
const maintenance = createUpdateMaintenanceLease(paths.wakeRoot);
|
|
32
32
|
const clock = options.clock ?? new SystemClock();
|
|
33
33
|
const ids = new UlidIdGenerator();
|
|
34
|
-
const { journal, projections, checkpoints
|
|
34
|
+
const { journal, projections, checkpoints } = composePersistence(paths, clock, options);
|
|
35
35
|
const work = createWorkService(journal);
|
|
36
36
|
const lookup = createResourceLookup({ journal, projections });
|
|
37
37
|
const resources = createResourceService(journal, lookup);
|
|
@@ -125,8 +125,6 @@ export async function createCompositionRoot(wakeRoot, options = {}) {
|
|
|
125
125
|
ids,
|
|
126
126
|
wakeRoot,
|
|
127
127
|
scheduleCheckpoints: options.scheduleCheckpoints ?? new FileScheduleCheckpointStore(paths.dataRoot),
|
|
128
|
-
resourceTransitionOrdering,
|
|
129
|
-
resourceTransitionTriggers,
|
|
130
128
|
...(options.decorateDeliveryAdapter === undefined
|
|
131
129
|
? {}
|
|
132
130
|
: { decorateDeliveryAdapter: options.decorateDeliveryAdapter }),
|
|
@@ -33,7 +33,6 @@ export function composeControlPlaneHosts(advanceOnce, sleep) {
|
|
|
33
33
|
}
|
|
34
34
|
export * from './composition-root.js';
|
|
35
35
|
export * from './resource-transition-evidence.js';
|
|
36
|
-
export * from './resource-transition-ordering.js';
|
|
37
36
|
export * from './analytics-projection.js';
|
|
38
37
|
export * from './board-projection.js';
|
|
39
38
|
export * from './config/load-config.js';
|
|
@@ -110,13 +110,11 @@ export async function composeIntegrationRuntime(input) {
|
|
|
110
110
|
},
|
|
111
111
|
],
|
|
112
112
|
});
|
|
113
|
-
input.
|
|
114
|
-
input.
|
|
115
|
-
const resourceTransitions = createResourceTransitionReactor(input.orchestration, resourceTransitionEvidence, input.journal, input.checkpoints, input.resourceTransitionOrdering);
|
|
116
|
-
input.orchestration.setAcceptSignalOperationCoordinator((operation) => input.resourceTransitionOrdering(async () => {
|
|
113
|
+
const resourceTransitions = createResourceTransitionReactor(input.orchestration, resourceTransitionEvidence, input.journal, input.checkpoints);
|
|
114
|
+
input.orchestration.setAcceptSignalOperationCoordinator(async (operation) => {
|
|
117
115
|
await resourceTransitions.drain();
|
|
118
116
|
return operation();
|
|
119
|
-
})
|
|
117
|
+
});
|
|
120
118
|
const outcomes = new DeliveryOutcomeReactor(input.journal, input.checkpoints, input.orchestration);
|
|
121
119
|
const catchUpProjections = async () => {
|
|
122
120
|
await projectionRunner.runRegisteredOnce();
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { FileCheckpointStore, FileEventJournal, FileProjectionStore, } from '../persistence/index.js';
|
|
2
|
-
import { createResourceTransitionOrdering, createTriggerAwareEventJournal, createTriggerRegistry, } from './resource-transition-ordering.js';
|
|
3
2
|
function identity(value) {
|
|
4
3
|
return value;
|
|
5
4
|
}
|
|
6
5
|
export function composePersistence(paths, clock, options) {
|
|
7
|
-
const resourceTransitionOrdering = createResourceTransitionOrdering(options.journal === undefined
|
|
8
|
-
? `${paths.locksRoot}/resource-transition-ordering.lock`
|
|
9
|
-
: undefined);
|
|
10
|
-
const resourceTransitionTriggers = createTriggerRegistry();
|
|
11
|
-
const journal = createTriggerAwareEventJournal((options.decorateJournal ?? identity)(options.journal ?? new FileEventJournal(paths.dataRoot, clock)), resourceTransitionTriggers, resourceTransitionOrdering);
|
|
12
6
|
return {
|
|
13
|
-
journal,
|
|
7
|
+
journal: (options.decorateJournal ?? identity)(options.journal ?? new FileEventJournal(paths.dataRoot, clock)),
|
|
14
8
|
projections: (options.decorateProjections ?? identity)(options.projections ?? new FileProjectionStore(paths.dataRoot)),
|
|
15
9
|
checkpoints: (options.decorateCheckpoints ?? identity)(options.checkpoints ?? new FileCheckpointStore(paths.dataRoot)),
|
|
16
|
-
resourceTransitionOrdering,
|
|
17
|
-
resourceTransitionTriggers,
|
|
18
10
|
};
|
|
19
11
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActivityOutcomeKind, ReviewActorKind, ReviewDecisionKind, ReviewerAuthorizationSource, createPullRequestService, } from '../../../activities/index.js';
|
|
2
|
+
import { ApprovalAuthorityKind } from '../../../orchestration/index.js';
|
|
2
3
|
import { BuiltInResourceKind, ResourceCorrelationRole, ResourceStreamKind, resourceId, } from '../../../resources/index.js';
|
|
3
4
|
import { UnknownGitHubIdentity } from '../contracts/vocabulary.js';
|
|
4
5
|
import { commandContext } from './inbound-context.js';
|
|
@@ -143,6 +144,9 @@ async function applyWorkflowSignal(input) {
|
|
|
143
144
|
evidenceId: event.eventId,
|
|
144
145
|
},
|
|
145
146
|
providerEventId: event.eventId,
|
|
147
|
+
...(event.payload.actor.kind === ReviewActorKind.Human
|
|
148
|
+
? { authority: { kind: ApprovalAuthorityKind.Human } }
|
|
149
|
+
: {}),
|
|
146
150
|
}, commandContext(event));
|
|
147
151
|
}
|
|
148
152
|
else if (resumeBlockedOnChanges) {
|
|
@@ -66,7 +66,18 @@ export async function acceptResourceTransition(repository, workflows, id, target
|
|
|
66
66
|
causationId: `${context.commandId}:${evidenceId}`,
|
|
67
67
|
consent: true,
|
|
68
68
|
});
|
|
69
|
-
if (decision.kind === 'append')
|
|
70
|
-
|
|
69
|
+
if (decision.kind === 'append') {
|
|
70
|
+
try {
|
|
71
|
+
await repository.append(id, loaded.sequence, decision.events);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const reloaded = await repository.load(id);
|
|
75
|
+
if (reloaded.view !== null &&
|
|
76
|
+
(reloaded.view.acceptedSignalIds.includes(evidenceId) ||
|
|
77
|
+
reloaded.view.waitingFor === undefined))
|
|
78
|
+
return reloaded.view;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
71
82
|
return (await repository.load(id)).view;
|
|
72
83
|
}
|
|
@@ -3,7 +3,7 @@ import { selectOrchestrationEvent } from '../contracts/event-decoder.js';
|
|
|
3
3
|
import { OrchestrationEventType } from '../contracts/events.js';
|
|
4
4
|
const checkpoint = 'reactor:orchestration.resource-transition';
|
|
5
5
|
const batchSize = 100;
|
|
6
|
-
export function createResourceTransitionReactor(orchestration, evidence, journal, checkpoints
|
|
6
|
+
export function createResourceTransitionReactor(orchestration, evidence, journal, checkpoints) {
|
|
7
7
|
let queue = Promise.resolve();
|
|
8
8
|
const react = async (event, context) => {
|
|
9
9
|
const orchestrationEvent = selectOrchestrationEvent(event);
|
|
@@ -40,10 +40,10 @@ export function createResourceTransitionReactor(orchestration, evidence, journal
|
|
|
40
40
|
return {
|
|
41
41
|
react,
|
|
42
42
|
runOnce(limit = batchSize) {
|
|
43
|
-
return
|
|
43
|
+
return serialize(() => runBatch(limit));
|
|
44
44
|
},
|
|
45
45
|
drain() {
|
|
46
|
-
return
|
|
46
|
+
return serialize(async () => {
|
|
47
47
|
let total = 0;
|
|
48
48
|
let processed;
|
|
49
49
|
do {
|
|
@@ -52,7 +52,10 @@ function signalRejectionReason(state, input) {
|
|
|
52
52
|
if (state.acceptedSignalIds.includes(signal.providerEventId))
|
|
53
53
|
return 'provider signal was already accepted';
|
|
54
54
|
const expected = state.waitingFor;
|
|
55
|
-
if (
|
|
55
|
+
if (expected === undefined)
|
|
56
|
+
return 'WorkflowInstance is not waiting for a signal';
|
|
57
|
+
if (state.status !== WorkflowStatus.Waiting &&
|
|
58
|
+
!isBlockedHumanAuthorizedWait(state, signal, expected))
|
|
56
59
|
return 'WorkflowInstance is not waiting for a signal';
|
|
57
60
|
if (!matchesExpectation(signal, expected))
|
|
58
61
|
return 'signal does not match the current expectation';
|
|
@@ -66,6 +69,11 @@ function signalRejectionReason(state, input) {
|
|
|
66
69
|
return 'watch-gate signal outcome is neither done nor rejected';
|
|
67
70
|
return null;
|
|
68
71
|
}
|
|
72
|
+
function isBlockedHumanAuthorizedWait(state, signal, expected) {
|
|
73
|
+
return (state.status === WorkflowStatus.Blocked &&
|
|
74
|
+
signal.authority?.kind === ApprovalAuthorityKind.Human &&
|
|
75
|
+
expected.from?.some((authority) => authority.kind === ApprovalAuthorityKind.Human) === true);
|
|
76
|
+
}
|
|
69
77
|
function signalAuthorityAccepted(signal, expected, consent) {
|
|
70
78
|
if (expected.from === undefined)
|
|
71
79
|
return true;
|
package/package.json
CHANGED
package/prompts/refine.md
CHANGED
|
@@ -26,6 +26,14 @@ Your job here is only to:
|
|
|
26
26
|
response (do not try to save it to a file).
|
|
27
27
|
- If underspecified, ask the smallest set of clarifying questions needed.
|
|
28
28
|
|
|
29
|
+
"Underspecified" means the requirement or acceptance criteria are unclear or
|
|
30
|
+
contradictory — not that an implementation detail is undecided. Investigate
|
|
31
|
+
the codebase and decide design questions (module ownership, event/projection
|
|
32
|
+
shape, which pattern to extend); state the decision as an assumption in your
|
|
33
|
+
plan instead of asking. plan-review checks stated assumptions and rejects a
|
|
34
|
+
wrong one. Block only when the ticket's own intent can't be determined by
|
|
35
|
+
reading the repository.
|
|
36
|
+
|
|
29
37
|
Wake will provide the issue data and comments below in a delimited untrusted
|
|
30
38
|
data block.
|
|
31
39
|
{{else}}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { acquireFileLock } from '../persistence/index.js';
|
|
3
|
-
export function createTriggerRegistry() {
|
|
4
|
-
const triggers = new Set();
|
|
5
|
-
let frozen = false;
|
|
6
|
-
return {
|
|
7
|
-
register(values) {
|
|
8
|
-
if (frozen)
|
|
9
|
-
throw new Error('Resource transition trigger registry is frozen');
|
|
10
|
-
values.forEach((value) => triggers.add(value));
|
|
11
|
-
},
|
|
12
|
-
freeze() {
|
|
13
|
-
frozen = true;
|
|
14
|
-
},
|
|
15
|
-
includes(eventType) {
|
|
16
|
-
return triggers.has(eventType);
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function createResourceTransitionOrdering(lockPath) {
|
|
21
|
-
const active = new AsyncLocalStorage();
|
|
22
|
-
let queue = Promise.resolve();
|
|
23
|
-
const coordinate = (operation) => {
|
|
24
|
-
if (active.getStore() === true)
|
|
25
|
-
return operation();
|
|
26
|
-
const result = queue.then(async () => {
|
|
27
|
-
const lock = lockPath === undefined ? undefined : await waitForFileLock(lockPath);
|
|
28
|
-
try {
|
|
29
|
-
return await active.run(true, operation);
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
await lock?.release();
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
queue = result.catch(() => { });
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
return coordinate;
|
|
39
|
-
}
|
|
40
|
-
async function waitForFileLock(lockPath) {
|
|
41
|
-
for (;;) {
|
|
42
|
-
const lock = await acquireFileLock(lockPath, {
|
|
43
|
-
staleAfterMs: 60_000,
|
|
44
|
-
staleRequiresDeadProcess: true,
|
|
45
|
-
});
|
|
46
|
-
if (lock.acquired)
|
|
47
|
-
return lock;
|
|
48
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export function createTriggerAwareEventJournal(journal, registry, coordinate) {
|
|
52
|
-
return {
|
|
53
|
-
append(stream, expectedSequence, events) {
|
|
54
|
-
const append = () => journal.append(stream, expectedSequence, events);
|
|
55
|
-
return events.some(({ eventType }) => registry.includes(eventType))
|
|
56
|
-
? coordinate(append)
|
|
57
|
-
: append();
|
|
58
|
-
},
|
|
59
|
-
readStream: (stream) => journal.readStream(stream),
|
|
60
|
-
readAll: (afterGlobalPosition, limit) => journal.readAll(afterGlobalPosition, limit),
|
|
61
|
-
...(journal.readLatest === undefined
|
|
62
|
-
? {}
|
|
63
|
-
: {
|
|
64
|
-
readLatest: (beforeGlobalPosition, limit) => journal.readLatest(beforeGlobalPosition, limit),
|
|
65
|
-
}),
|
|
66
|
-
};
|
|
67
|
-
}
|