@open-agent-toolkit/cli 0.2.65 → 0.2.66
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +19 -1
- package/assets/docs/cli-utilities/index.md +3 -0
- package/assets/docs/cli-utilities/remote-project-management.md +191 -0
- package/assets/docs/reference/cli-reference.md +12 -1
- package/assets/docs/reference/file-locations.md +22 -0
- package/assets/docs/reference/oat-directory-structure.md +30 -17
- package/assets/docs/reference/troubleshooting.md +44 -0
- package/assets/docs/workflows/skills/index.md +33 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-doctor/SKILL.md +4 -4
- package/assets/skills/oat-pjm-remote/SKILL.md +101 -0
- package/assets/skills/oat-pjm-remote/references/external-action-protocol.md +43 -0
- package/dist/commands/backlog/new.d.ts +1 -0
- package/dist/commands/backlog/new.d.ts.map +1 -1
- package/dist/commands/backlog/new.js +3 -1
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +232 -1
- package/dist/commands/pjm/doctor.d.ts +2 -0
- package/dist/commands/pjm/doctor.d.ts.map +1 -1
- package/dist/commands/pjm/doctor.js +71 -1
- package/dist/commands/pjm/index.d.ts +2 -0
- package/dist/commands/pjm/index.d.ts.map +1 -1
- package/dist/commands/pjm/index.js +3 -0
- package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts +60 -0
- package/dist/commands/pjm/remote/__integration__/lifecycle-harness.d.ts.map +1 -0
- package/dist/commands/pjm/remote/__integration__/lifecycle-harness.js +151 -0
- package/dist/commands/pjm/remote/association.d.ts +27 -0
- package/dist/commands/pjm/remote/association.d.ts.map +1 -0
- package/dist/commands/pjm/remote/association.js +68 -0
- package/dist/commands/pjm/remote/authority.d.ts +210 -0
- package/dist/commands/pjm/remote/authority.d.ts.map +1 -0
- package/dist/commands/pjm/remote/authority.js +341 -0
- package/dist/commands/pjm/remote/batch.d.ts +15 -0
- package/dist/commands/pjm/remote/batch.d.ts.map +1 -0
- package/dist/commands/pjm/remote/batch.js +126 -0
- package/dist/commands/pjm/remote/closeout.d.ts +69 -0
- package/dist/commands/pjm/remote/closeout.d.ts.map +1 -0
- package/dist/commands/pjm/remote/closeout.js +208 -0
- package/dist/commands/pjm/remote/create-binding.d.ts +62 -0
- package/dist/commands/pjm/remote/create-binding.d.ts.map +1 -0
- package/dist/commands/pjm/remote/create-binding.js +126 -0
- package/dist/commands/pjm/remote/credential-safety.d.ts +7 -0
- package/dist/commands/pjm/remote/credential-safety.d.ts.map +1 -0
- package/dist/commands/pjm/remote/credential-safety.js +21 -0
- package/dist/commands/pjm/remote/discussion.d.ts +49 -0
- package/dist/commands/pjm/remote/discussion.d.ts.map +1 -0
- package/dist/commands/pjm/remote/discussion.js +124 -0
- package/dist/commands/pjm/remote/doctor.d.ts +22 -0
- package/dist/commands/pjm/remote/doctor.d.ts.map +1 -0
- package/dist/commands/pjm/remote/doctor.js +380 -0
- package/dist/commands/pjm/remote/external-action.d.ts +208 -0
- package/dist/commands/pjm/remote/external-action.d.ts.map +1 -0
- package/dist/commands/pjm/remote/external-action.js +367 -0
- package/dist/commands/pjm/remote/host-execution.d.ts +28 -0
- package/dist/commands/pjm/remote/host-execution.d.ts.map +1 -0
- package/dist/commands/pjm/remote/host-execution.js +63 -0
- package/dist/commands/pjm/remote/index.d.ts +41 -0
- package/dist/commands/pjm/remote/index.d.ts.map +1 -0
- package/dist/commands/pjm/remote/index.js +280 -0
- package/dist/commands/pjm/remote/jira-adf.d.ts +35 -0
- package/dist/commands/pjm/remote/jira-adf.d.ts.map +1 -0
- package/dist/commands/pjm/remote/jira-adf.js +169 -0
- package/dist/commands/pjm/remote/lifecycle.d.ts +163 -0
- package/dist/commands/pjm/remote/lifecycle.d.ts.map +1 -0
- package/dist/commands/pjm/remote/lifecycle.js +224 -0
- package/dist/commands/pjm/remote/local-projection.d.ts +30 -0
- package/dist/commands/pjm/remote/local-projection.d.ts.map +1 -0
- package/dist/commands/pjm/remote/local-projection.js +95 -0
- package/dist/commands/pjm/remote/managed-markdown.d.ts +30 -0
- package/dist/commands/pjm/remote/managed-markdown.d.ts.map +1 -0
- package/dist/commands/pjm/remote/managed-markdown.js +128 -0
- package/dist/commands/pjm/remote/migrate.d.ts +14 -0
- package/dist/commands/pjm/remote/migrate.d.ts.map +1 -0
- package/dist/commands/pjm/remote/migrate.js +32 -0
- package/dist/commands/pjm/remote/operation-state.d.ts +15 -0
- package/dist/commands/pjm/remote/operation-state.d.ts.map +1 -0
- package/dist/commands/pjm/remote/operation-state.js +143 -0
- package/dist/commands/pjm/remote/outbound-projection-safety.d.ts +25 -0
- package/dist/commands/pjm/remote/outbound-projection-safety.d.ts.map +1 -0
- package/dist/commands/pjm/remote/outbound-projection-safety.js +99 -0
- package/dist/commands/pjm/remote/output.d.ts +60 -0
- package/dist/commands/pjm/remote/output.d.ts.map +1 -0
- package/dist/commands/pjm/remote/output.js +48 -0
- package/dist/commands/pjm/remote/preview.d.ts +88 -0
- package/dist/commands/pjm/remote/preview.d.ts.map +1 -0
- package/dist/commands/pjm/remote/preview.js +154 -0
- package/dist/commands/pjm/remote/provider-conformance.d.ts +13 -0
- package/dist/commands/pjm/remote/provider-conformance.d.ts.map +1 -0
- package/dist/commands/pjm/remote/provider-conformance.js +37 -0
- package/dist/commands/pjm/remote/provider.d.ts +57 -0
- package/dist/commands/pjm/remote/provider.d.ts.map +1 -0
- package/dist/commands/pjm/remote/provider.js +28 -0
- package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts +46 -0
- package/dist/commands/pjm/remote/providers/github-publication-safety.d.ts.map +1 -0
- package/dist/commands/pjm/remote/providers/github-publication-safety.js +126 -0
- package/dist/commands/pjm/remote/providers/github.d.ts +207 -0
- package/dist/commands/pjm/remote/providers/github.d.ts.map +1 -0
- package/dist/commands/pjm/remote/providers/github.js +1456 -0
- package/dist/commands/pjm/remote/providers/jira.d.ts +245 -0
- package/dist/commands/pjm/remote/providers/jira.d.ts.map +1 -0
- package/dist/commands/pjm/remote/providers/jira.js +1650 -0
- package/dist/commands/pjm/remote/providers/linear.d.ts +182 -0
- package/dist/commands/pjm/remote/providers/linear.d.ts.map +1 -0
- package/dist/commands/pjm/remote/providers/linear.js +1289 -0
- package/dist/commands/pjm/remote/purpose-policy.d.ts +29 -0
- package/dist/commands/pjm/remote/purpose-policy.d.ts.map +1 -0
- package/dist/commands/pjm/remote/purpose-policy.js +118 -0
- package/dist/commands/pjm/remote/reconcile.d.ts +42 -0
- package/dist/commands/pjm/remote/reconcile.d.ts.map +1 -0
- package/dist/commands/pjm/remote/reconcile.js +101 -0
- package/dist/commands/pjm/remote/resolution.d.ts +122 -0
- package/dist/commands/pjm/remote/resolution.d.ts.map +1 -0
- package/dist/commands/pjm/remote/resolution.js +243 -0
- package/dist/commands/pjm/remote/schema.d.ts +9309 -0
- package/dist/commands/pjm/remote/schema.d.ts.map +1 -0
- package/dist/commands/pjm/remote/schema.js +1503 -0
- package/dist/commands/pjm/remote/service.d.ts +20 -0
- package/dist/commands/pjm/remote/service.d.ts.map +1 -0
- package/dist/commands/pjm/remote/service.js +5121 -0
- package/dist/commands/pjm/remote/shared-storage.d.ts +36 -0
- package/dist/commands/pjm/remote/shared-storage.d.ts.map +1 -0
- package/dist/commands/pjm/remote/shared-storage.js +110 -0
- package/dist/commands/pjm/remote/snapshot.d.ts +32 -0
- package/dist/commands/pjm/remote/snapshot.d.ts.map +1 -0
- package/dist/commands/pjm/remote/snapshot.js +140 -0
- package/dist/commands/pjm/remote/storage-locator.d.ts +29 -0
- package/dist/commands/pjm/remote/storage-locator.d.ts.map +1 -0
- package/dist/commands/pjm/remote/storage-locator.js +62 -0
- package/dist/commands/pjm/remote/store.d.ts +85 -0
- package/dist/commands/pjm/remote/store.d.ts.map +1 -0
- package/dist/commands/pjm/remote/store.js +553 -0
- package/dist/commands/pjm/remote/verification.d.ts +40 -0
- package/dist/commands/pjm/remote/verification.d.ts.map +1 -0
- package/dist/commands/pjm/remote/verification.js +100 -0
- package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.js +1 -0
- package/dist/config/oat-config.d.ts +26 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +186 -2
- package/package.json +2 -2
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { validatePreviewApproval, } from './preview.js';
|
|
3
|
+
import { semanticDigest } from './provider.js';
|
|
4
|
+
const TimestampSchema = z.string().datetime({ offset: true });
|
|
5
|
+
const InvocationScopeSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
operationClass: z.enum([
|
|
8
|
+
'create',
|
|
9
|
+
'update-fields',
|
|
10
|
+
'transition',
|
|
11
|
+
'annotate',
|
|
12
|
+
'delete',
|
|
13
|
+
'relink',
|
|
14
|
+
'detach',
|
|
15
|
+
'recreate',
|
|
16
|
+
]),
|
|
17
|
+
targetId: z.string().min(1).max(255),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
export const ProductionMutationInvocationSchema = z.discriminatedUnion('kind', [
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
schemaVersion: z.literal(1),
|
|
24
|
+
kind: z.literal('interactive'),
|
|
25
|
+
sourceId: z.string().min(1).max(255),
|
|
26
|
+
invocationId: z.string().min(1).max(255),
|
|
27
|
+
issuedAt: TimestampSchema,
|
|
28
|
+
expiresAt: TimestampSchema,
|
|
29
|
+
instruction: InvocationScopeSchema.extend({
|
|
30
|
+
evidenceDigest: z.string().min(1).max(512),
|
|
31
|
+
}).strict(),
|
|
32
|
+
approval: z
|
|
33
|
+
.object({
|
|
34
|
+
previewDigest: z.string().min(1).max(512),
|
|
35
|
+
operationClass: z.enum([
|
|
36
|
+
'create',
|
|
37
|
+
'update-fields',
|
|
38
|
+
'transition',
|
|
39
|
+
'annotate',
|
|
40
|
+
'delete',
|
|
41
|
+
'relink',
|
|
42
|
+
'detach',
|
|
43
|
+
'recreate',
|
|
44
|
+
]),
|
|
45
|
+
approvedAt: TimestampSchema,
|
|
46
|
+
actor: z.string().min(1).max(255),
|
|
47
|
+
source: z.string().min(1).max(255),
|
|
48
|
+
})
|
|
49
|
+
.strict()
|
|
50
|
+
.nullable(),
|
|
51
|
+
})
|
|
52
|
+
.strict(),
|
|
53
|
+
z
|
|
54
|
+
.object({
|
|
55
|
+
schemaVersion: z.literal(1),
|
|
56
|
+
kind: z.literal('workflow'),
|
|
57
|
+
sourceId: z.string().min(1).max(255),
|
|
58
|
+
invocationId: z.string().min(1).max(255),
|
|
59
|
+
issuedAt: TimestampSchema,
|
|
60
|
+
expiresAt: TimestampSchema,
|
|
61
|
+
operationClass: InvocationScopeSchema.shape.operationClass,
|
|
62
|
+
targetId: InvocationScopeSchema.shape.targetId,
|
|
63
|
+
workflowId: z.string().min(1).max(255),
|
|
64
|
+
revision: z.string().min(1).max(512),
|
|
65
|
+
})
|
|
66
|
+
.strict(),
|
|
67
|
+
]);
|
|
68
|
+
export function parseProductionMutationInvocation(value) {
|
|
69
|
+
return ProductionMutationInvocationSchema.parse(value);
|
|
70
|
+
}
|
|
71
|
+
export function validateProductionMutationAuthority(input) {
|
|
72
|
+
const assessment = assessProductionMutationAuthority(input);
|
|
73
|
+
if (assessment.status === 'needs-review') {
|
|
74
|
+
throw new Error(`Fresh approval does not match the current preview ${input.preview.digest}.`);
|
|
75
|
+
}
|
|
76
|
+
return assessment;
|
|
77
|
+
}
|
|
78
|
+
export function assessProductionMutationAuthority(input) {
|
|
79
|
+
if (input.effective === 'read-only') {
|
|
80
|
+
throw new Error('Remote mutation is read-only under current policy.');
|
|
81
|
+
}
|
|
82
|
+
if (!input.invocation) {
|
|
83
|
+
throw new Error('Current caller invocation evidence is required.');
|
|
84
|
+
}
|
|
85
|
+
assertCurrentInvocation(input.invocation, input.now);
|
|
86
|
+
const expectedScope = {
|
|
87
|
+
operationClass: input.expected.operationClass,
|
|
88
|
+
targetId: input.expected.targetId,
|
|
89
|
+
};
|
|
90
|
+
if (input.effective === 'user-authorized') {
|
|
91
|
+
if (input.invocation.kind !== 'interactive' ||
|
|
92
|
+
input.invocation.instruction.operationClass !==
|
|
93
|
+
expectedScope.operationClass ||
|
|
94
|
+
input.invocation.instruction.targetId !== expectedScope.targetId) {
|
|
95
|
+
throw new Error('Explicit invocation evidence does not authorize this mutation.');
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
status: 'ready',
|
|
99
|
+
authority: {
|
|
100
|
+
effective: input.effective,
|
|
101
|
+
sourceDigest: semanticDigest(input.invocation),
|
|
102
|
+
},
|
|
103
|
+
approval: null,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (input.effective === 'user-approved') {
|
|
107
|
+
const approval = input.invocation.kind === 'interactive'
|
|
108
|
+
? input.invocation.approval
|
|
109
|
+
: null;
|
|
110
|
+
if (input.invocation.kind !== 'interactive' ||
|
|
111
|
+
input.invocation.instruction.operationClass !==
|
|
112
|
+
expectedScope.operationClass ||
|
|
113
|
+
input.invocation.instruction.targetId !== expectedScope.targetId) {
|
|
114
|
+
throw new Error('Explicit invocation evidence does not authorize this mutation.');
|
|
115
|
+
}
|
|
116
|
+
const sourceDigest = semanticDigest({
|
|
117
|
+
...input.invocation,
|
|
118
|
+
approval: null,
|
|
119
|
+
});
|
|
120
|
+
if (!approval) {
|
|
121
|
+
return {
|
|
122
|
+
status: 'needs-review',
|
|
123
|
+
authority: { effective: 'user-approved', sourceDigest },
|
|
124
|
+
approval: null,
|
|
125
|
+
previewDigest: input.preview.digest,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (!validatePreviewApproval(input.preview, approval, {
|
|
129
|
+
now: input.now,
|
|
130
|
+
maxAgeMs: input.approvalMaxAgeMs,
|
|
131
|
+
}).valid) {
|
|
132
|
+
throw new Error(`Fresh approval does not match the current preview ${input.preview.digest}.`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
status: 'ready',
|
|
136
|
+
authority: {
|
|
137
|
+
effective: input.effective,
|
|
138
|
+
sourceDigest,
|
|
139
|
+
},
|
|
140
|
+
approval,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (input.invocation.kind !== 'workflow' ||
|
|
144
|
+
input.invocation.operationClass !== expectedScope.operationClass ||
|
|
145
|
+
input.invocation.targetId !== expectedScope.targetId ||
|
|
146
|
+
input.invocation.workflowId !== input.expected.workflowId ||
|
|
147
|
+
input.invocation.revision !== input.expected.workflowRevision) {
|
|
148
|
+
throw new Error('Autonomous mutation requires current active-workflow authority.');
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
status: 'ready',
|
|
152
|
+
authority: {
|
|
153
|
+
effective: input.effective,
|
|
154
|
+
sourceDigest: semanticDigest(input.invocation),
|
|
155
|
+
},
|
|
156
|
+
approval: null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function assertCurrentInvocation(invocation, now) {
|
|
160
|
+
const nowMs = Date.parse(now);
|
|
161
|
+
const issuedAtMs = Date.parse(invocation.issuedAt);
|
|
162
|
+
const expiresAtMs = Date.parse(invocation.expiresAt);
|
|
163
|
+
if (!Number.isFinite(nowMs) ||
|
|
164
|
+
issuedAtMs > nowMs ||
|
|
165
|
+
expiresAtMs < nowMs ||
|
|
166
|
+
expiresAtMs < issuedAtMs) {
|
|
167
|
+
throw new Error('Current caller invocation evidence is expired or stale.');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const OPERATIONS = [
|
|
171
|
+
'create',
|
|
172
|
+
'update-fields',
|
|
173
|
+
'transition',
|
|
174
|
+
'annotate',
|
|
175
|
+
'delete',
|
|
176
|
+
'relink',
|
|
177
|
+
'detach',
|
|
178
|
+
'recreate',
|
|
179
|
+
];
|
|
180
|
+
const AUTHORITIES = [
|
|
181
|
+
'read-only',
|
|
182
|
+
'user-approved',
|
|
183
|
+
'user-authorized',
|
|
184
|
+
'autonomous',
|
|
185
|
+
];
|
|
186
|
+
const DESCRIPTION_MODES = [
|
|
187
|
+
'none',
|
|
188
|
+
'managed-section',
|
|
189
|
+
'replace',
|
|
190
|
+
];
|
|
191
|
+
const DESTRUCTIVE_OPERATIONS = new Set([
|
|
192
|
+
'delete',
|
|
193
|
+
'recreate',
|
|
194
|
+
]);
|
|
195
|
+
const IDENTITY_OPERATIONS = new Set([
|
|
196
|
+
'relink',
|
|
197
|
+
'detach',
|
|
198
|
+
'recreate',
|
|
199
|
+
]);
|
|
200
|
+
export function resolveEffectiveRemotePolicy(input) {
|
|
201
|
+
const findings = [];
|
|
202
|
+
const description = resolveDescription(input, findings);
|
|
203
|
+
const authority = {};
|
|
204
|
+
const authorityTrace = {};
|
|
205
|
+
const hardFloors = new Set();
|
|
206
|
+
for (const operation of OPERATIONS) {
|
|
207
|
+
const resolved = resolveAuthority(operation, input, findings);
|
|
208
|
+
let final = resolved.value;
|
|
209
|
+
let hardFloor = null;
|
|
210
|
+
if (DESTRUCTIVE_OPERATIONS.has(operation)) {
|
|
211
|
+
hardFloors.add('destructive');
|
|
212
|
+
hardFloor = 'user-approved';
|
|
213
|
+
}
|
|
214
|
+
if (IDENTITY_OPERATIONS.has(operation)) {
|
|
215
|
+
hardFloors.add('identity-resolution');
|
|
216
|
+
hardFloor = 'user-approved';
|
|
217
|
+
}
|
|
218
|
+
if ((operation === 'create' || operation === 'update-fields') &&
|
|
219
|
+
input.completeDescriptionReplacement === true) {
|
|
220
|
+
hardFloors.add('replace-description');
|
|
221
|
+
hardFloor = 'user-approved';
|
|
222
|
+
}
|
|
223
|
+
if (hardFloor)
|
|
224
|
+
final = clampAuthority(final, hardFloor);
|
|
225
|
+
authority[operation] = final;
|
|
226
|
+
authorityTrace[operation] = {
|
|
227
|
+
builtIn: 'read-only',
|
|
228
|
+
repository: resolved.repository,
|
|
229
|
+
provider: resolved.provider,
|
|
230
|
+
bindingDefault: resolved.bindingDefault,
|
|
231
|
+
bindingOperation: resolved.bindingOperation,
|
|
232
|
+
hardFloor,
|
|
233
|
+
final,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
description: description.final,
|
|
238
|
+
descriptionTrace: description,
|
|
239
|
+
authority,
|
|
240
|
+
authorityTrace,
|
|
241
|
+
hardFloors: [...hardFloors],
|
|
242
|
+
findings,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function resolveAuthority(operation, input, findings) {
|
|
246
|
+
const repositoryAuthority = input.repository.authority;
|
|
247
|
+
let repository;
|
|
248
|
+
if (hasOperation(repositoryAuthority, operation)) {
|
|
249
|
+
repository = {
|
|
250
|
+
value: sanitizeAuthority(repositoryAuthority?.operations?.[operation], `repository operation '${operation}'`, findings),
|
|
251
|
+
source: 'operation',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
else if (hasDefined(repositoryAuthority, 'default')) {
|
|
255
|
+
repository = {
|
|
256
|
+
value: sanitizeAuthority(repositoryAuthority?.default, 'repository default', findings),
|
|
257
|
+
source: 'default',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
repository = { value: 'read-only', source: 'built-in' };
|
|
262
|
+
}
|
|
263
|
+
const providerAuthority = input.provider?.authority;
|
|
264
|
+
let provider;
|
|
265
|
+
let providerValue = repository.value;
|
|
266
|
+
if (hasOperation(providerAuthority, operation)) {
|
|
267
|
+
providerValue = sanitizeAuthority(providerAuthority?.operations?.[operation], `provider operation '${operation}'`, findings);
|
|
268
|
+
provider = { value: providerValue, source: 'operation' };
|
|
269
|
+
}
|
|
270
|
+
else if (hasDefined(providerAuthority, 'default')) {
|
|
271
|
+
providerValue = sanitizeAuthority(providerAuthority?.default, 'provider default', findings);
|
|
272
|
+
provider = { value: providerValue, source: 'default' };
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
provider = { value: null, source: 'inherit' };
|
|
276
|
+
}
|
|
277
|
+
const bindingAuthority = input.binding?.authority;
|
|
278
|
+
const bindingDefault = hasDefined(bindingAuthority, 'default')
|
|
279
|
+
? sanitizeAuthority(bindingAuthority?.default, 'binding default', findings)
|
|
280
|
+
: null;
|
|
281
|
+
const bindingOperation = hasOperation(bindingAuthority, operation)
|
|
282
|
+
? sanitizeAuthority(bindingAuthority?.operations?.[operation], `binding operation '${operation}'`, findings)
|
|
283
|
+
: null;
|
|
284
|
+
const value = [bindingDefault, bindingOperation]
|
|
285
|
+
.filter((candidate) => candidate !== null)
|
|
286
|
+
.reduce(clampAuthority, providerValue);
|
|
287
|
+
return {
|
|
288
|
+
value,
|
|
289
|
+
repository,
|
|
290
|
+
provider,
|
|
291
|
+
bindingDefault,
|
|
292
|
+
bindingOperation,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function resolveDescription(input, findings) {
|
|
296
|
+
const repository = hasDefined(input.repository, 'description')
|
|
297
|
+
? sanitizeDescription(input.repository.description, 'repository description', findings)
|
|
298
|
+
: 'none';
|
|
299
|
+
const provider = hasDefined(input.provider, 'description')
|
|
300
|
+
? sanitizeDescription(input.provider?.description, 'provider description', findings)
|
|
301
|
+
: null;
|
|
302
|
+
const binding = hasDefined(input.binding, 'description')
|
|
303
|
+
? sanitizeDescription(input.binding?.description, 'binding description', findings)
|
|
304
|
+
: null;
|
|
305
|
+
const providerResult = provider ?? repository;
|
|
306
|
+
const final = binding
|
|
307
|
+
? clampDescription(providerResult, binding)
|
|
308
|
+
: providerResult;
|
|
309
|
+
return { builtIn: 'none', repository, provider, binding, final };
|
|
310
|
+
}
|
|
311
|
+
function sanitizeAuthority(value, source, findings) {
|
|
312
|
+
if (AUTHORITIES.includes(value)) {
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
findings.push(`Invalid ${source} value; using read-only.`);
|
|
316
|
+
return 'read-only';
|
|
317
|
+
}
|
|
318
|
+
function sanitizeDescription(value, source, findings) {
|
|
319
|
+
if (DESCRIPTION_MODES.includes(value)) {
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
findings.push(`Invalid ${source} value; using none.`);
|
|
323
|
+
return 'none';
|
|
324
|
+
}
|
|
325
|
+
function clampAuthority(left, right) {
|
|
326
|
+
return AUTHORITIES.indexOf(left) <= AUTHORITIES.indexOf(right) ? left : right;
|
|
327
|
+
}
|
|
328
|
+
function clampDescription(left, right) {
|
|
329
|
+
return DESCRIPTION_MODES.indexOf(left) <= DESCRIPTION_MODES.indexOf(right)
|
|
330
|
+
? left
|
|
331
|
+
: right;
|
|
332
|
+
}
|
|
333
|
+
function hasDefined(value, key) {
|
|
334
|
+
return (value !== undefined &&
|
|
335
|
+
Object.hasOwn(value, key) &&
|
|
336
|
+
value[key] !== undefined);
|
|
337
|
+
}
|
|
338
|
+
function hasOperation(authority, operation) {
|
|
339
|
+
return (authority?.operations !== undefined &&
|
|
340
|
+
Object.hasOwn(authority.operations, operation));
|
|
341
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RemoteBatchRecord } from './schema.js';
|
|
2
|
+
export type ReviewedBatchMember = RemoteBatchRecord['members'][number];
|
|
3
|
+
export type ReviewedBatchOutcome = RemoteBatchRecord['outcomes'][string];
|
|
4
|
+
export interface BuildReviewedBatchInput {
|
|
5
|
+
batchId: string;
|
|
6
|
+
lifecycleOperation: RemoteBatchRecord['lifecycleOperation'];
|
|
7
|
+
members: readonly ReviewedBatchMember[];
|
|
8
|
+
authority: RemoteBatchRecord['authority'];
|
|
9
|
+
approval?: RemoteBatchRecord['approval'];
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildReviewedBatch(input: BuildReviewedBatchInput): RemoteBatchRecord;
|
|
13
|
+
export declare function resumeReviewedBatch(batch: RemoteBatchRecord, currentMembers: readonly ReviewedBatchMember[]): RemoteBatchRecord;
|
|
14
|
+
export declare function reduceReviewedBatchOutcomes(batch: RemoteBatchRecord, updates: Partial<Record<string, ReviewedBatchOutcome>>, updatedAt: string): RemoteBatchRecord;
|
|
15
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../../../src/commands/pjm/remote/batch.ts"],"names":[],"mappings":"AACA,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE3E,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACvE,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,iBAAiB,CAAC,oBAAoB,CAAC,CAAC;IAC5D,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,uBAAuB,GAC7B,iBAAiB,CA8BnB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,iBAAiB,EACxB,cAAc,EAAE,SAAS,mBAAmB,EAAE,GAC7C,iBAAiB,CASnB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,EACtD,SAAS,EAAE,MAAM,GAChB,iBAAiB,CAyBnB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { semanticDigest } from './provider.js';
|
|
2
|
+
import { RemoteBatchRecordSchema } from './schema.js';
|
|
3
|
+
export function buildReviewedBatch(input) {
|
|
4
|
+
const members = canonicalMembers(input.members);
|
|
5
|
+
assertUniqueMembers(members);
|
|
6
|
+
const membershipDigest = digestMembership(members);
|
|
7
|
+
const previewDigest = semanticDigest({
|
|
8
|
+
lifecycleOperation: input.lifecycleOperation,
|
|
9
|
+
membershipDigest,
|
|
10
|
+
authority: input.authority,
|
|
11
|
+
members,
|
|
12
|
+
});
|
|
13
|
+
const approval = input.approval?.previewDigest === previewDigest ? input.approval : null;
|
|
14
|
+
return RemoteBatchRecordSchema.parse({
|
|
15
|
+
recordType: 'batch',
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
batchId: input.batchId,
|
|
18
|
+
lifecycleOperation: input.lifecycleOperation,
|
|
19
|
+
state: approval ? 'authorized' : 'pending',
|
|
20
|
+
membershipDigest,
|
|
21
|
+
previewDigest,
|
|
22
|
+
authority: input.authority,
|
|
23
|
+
approval,
|
|
24
|
+
createdAt: input.createdAt,
|
|
25
|
+
updatedAt: input.createdAt,
|
|
26
|
+
members,
|
|
27
|
+
outcomes: Object.fromEntries(members.map((member) => [member.operationId, 'planned'])),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function resumeReviewedBatch(batch, currentMembers) {
|
|
31
|
+
const parsed = RemoteBatchRecordSchema.parse(batch);
|
|
32
|
+
const membershipDigest = digestMembership(canonicalMembers(currentMembers));
|
|
33
|
+
if (membershipDigest !== parsed.membershipDigest) {
|
|
34
|
+
throw new Error(`Reviewed batch '${parsed.batchId}' membership changed; create and approve a new batch.`);
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
export function reduceReviewedBatchOutcomes(batch, updates, updatedAt) {
|
|
39
|
+
const parsed = RemoteBatchRecordSchema.parse(batch);
|
|
40
|
+
const operationIds = new Set(parsed.members.map((member) => member.operationId));
|
|
41
|
+
for (const operationId of Object.keys(updates)) {
|
|
42
|
+
if (!operationIds.has(operationId)) {
|
|
43
|
+
throw new Error(`Operation '${operationId}' is not a member of reviewed batch '${parsed.batchId}'.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const outcomes = { ...parsed.outcomes };
|
|
47
|
+
for (const [operationId, state] of Object.entries(updates)) {
|
|
48
|
+
if (state !== undefined) {
|
|
49
|
+
assertMonotonicOutcome(operationId, outcomes[operationId], state);
|
|
50
|
+
outcomes[operationId] = state;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return RemoteBatchRecordSchema.parse({
|
|
54
|
+
...parsed,
|
|
55
|
+
outcomes,
|
|
56
|
+
state: reduceBatchState(Object.values(outcomes)),
|
|
57
|
+
updatedAt,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const TERMINAL_MEMBER_OUTCOMES = new Set([
|
|
61
|
+
'verified',
|
|
62
|
+
'blocked',
|
|
63
|
+
'uncertain',
|
|
64
|
+
'rejected',
|
|
65
|
+
'failed',
|
|
66
|
+
'partial',
|
|
67
|
+
]);
|
|
68
|
+
const OUTCOME_RANK = {
|
|
69
|
+
planned: 0,
|
|
70
|
+
pending: 0,
|
|
71
|
+
authorized: 1,
|
|
72
|
+
'attempt-started': 2,
|
|
73
|
+
'verification-pending': 3,
|
|
74
|
+
};
|
|
75
|
+
function assertMonotonicOutcome(operationId, previous, next) {
|
|
76
|
+
if (previous === next)
|
|
77
|
+
return;
|
|
78
|
+
if (TERMINAL_MEMBER_OUTCOMES.has(previous) ||
|
|
79
|
+
(OUTCOME_RANK[next] ?? Number.POSITIVE_INFINITY) <
|
|
80
|
+
(OUTCOME_RANK[previous] ?? Number.POSITIVE_INFINITY)) {
|
|
81
|
+
throw new Error(`Reviewed batch outcome for '${operationId}' cannot regress from '${previous}' to '${next}'.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function canonicalMembers(members) {
|
|
85
|
+
return [...members].sort((left, right) => left.bindingId.localeCompare(right.bindingId) ||
|
|
86
|
+
left.operationId.localeCompare(right.operationId));
|
|
87
|
+
}
|
|
88
|
+
function assertUniqueMembers(members) {
|
|
89
|
+
const bindingIds = new Set();
|
|
90
|
+
const operationIds = new Set();
|
|
91
|
+
for (const member of members) {
|
|
92
|
+
if (bindingIds.has(member.bindingId)) {
|
|
93
|
+
throw new Error(`Duplicate reviewed binding '${member.bindingId}'.`);
|
|
94
|
+
}
|
|
95
|
+
if (operationIds.has(member.operationId)) {
|
|
96
|
+
throw new Error(`Duplicate reviewed operation '${member.operationId}'.`);
|
|
97
|
+
}
|
|
98
|
+
bindingIds.add(member.bindingId);
|
|
99
|
+
operationIds.add(member.operationId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function digestMembership(members) {
|
|
103
|
+
return semanticDigest(members.map(({ bindingId, operationId, bindingPreviewDigest }) => ({
|
|
104
|
+
bindingId,
|
|
105
|
+
operationId,
|
|
106
|
+
bindingPreviewDigest,
|
|
107
|
+
})));
|
|
108
|
+
}
|
|
109
|
+
function reduceBatchState(outcomes) {
|
|
110
|
+
if (outcomes.every((state) => state === 'verified'))
|
|
111
|
+
return 'complete';
|
|
112
|
+
const anyVerified = outcomes.some((state) => state === 'verified');
|
|
113
|
+
if (anyVerified)
|
|
114
|
+
return 'partial';
|
|
115
|
+
if (outcomes.some((state) => state === 'partial'))
|
|
116
|
+
return 'partial';
|
|
117
|
+
if (outcomes.some((state) => state === 'uncertain'))
|
|
118
|
+
return 'uncertain';
|
|
119
|
+
if (outcomes.every((state) => state === 'blocked'))
|
|
120
|
+
return 'blocked';
|
|
121
|
+
if (outcomes.some((state) => state === 'attempt-started'))
|
|
122
|
+
return 'in-progress';
|
|
123
|
+
if (outcomes.some((state) => state === 'authorized'))
|
|
124
|
+
return 'authorized';
|
|
125
|
+
return 'pending';
|
|
126
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type RemoteProvider } from './provider.js';
|
|
2
|
+
import type { RemoteBatchRecord, RemoteOperationRecord } from './schema.js';
|
|
3
|
+
export type CloseoutStepState = RemoteOperationRecord['state'];
|
|
4
|
+
export type CloseoutPurpose = 'source' | 'planning' | 'delivery' | 'reference';
|
|
5
|
+
export type CloseoutAuthority = 'read-only' | 'user-approved' | 'user-authorized' | 'autonomous';
|
|
6
|
+
export interface CloseoutStepPlan {
|
|
7
|
+
authority: CloseoutAuthority;
|
|
8
|
+
sourceDigest: string;
|
|
9
|
+
previewDigest: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CloseoutBindingPlan {
|
|
12
|
+
bindingId: string;
|
|
13
|
+
operationId: string;
|
|
14
|
+
provider: RemoteProvider;
|
|
15
|
+
purposes: readonly CloseoutPurpose[];
|
|
16
|
+
annotation?: CloseoutStepPlan;
|
|
17
|
+
transition?: CloseoutStepPlan;
|
|
18
|
+
providerAutomation?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface CloseoutSubstep {
|
|
21
|
+
stepId: string;
|
|
22
|
+
kind: 'annotation' | 'transition';
|
|
23
|
+
state: CloseoutStepState;
|
|
24
|
+
previewDigest: string;
|
|
25
|
+
authority: CloseoutAuthority;
|
|
26
|
+
authoritySourceDigest: string;
|
|
27
|
+
approvalRequirement: 'none' | 'explicit-instruction' | 'fresh-approval';
|
|
28
|
+
dependsOn: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface CloseoutJournal {
|
|
31
|
+
schemaVersion: 1;
|
|
32
|
+
operationId: string;
|
|
33
|
+
bindingId: string;
|
|
34
|
+
provider: RemoteProvider;
|
|
35
|
+
projectPath: string;
|
|
36
|
+
previewDigest: string;
|
|
37
|
+
state: CloseoutStepState;
|
|
38
|
+
substeps: CloseoutSubstep[];
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}
|
|
42
|
+
export interface CloseoutStore {
|
|
43
|
+
readOperation(operationId: string): Promise<CloseoutJournal | null>;
|
|
44
|
+
writeOperation(operation: CloseoutJournal): Promise<void>;
|
|
45
|
+
writeBatch(batch: RemoteBatchRecord): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export interface CloseoutInput {
|
|
48
|
+
batchId: string;
|
|
49
|
+
projectPath: string;
|
|
50
|
+
plans: readonly CloseoutBindingPlan[];
|
|
51
|
+
now: string;
|
|
52
|
+
approval?: RemoteBatchRecord['approval'];
|
|
53
|
+
}
|
|
54
|
+
export interface CloseoutResult {
|
|
55
|
+
batch: RemoteBatchRecord;
|
|
56
|
+
operations: CloseoutJournal[];
|
|
57
|
+
}
|
|
58
|
+
export type CloseoutCrashPoint = 'before-annotation' | 'after-annotation' | 'before-transition' | 'after-transition';
|
|
59
|
+
export type ExecuteCloseoutStep = (step: CloseoutSubstep) => Promise<'verified' | 'uncertain' | 'rejected' | 'blocked' | 'failed'>;
|
|
60
|
+
export interface CloseoutAuthorization {
|
|
61
|
+
kind: 'approval' | 'instruction';
|
|
62
|
+
previewDigest: string;
|
|
63
|
+
authorizedAt: string;
|
|
64
|
+
source: string;
|
|
65
|
+
}
|
|
66
|
+
export declare function closeoutBindings(input: CloseoutInput, store: CloseoutStore): Promise<CloseoutResult>;
|
|
67
|
+
export declare function resumeCloseoutOperation(journal: CloseoutJournal, store: CloseoutStore, executeStep: ExecuteCloseoutStep, crash?: (point: CloseoutCrashPoint) => void, now?: () => string, authorization?: CloseoutAuthorization): Promise<CloseoutJournal>;
|
|
68
|
+
export declare function reduceCloseoutState(substeps: readonly CloseoutSubstep[]): CloseoutStepState;
|
|
69
|
+
//# sourceMappingURL=closeout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closeout.d.ts","sourceRoot":"","sources":["../../../../src/commands/pjm/remote/closeout.ts"],"names":[],"mappings":"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"}
|