@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.
@@ -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