@cavelang/solver-z3 0.29.0
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/Authors.md +5 -0
- package/BENCHMARK.md +44 -0
- package/License.md +6 -0
- package/README.md +105 -0
- package/dist/scripts/benchmark.d.ts +2 -0
- package/dist/scripts/benchmark.d.ts.map +1 -0
- package/dist/scripts/benchmark.js +96 -0
- package/dist/scripts/benchmark.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/runtime.d.ts +10 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +399 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/workflow-fixture.d.ts +20 -0
- package/dist/src/workflow-fixture.d.ts.map +1 -0
- package/dist/src/workflow-fixture.js +185 -0
- package/dist/src/workflow-fixture.js.map +1 -0
- package/dist/src/workflow-main.d.ts +3 -0
- package/dist/src/workflow-main.d.ts.map +1 -0
- package/dist/src/workflow-main.js +9 -0
- package/dist/src/workflow-main.js.map +1 -0
- package/package.json +67 -0
- package/scripts/benchmark.ts +101 -0
- package/src/index.ts +10 -0
- package/src/runtime.ts +479 -0
- package/src/workflow-fixture.ts +227 -0
- package/src/workflow-main.ts +8 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/** Named CLI fixture for the solver workflow API. No raw model input is accepted. */
|
|
2
|
+
|
|
3
|
+
import { Adapter, Model, Workflow } from '@cavelang/solver'
|
|
4
|
+
import { create } from './runtime.ts'
|
|
5
|
+
|
|
6
|
+
export type Output = { readonly code: number, readonly out: string, readonly err: string }
|
|
7
|
+
|
|
8
|
+
const usage = `Usage: cave-solver-workflow architecture <operation> [options]
|
|
9
|
+
|
|
10
|
+
Operations:
|
|
11
|
+
feasibility Find a deterministic feasible architecture.
|
|
12
|
+
optimization Minimize the declared architecture cost model.
|
|
13
|
+
counterexample Test the declared small-team-monolith invariant.
|
|
14
|
+
sensitivity Vary deployment frequency and report architecture changes.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--team-size <2..20> Default: 10
|
|
18
|
+
--deployment-frequency <1..12> Default: 6
|
|
19
|
+
--from <1..12> Sensitivity start; default: 1
|
|
20
|
+
--to <1..12> Sensitivity end; default: 12
|
|
21
|
+
--timeout-ms <positive integer> Per solver run; default: 10000
|
|
22
|
+
|
|
23
|
+
The fixture is the only selectable model. Inputs are bounded and typed before
|
|
24
|
+
the optional Z3 adapter runs; arbitrary expressions and SMT-LIB are rejected.
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const fail = (message: string, code = 2): Output => ({ code, out: '', err: `${message}\n\n${usage}` })
|
|
28
|
+
const ok = (value: unknown): Output => ({ code: 0, out: `${JSON.stringify(value, null, 2)}\n`, err: '' })
|
|
29
|
+
|
|
30
|
+
const integer = (value: number): Model.Expression => ({ kind: 'literal', sort: 'int', value })
|
|
31
|
+
const ref = (id: string): Model.Expression => ({ kind: 'variable', id })
|
|
32
|
+
const architecture = (value: string): Model.Expression => ({
|
|
33
|
+
kind: 'literal', sort: 'enum', domain: 'architecture', value
|
|
34
|
+
})
|
|
35
|
+
const equal = (id: string, value: Model.Expression): Model.Expression => ({ kind: 'eq', left: ref(id), right: value })
|
|
36
|
+
const add = (...operands: readonly Model.Expression[]): Model.Expression => ({ kind: 'add', operands })
|
|
37
|
+
const multiply = (left: Model.Expression, right: Model.Expression): Model.Expression => ({
|
|
38
|
+
kind: 'multiply', operands: [left, right]
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export type ArchitectureInputs = {
|
|
42
|
+
readonly teamSize?: number
|
|
43
|
+
readonly deploymentFrequency?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Small but non-trivial architecture decision model used by the CLI fixture.
|
|
48
|
+
* The invariant is intentionally stronger than the feasibility constraint so
|
|
49
|
+
* the counterexample workflow has a concrete bounded witness.
|
|
50
|
+
*/
|
|
51
|
+
export const architectureModel = (inputs: ArchitectureInputs = {}): Model.t => {
|
|
52
|
+
const constraints: Model.HardConstraint[] = [
|
|
53
|
+
{
|
|
54
|
+
id: 'microservices-team-capacity',
|
|
55
|
+
description: 'Microservices need at least six people in this fixture.',
|
|
56
|
+
declaration: { uri: 'packages/solver-z3/src/workflow-fixture.ts' },
|
|
57
|
+
expression: {
|
|
58
|
+
kind: 'implies',
|
|
59
|
+
left: equal('architecture', architecture('microservices')),
|
|
60
|
+
right: { kind: 'gte', left: ref('team-size'), right: integer(6) }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'small-team-monolith',
|
|
65
|
+
description: 'Teams below eight people should choose a monolith.',
|
|
66
|
+
declaration: { uri: 'packages/solver-z3/src/workflow-fixture.ts' },
|
|
67
|
+
expression: {
|
|
68
|
+
kind: 'implies',
|
|
69
|
+
left: { kind: 'lt', left: ref('team-size'), right: integer(8) },
|
|
70
|
+
right: equal('architecture', architecture('monolith'))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
if (inputs.teamSize !== undefined) {
|
|
75
|
+
constraints.push({
|
|
76
|
+
id: 'input/team-size',
|
|
77
|
+
expression: equal('team-size', integer(inputs.teamSize)),
|
|
78
|
+
scenarioInputIds: ['team-size']
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
if (inputs.deploymentFrequency !== undefined) {
|
|
82
|
+
constraints.push({
|
|
83
|
+
id: 'input/deployment-frequency',
|
|
84
|
+
expression: equal('deployment-frequency', integer(inputs.deploymentFrequency)),
|
|
85
|
+
scenarioInputIds: ['deployment-frequency']
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
const monolithCost = add(integer(20), ref('team-size'), multiply(integer(3), ref('deployment-frequency')))
|
|
89
|
+
const microservicesCost = add(
|
|
90
|
+
integer(45),
|
|
91
|
+
multiply(integer(2), ref('team-size')),
|
|
92
|
+
{ kind: 'negate', value: multiply(integer(4), ref('deployment-frequency')) }
|
|
93
|
+
)
|
|
94
|
+
return {
|
|
95
|
+
schema: Model.schema,
|
|
96
|
+
enums: [{ id: 'architecture', values: ['monolith', 'microservices'] }],
|
|
97
|
+
variables: [
|
|
98
|
+
{ id: 'architecture', sort: 'enum', domain: 'architecture' },
|
|
99
|
+
{ id: 'deployment-frequency', sort: 'int', min: 1, max: 12, scenarioInputIds: ['deployment-frequency'] },
|
|
100
|
+
{ id: 'team-size', sort: 'int', min: 2, max: 20, scenarioInputIds: ['team-size'] }
|
|
101
|
+
],
|
|
102
|
+
constraints,
|
|
103
|
+
objectives: [{
|
|
104
|
+
id: 'operational-cost',
|
|
105
|
+
direction: 'minimize',
|
|
106
|
+
description: 'Illustrative relative operating cost, not epistemic confidence.',
|
|
107
|
+
expression: {
|
|
108
|
+
kind: 'if',
|
|
109
|
+
condition: equal('architecture', architecture('monolith')),
|
|
110
|
+
then: monolithCost,
|
|
111
|
+
else: microservicesCost
|
|
112
|
+
}
|
|
113
|
+
}]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type Parsed = {
|
|
118
|
+
readonly operation: 'feasibility' | 'optimization' | 'counterexample' | 'sensitivity'
|
|
119
|
+
readonly teamSize: number
|
|
120
|
+
readonly deploymentFrequency: number
|
|
121
|
+
readonly from: number
|
|
122
|
+
readonly to: number
|
|
123
|
+
readonly timeoutMs: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const parseInteger = (name: string, value: string | undefined, min: number, max?: number): number => {
|
|
127
|
+
const parsed = Number(value)
|
|
128
|
+
if (!Number.isSafeInteger(parsed) || parsed < min || (max !== undefined && parsed > max)) {
|
|
129
|
+
throw new TypeError(`${name} must be an integer from ${min}${max === undefined ? '' : ` to ${max}`}`)
|
|
130
|
+
}
|
|
131
|
+
return parsed
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const parse = (argv: readonly string[]): Parsed | Output => {
|
|
135
|
+
if (argv.includes('--help') || argv.includes('-h')) return { code: 0, out: usage, err: '' }
|
|
136
|
+
if (argv[0] !== 'architecture') return fail(`unknown workflow model ${JSON.stringify(argv[0] ?? '')}`)
|
|
137
|
+
const operation = argv[1]
|
|
138
|
+
if (operation !== 'feasibility' && operation !== 'optimization' &&
|
|
139
|
+
operation !== 'counterexample' && operation !== 'sensitivity') {
|
|
140
|
+
return fail(`unknown workflow operation ${JSON.stringify(operation ?? '')}`)
|
|
141
|
+
}
|
|
142
|
+
const values = new Map<string, string>()
|
|
143
|
+
for (let index = 2; index < argv.length; index += 2) {
|
|
144
|
+
const flag = argv[index]
|
|
145
|
+
const value = argv[index + 1]
|
|
146
|
+
if (flag === undefined || !['--team-size', '--deployment-frequency', '--from', '--to', '--timeout-ms'].includes(flag)) {
|
|
147
|
+
return fail(`unknown workflow option ${JSON.stringify(flag ?? '')}`)
|
|
148
|
+
}
|
|
149
|
+
if (value === undefined || value.startsWith('--')) return fail(`${flag} needs a value`)
|
|
150
|
+
if (values.has(flag)) return fail(`${flag} was provided more than once`)
|
|
151
|
+
values.set(flag, value)
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const from = parseInteger('--from', values.get('--from') ?? '1', 1, 12)
|
|
155
|
+
const to = parseInteger('--to', values.get('--to') ?? '12', 1, 12)
|
|
156
|
+
if (from > to) return fail('--from must be less than or equal to --to')
|
|
157
|
+
return {
|
|
158
|
+
operation,
|
|
159
|
+
teamSize: parseInteger('--team-size', values.get('--team-size') ?? '10', 2, 20),
|
|
160
|
+
deploymentFrequency: parseInteger(
|
|
161
|
+
'--deployment-frequency', values.get('--deployment-frequency') ?? '6', 1, 12
|
|
162
|
+
),
|
|
163
|
+
from,
|
|
164
|
+
to,
|
|
165
|
+
timeoutMs: parseInteger('--timeout-ms', values.get('--timeout-ms') ?? '10000', 1)
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return fail(error instanceof Error ? error.message : String(error))
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Runs the allowlisted fixture. Supplying an adapter is intended for tests. */
|
|
173
|
+
export const runWorkflowFixture = async (
|
|
174
|
+
argv: readonly string[],
|
|
175
|
+
supplied?: Adapter.t
|
|
176
|
+
): Promise<Output> => {
|
|
177
|
+
const parsed = parse(argv)
|
|
178
|
+
if ('code' in parsed) return parsed
|
|
179
|
+
const runtime = supplied === undefined ? await create() : undefined
|
|
180
|
+
const adapter = supplied ?? runtime!
|
|
181
|
+
const options: Adapter.Options = { limits: { timeoutMs: parsed.timeoutMs } }
|
|
182
|
+
const context = { snapshot: { transactionTime: null } } as const
|
|
183
|
+
try {
|
|
184
|
+
switch (parsed.operation) {
|
|
185
|
+
case 'feasibility':
|
|
186
|
+
return ok(await Workflow.feasibility(
|
|
187
|
+
adapter,
|
|
188
|
+
architectureModel({ teamSize: parsed.teamSize, deploymentFrequency: parsed.deploymentFrequency }),
|
|
189
|
+
options,
|
|
190
|
+
context
|
|
191
|
+
))
|
|
192
|
+
case 'optimization':
|
|
193
|
+
return ok(await Workflow.optimization(
|
|
194
|
+
adapter,
|
|
195
|
+
architectureModel({ teamSize: parsed.teamSize, deploymentFrequency: parsed.deploymentFrequency }),
|
|
196
|
+
options,
|
|
197
|
+
context
|
|
198
|
+
))
|
|
199
|
+
case 'counterexample':
|
|
200
|
+
return ok(await Workflow.counterexample(
|
|
201
|
+
adapter,
|
|
202
|
+
architectureModel(),
|
|
203
|
+
'small-team-monolith',
|
|
204
|
+
options,
|
|
205
|
+
context
|
|
206
|
+
))
|
|
207
|
+
case 'sensitivity': {
|
|
208
|
+
const samples = Array.from(
|
|
209
|
+
{ length: parsed.to - parsed.from + 1 },
|
|
210
|
+
(_, index): Model.Value => ({ sort: 'int', value: String(parsed.from + index) })
|
|
211
|
+
)
|
|
212
|
+
return ok(await Workflow.sensitivity(adapter, architectureModel(), {
|
|
213
|
+
variableId: 'deployment-frequency',
|
|
214
|
+
samples,
|
|
215
|
+
fixed: [{ variableId: 'team-size', value: { sort: 'int', value: String(parsed.teamSize) } }],
|
|
216
|
+
observe: ['architecture'],
|
|
217
|
+
operation: 'optimization',
|
|
218
|
+
maxRuns: 12
|
|
219
|
+
}, options, context))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return { code: 1, out: '', err: `${error instanceof Error ? error.message : String(error)}\n` }
|
|
224
|
+
} finally {
|
|
225
|
+
await runtime?.close()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { runWorkflowFixture } from './workflow-fixture.ts'
|
|
4
|
+
|
|
5
|
+
const result = await runWorkflowFixture(process.argv.slice(2))
|
|
6
|
+
if (result.out !== '') process.stdout.write(result.out)
|
|
7
|
+
if (result.err !== '') process.stderr.write(result.err)
|
|
8
|
+
process.exitCode = result.code
|