@atolis-hq/wake 0.2.40 → 0.2.41
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.
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AUTONOMOUS_DECISION_AUDIT_EVENT } from '../domain/schema.js';
|
|
2
|
+
function formatValue(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return 'n/a';
|
|
5
|
+
if (typeof value === 'string')
|
|
6
|
+
return value;
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
function auditEventsForWorkItem(events, workItemKey) {
|
|
10
|
+
return events
|
|
11
|
+
.filter((event) => event.workItemKey === workItemKey &&
|
|
12
|
+
event.sourceEventType === AUTONOMOUS_DECISION_AUDIT_EVENT)
|
|
13
|
+
.sort((left, right) => left.ingestedAt.localeCompare(right.ingestedAt));
|
|
14
|
+
}
|
|
15
|
+
export async function runAuditCommand(input) {
|
|
16
|
+
const log = input.log ?? console.log;
|
|
17
|
+
const [workItemKey] = input.args;
|
|
18
|
+
if (workItemKey === undefined) {
|
|
19
|
+
throw new Error('Usage: wake audit <workItemKey>');
|
|
20
|
+
}
|
|
21
|
+
const events = auditEventsForWorkItem(await input.stateStore.listEventEnvelopes(), workItemKey);
|
|
22
|
+
if (events.length === 0) {
|
|
23
|
+
log(`No autonomous audit events found for ${workItemKey}.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
log(`Autonomous audit history for ${workItemKey}`);
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
const payload = event.payload;
|
|
29
|
+
log('');
|
|
30
|
+
log(`${formatValue(payload.timestamp)} ${formatValue(payload.decisionType)}`);
|
|
31
|
+
log(` runId: ${formatValue(payload.runId)}`);
|
|
32
|
+
log(` workflowRevision: ${formatValue(payload.workflowRevision)}`);
|
|
33
|
+
log(` inputsConsidered: ${formatValue(payload.inputsConsidered)}`);
|
|
34
|
+
log(` outcome: ${formatValue(payload.outcome)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { AUTONOMOUS_DECISION_AUDIT_EVENT } from '../domain/schema.js';
|
|
5
|
+
import { createEventEnvelope } from '../lib/event-log.js';
|
|
6
|
+
function stableJson(value) {
|
|
7
|
+
if (value === null || typeof value !== 'object') {
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return `[${value.map((entry) => stableJson(entry)).join(',')}]`;
|
|
12
|
+
}
|
|
13
|
+
const record = value;
|
|
14
|
+
return `{${Object.keys(record)
|
|
15
|
+
.sort()
|
|
16
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
|
|
17
|
+
.join(',')}}`;
|
|
18
|
+
}
|
|
19
|
+
async function promptRevisionInput(input) {
|
|
20
|
+
if (input.action === undefined) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
const promptsRoot = input.config.paths.promptsRoot;
|
|
24
|
+
if (promptsRoot === undefined) {
|
|
25
|
+
return { prompt: { action: input.action, status: 'not-configured' } };
|
|
26
|
+
}
|
|
27
|
+
for (const suffix of ['.md', '.start.md', '.resume.md']) {
|
|
28
|
+
const path = join(promptsRoot, `${input.action}${suffix}`);
|
|
29
|
+
try {
|
|
30
|
+
return {
|
|
31
|
+
prompt: {
|
|
32
|
+
action: input.action,
|
|
33
|
+
path,
|
|
34
|
+
sha256: createHash('sha256')
|
|
35
|
+
.update(await readFile(path, 'utf8'))
|
|
36
|
+
.digest('hex'),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Try the next supported prompt template name.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { prompt: { action: input.action, status: 'missing' } };
|
|
45
|
+
}
|
|
46
|
+
export async function workflowRevision(input) {
|
|
47
|
+
const revisionInput = {
|
|
48
|
+
workflowName: input.workflowName,
|
|
49
|
+
workflow: input.workflow,
|
|
50
|
+
...(await promptRevisionInput({
|
|
51
|
+
config: input.config,
|
|
52
|
+
...(input.action === undefined ? {} : { action: input.action }),
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
return `sha256:${createHash('sha256').update(stableJson(revisionInput)).digest('hex')}`;
|
|
56
|
+
}
|
|
57
|
+
export function createAutonomousDecisionAuditEvent(input) {
|
|
58
|
+
return createEventEnvelope({
|
|
59
|
+
eventId: input.eventId,
|
|
60
|
+
workItemKey: input.workItemKey,
|
|
61
|
+
streamScope: 'work-item',
|
|
62
|
+
direction: 'internal',
|
|
63
|
+
sourceSystem: 'wake',
|
|
64
|
+
sourceEventType: AUTONOMOUS_DECISION_AUDIT_EVENT,
|
|
65
|
+
sourceRefs: {
|
|
66
|
+
...(input.sourceRefs ?? {}),
|
|
67
|
+
runId: input.runId,
|
|
68
|
+
},
|
|
69
|
+
occurredAt: input.timestamp,
|
|
70
|
+
ingestedAt: input.timestamp,
|
|
71
|
+
trigger: 'context-only',
|
|
72
|
+
payload: {
|
|
73
|
+
decisionType: input.decisionType,
|
|
74
|
+
workItemId: input.workItemKey,
|
|
75
|
+
runId: input.runId,
|
|
76
|
+
workflowRevision: input.workflowRevision,
|
|
77
|
+
inputsConsidered: input.inputsConsidered,
|
|
78
|
+
outcome: input.outcome,
|
|
79
|
+
timestamp: input.timestamp,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -17,6 +17,7 @@ import { createOutbox } from './outbox.js';
|
|
|
17
17
|
import { createEventResolver } from './event-resolver.js';
|
|
18
18
|
import { createStaleRunReconciler } from './stale-run-reconciler.js';
|
|
19
19
|
import { createWorkspaceCleanup } from './workspace-cleanup.js';
|
|
20
|
+
import { createAutonomousDecisionAuditEvent, workflowRevision as computeWorkflowRevision, } from './audit-events.js';
|
|
20
21
|
import { createRunLease, isRunLeaseExpired, renewRunLease, runLeaseRenewalIntervalMs, } from './run-lease.js';
|
|
21
22
|
import { currentProcessIdentity, processIdentityMatches } from '../lib/process-identity.js';
|
|
22
23
|
import { readJsonFile, writeJsonFile } from '../lib/json-file.js';
|
|
@@ -172,6 +173,9 @@ export function createTickRunner(deps) {
|
|
|
172
173
|
function eventStampNow() {
|
|
173
174
|
return deps.clock.now().toISOString();
|
|
174
175
|
}
|
|
176
|
+
async function appendAuditEvent(input) {
|
|
177
|
+
await deps.stateStore.appendEventEnvelope(createAutonomousDecisionAuditEvent(input));
|
|
178
|
+
}
|
|
175
179
|
// The watchlist is every resource currently correlated to an open work
|
|
176
180
|
// item, deduplicated by exact resourceUri. It is derived once per tick from
|
|
177
181
|
// the pre-poll projection snapshot (see runTick's ordering note) and handed
|
|
@@ -309,6 +313,38 @@ export function createTickRunner(deps) {
|
|
|
309
313
|
await retryUnconfirmedDeliveries();
|
|
310
314
|
const watchlistProjections = await deps.stateStore.listIssueStates();
|
|
311
315
|
const inboundEvents = await ingestInboundEvents(await deps.workSource.pollEvents({ watch: deriveWatchlist(watchlistProjections) }));
|
|
316
|
+
for (const event of inboundEvents) {
|
|
317
|
+
const trigger = event.payload.trigger;
|
|
318
|
+
const workflowName = typeof event.payload.workflow === 'string' ? event.payload.workflow : undefined;
|
|
319
|
+
const workflow = workflowName === undefined ? undefined : deps.config.workflows[workflowName];
|
|
320
|
+
if (trigger?.kind !== 'schedule' || workflowName === undefined || workflow === undefined) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const timestamp = eventStampNow();
|
|
324
|
+
await appendAuditEvent({
|
|
325
|
+
eventId: `${event.eventId}-audit-trigger-fired`,
|
|
326
|
+
decisionType: 'trigger.fired',
|
|
327
|
+
workItemKey: event.workItemKey,
|
|
328
|
+
runId: event.eventId,
|
|
329
|
+
workflowRevision: await computeWorkflowRevision({
|
|
330
|
+
config: deps.config,
|
|
331
|
+
workflowName,
|
|
332
|
+
workflow,
|
|
333
|
+
}),
|
|
334
|
+
inputsConsidered: {
|
|
335
|
+
workflow: workflowName,
|
|
336
|
+
schedule: workflow.trigger?.schedule,
|
|
337
|
+
trigger,
|
|
338
|
+
sourceEventId: event.eventId,
|
|
339
|
+
},
|
|
340
|
+
outcome: {
|
|
341
|
+
fired: true,
|
|
342
|
+
sourceEventType: event.sourceEventType,
|
|
343
|
+
},
|
|
344
|
+
timestamp,
|
|
345
|
+
sourceRefs: event.sourceRefs,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
312
348
|
const projections = await deps.stateStore.listIssueStates();
|
|
313
349
|
await cleanupClosedIssueWorkspaces(projections);
|
|
314
350
|
if (inboundEvents.length > 0) {
|
|
@@ -661,6 +697,35 @@ export function createTickRunner(deps) {
|
|
|
661
697
|
},
|
|
662
698
|
});
|
|
663
699
|
await deps.stateStore.appendEventEnvelope(approvalCompletedEvent);
|
|
700
|
+
if (automaticApproval) {
|
|
701
|
+
await appendAuditEvent({
|
|
702
|
+
eventId: `${approvalId}-audit-auto-resolution`,
|
|
703
|
+
decisionType: 'approval.auto-resolved',
|
|
704
|
+
workItemKey: candidate.workItemKey,
|
|
705
|
+
runId: approvalId,
|
|
706
|
+
workflowRevision: await computeWorkflowRevision({
|
|
707
|
+
config: deps.config,
|
|
708
|
+
workflowName,
|
|
709
|
+
workflow,
|
|
710
|
+
action: approvalResolution.pendingAction,
|
|
711
|
+
}),
|
|
712
|
+
inputsConsidered: {
|
|
713
|
+
labels: candidate.issue.labels,
|
|
714
|
+
pendingAction: approvalResolution.pendingAction,
|
|
715
|
+
pendingApprovalAllowAutoApproval: candidate.context.pendingApprovalAllowAutoApproval === true,
|
|
716
|
+
},
|
|
717
|
+
outcome: {
|
|
718
|
+
approved: true,
|
|
719
|
+
nextStage,
|
|
720
|
+
reason: approvalResolution.reason,
|
|
721
|
+
},
|
|
722
|
+
timestamp: approvedAt,
|
|
723
|
+
sourceRefs: {
|
|
724
|
+
repo: candidate.issue.repo,
|
|
725
|
+
issueNumber: candidate.issue.number,
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
}
|
|
664
729
|
await projectionUpdater.rebuildFromEvents([approvalCompletedEvent]);
|
|
665
730
|
await deliverOutboundEvent(createLabelsEvent({
|
|
666
731
|
projection: candidate,
|
|
@@ -791,6 +856,38 @@ export function createTickRunner(deps) {
|
|
|
791
856
|
},
|
|
792
857
|
});
|
|
793
858
|
await deps.stateStore.appendEventEnvelope(claimedEvent);
|
|
859
|
+
if (watcherRun && watcherDispatch !== null && watcherTriggerForRun !== undefined) {
|
|
860
|
+
await appendAuditEvent({
|
|
861
|
+
eventId: `${runId}-audit-watcher-dispatched`,
|
|
862
|
+
decisionType: 'watcher.dispatched',
|
|
863
|
+
workItemKey: candidate.workItemKey,
|
|
864
|
+
runId,
|
|
865
|
+
workflowRevision: await computeWorkflowRevision({
|
|
866
|
+
config: deps.config,
|
|
867
|
+
workflowName,
|
|
868
|
+
workflow: deps.config.workflows[workflowName] ?? workflow,
|
|
869
|
+
action,
|
|
870
|
+
}),
|
|
871
|
+
inputsConsidered: {
|
|
872
|
+
parentWorkflowName: watcherDispatch.parentWorkflowName,
|
|
873
|
+
parentStage: watcherDispatch.parentStage,
|
|
874
|
+
watcherIndex: watcherDispatch.watcherIndex,
|
|
875
|
+
watcherStatus: watcherStatus(candidate),
|
|
876
|
+
trigger: watcherTriggerForRun,
|
|
877
|
+
},
|
|
878
|
+
outcome: {
|
|
879
|
+
dispatched: true,
|
|
880
|
+
targetWorkflowName: workflowName,
|
|
881
|
+
action,
|
|
882
|
+
claimedStage,
|
|
883
|
+
},
|
|
884
|
+
timestamp: claimedAt,
|
|
885
|
+
sourceRefs: {
|
|
886
|
+
repo: candidate.issue.repo,
|
|
887
|
+
issueNumber: candidate.issue.number,
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
}
|
|
794
891
|
await transitionRunLifecycle('CLAIMED');
|
|
795
892
|
await projectionUpdater.rebuildFromEvents([claimedEvent]);
|
|
796
893
|
if (watcherStateKeyForRun !== undefined && watcherTriggerForRun !== undefined) {
|
|
@@ -907,6 +1004,44 @@ export function createTickRunner(deps) {
|
|
|
907
1004
|
runnerResult,
|
|
908
1005
|
occurredAt: finishedAt,
|
|
909
1006
|
});
|
|
1007
|
+
await appendAuditEvent({
|
|
1008
|
+
eventId: `${runId}-audit-review-verdict`,
|
|
1009
|
+
decisionType: 'review.verdict',
|
|
1010
|
+
workItemKey: candidate.workItemKey,
|
|
1011
|
+
runId,
|
|
1012
|
+
workflowRevision: await computeWorkflowRevision({
|
|
1013
|
+
config: deps.config,
|
|
1014
|
+
workflowName,
|
|
1015
|
+
workflow: deps.config.workflows[workflowName] ?? workflow,
|
|
1016
|
+
action,
|
|
1017
|
+
}),
|
|
1018
|
+
inputsConsidered: {
|
|
1019
|
+
sourceRevision,
|
|
1020
|
+
watcherTrigger: watcherTriggerForRun,
|
|
1021
|
+
verifiedTargetResourceUri: prReviewTargetResourceUri,
|
|
1022
|
+
rawSentinel,
|
|
1023
|
+
},
|
|
1024
|
+
outcome: {
|
|
1025
|
+
sentinel,
|
|
1026
|
+
verdict: prReviewTargetResourceUri === null
|
|
1027
|
+
? 'uncertain'
|
|
1028
|
+
: sentinel === 'DONE'
|
|
1029
|
+
? 'approved'
|
|
1030
|
+
: sentinel === 'FAILED'
|
|
1031
|
+
? 'changes-requested'
|
|
1032
|
+
: 'uncertain',
|
|
1033
|
+
reasoning: parsedRunnerResult.body,
|
|
1034
|
+
runner: {
|
|
1035
|
+
model: runnerResult.model,
|
|
1036
|
+
cli: runnerResult.cli,
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1039
|
+
timestamp: finishedAt,
|
|
1040
|
+
sourceRefs: {
|
|
1041
|
+
repo: candidate.issue.repo,
|
|
1042
|
+
issueNumber: candidate.issue.number,
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
910
1045
|
}
|
|
911
1046
|
else if (workspaceMode === 'branch') {
|
|
912
1047
|
await registerReportedArtifacts({
|
|
@@ -210,6 +210,7 @@ export const WORK_ITEM_CREATED_EVENT = 'wake.workitem.created';
|
|
|
210
210
|
export const CORRELATION_REGISTERED_EVENT = 'wake.correlation.registered';
|
|
211
211
|
export const CORRELATION_RETRACTED_EVENT = 'wake.correlation.retracted';
|
|
212
212
|
export const CORRELATION_PRIMARY_CONFLICT_EVENT = 'wake.correlation.primary-conflict';
|
|
213
|
+
export const AUTONOMOUS_DECISION_AUDIT_EVENT = 'wake.audit.autonomous-decision';
|
|
213
214
|
/**
|
|
214
215
|
* Shared key for events whose resource failed mint qualification (spec D1').
|
|
215
216
|
* Never a real work item: `readIssueState(UNRESOLVED_WORK_ITEM_KEY)` always
|
package/dist/src/main.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createGitHubArtifactVerifier } from './adapters/github/github-artifact-
|
|
|
19
19
|
import { createGitHubClient } from './adapters/github/github-client.js';
|
|
20
20
|
import { createGitHubIssuesWorkSource } from './adapters/github/github-issues-work-source.js';
|
|
21
21
|
import { createGitHubPullRequestActivitySource } from './adapters/github/github-pull-request-activity-source.js';
|
|
22
|
+
import { runAuditCommand } from './cli/audit-command.js';
|
|
22
23
|
import { runCorrelateCommand } from './cli/correlate-command.js';
|
|
23
24
|
import { runDoctorCommand } from './cli/doctor-command.js';
|
|
24
25
|
import { runInitCommand } from './cli/init-command.js';
|
|
@@ -701,6 +702,15 @@ async function runCorrelate(args) {
|
|
|
701
702
|
readFlag: readFlagBeforeCommandTerminator,
|
|
702
703
|
});
|
|
703
704
|
}
|
|
705
|
+
async function runAudit(args) {
|
|
706
|
+
const wakeRoot = resolve(readFlagBeforeCommandTerminator('--wake-root', args) ?? process.cwd());
|
|
707
|
+
const stateStore = createStateStore({ wakeRoot });
|
|
708
|
+
await stateStore.ensureWakeRoot();
|
|
709
|
+
await runAuditCommand({
|
|
710
|
+
args,
|
|
711
|
+
stateStore,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
704
714
|
async function runSmoke(args) {
|
|
705
715
|
const runtime = await buildRuntime(args);
|
|
706
716
|
const explicitKind = args[0] === 'claude' || args[0] === 'codex' || args[0] === 'cursor' ? args[0] : undefined;
|
|
@@ -783,6 +793,7 @@ export function printUsage(stream) {
|
|
|
783
793
|
' wake stop Stop the sandbox container gracefully',
|
|
784
794
|
' wake smoke Smoke-test the configured runner',
|
|
785
795
|
' wake ui Run the control-plane UI server',
|
|
796
|
+
' wake audit Show autonomous decision audit history',
|
|
786
797
|
' wake correlate Manually correlate a resource to a work item',
|
|
787
798
|
' wake doctor Diagnose config/GitHub/Docker/sandbox setup problems',
|
|
788
799
|
' wake --version Print the installed Wake version',
|
|
@@ -792,14 +803,22 @@ export function printUsage(stream) {
|
|
|
792
803
|
' 1. wake init ./wake-home',
|
|
793
804
|
' 2. cd wake-home && wake start',
|
|
794
805
|
'',
|
|
795
|
-
'Runtime commands (tick/start/ui/smoke/correlate/validate-state) auto-delegate into the sandbox',
|
|
806
|
+
'Runtime commands (tick/start/ui/smoke/audit/correlate/validate-state) auto-delegate into the sandbox',
|
|
796
807
|
'when docker/Dockerfile exists at --wake-root (i.e. after `wake sandbox build`),',
|
|
797
808
|
'defaulting --wake-root to the current directory. Pass --no-sandbox to run',
|
|
798
809
|
'directly on the host instead.',
|
|
799
810
|
'',
|
|
800
811
|
].join('\n'));
|
|
801
812
|
}
|
|
802
|
-
const runtimeCommands = new Set([
|
|
813
|
+
const runtimeCommands = new Set([
|
|
814
|
+
'tick',
|
|
815
|
+
'start',
|
|
816
|
+
'ui',
|
|
817
|
+
'smoke',
|
|
818
|
+
'audit',
|
|
819
|
+
'correlate',
|
|
820
|
+
'validate-state',
|
|
821
|
+
]);
|
|
803
822
|
export async function dispatchMainCommand(input) {
|
|
804
823
|
const command = input.args[0] ?? 'help';
|
|
805
824
|
if (command === '--version' || command === '-v') {
|
|
@@ -855,6 +874,12 @@ export async function dispatchMainCommand(input) {
|
|
|
855
874
|
else if (command === 'ui') {
|
|
856
875
|
await input.runUi(hostArgs);
|
|
857
876
|
}
|
|
877
|
+
else if (command === 'audit') {
|
|
878
|
+
if (input.runAudit === undefined) {
|
|
879
|
+
throw new CliUsageError('audit command is not available in this runtime');
|
|
880
|
+
}
|
|
881
|
+
await input.runAudit(hostArgs);
|
|
882
|
+
}
|
|
858
883
|
else if (command === 'smoke') {
|
|
859
884
|
await input.runSmoke(hostArgs);
|
|
860
885
|
}
|
|
@@ -991,6 +1016,7 @@ async function main() {
|
|
|
991
1016
|
runStart,
|
|
992
1017
|
runSmoke,
|
|
993
1018
|
runUi,
|
|
1019
|
+
runAudit,
|
|
994
1020
|
runCorrelate,
|
|
995
1021
|
runValidateState,
|
|
996
1022
|
execIntoSandbox: async (commandArgs) => {
|
package/dist/src/version.js
CHANGED