# Pairwise Desk > Paste a Microsoft PICT model - parameters and their values, `~negative` values, `a | alias` aliases, `(n)` weights, `{ A, B } @ n` sub-models and `IF ... THEN ...;` constraints - together with a sentence or two about the feature it models, and the browser parses it, generates the t-wise (usually pairwise) covering array, and proves its own work: every achievable tuple appears in some test, every test satisfies every constraint, no test carries two negative values. That part is free, before you sign in. Then a senior test designer judges whether the model is the right model for the feature, proposing changes as operations the browser applies for you - or turns every generated row into a test case a tester can run. Live at https://pairwise-desk.skillsafe.ai/ · API tutorial at https://pairwise-desk.skillsafe.ai/api.html · Tokens at https://pairwise-desk.skillsafe.ai/tokens.html ## What it does One work object: one PICT model, pasted as text, plus a short spec saying what the feature or product does. The model is the Microsoft PICT model language, and the desk reads the parts of it that matter for combinatorial design. The free engine (no account, no model) ships as one plain script - `/pict.js`, loadable in node with `global.window = {}`, then `window.Pict.analyze(text, {order: 2})`. What it supports and what it computes: - parameters, one `Name: value1, value2, ...` line each; `#` comments; duplicate values dropped with a warning; a single-value parameter warned about because it never varies - `~value` negative (error-path) values: PICT semantics, so at most one negative value appears in any test, and a negative value is paired with every positive value of the other parameters rather than with other negatives - `value | alias | alias` aliases, kept on the value and reported in `facts.params[].aliases` - `value (n)` weights, reported in `facts.params[].weighted` - `{ A, B, C } @ n` sub-models: a higher-strength coverage target over a named subset, generated alongside the model-wide target - constraints: `IF pred THEN pred [ELSE pred];` and bare predicates ending in `;`, with `=`, `<>`, `<`, `<=`, `>`, `>=`, `IN { a, b }` and `LIKE "pat*"`, combined with `AND`, `OR`, `NOT` and parentheses; string, bare-word and numeric values; and parameter-to-parameter comparisons, `[A] <> [B]`, not just parameter-to-literal. A constraint naming a value without the `~` still matches the negative value it names. - greedy t-wise covering-array generation, order 2 by default and 1 to 6 accepted by the script (the page offers 1 to 4, which stay fast in a browser tab), deterministic: the same model text always yields the same suite, so the same facts and the same rows can be regenerated at any time - the self-check, recomputed on every run as `facts.self_check` and kept local rather than sent to the model, because it is the caller's evidence and not something the designer is asked to re-check: every row satisfies every constraint, no row is a duplicate, every non-excluded required tuple appears in some row, `covered_tuples == required_tuples`, no row holds two negative values - the excluded tuples, listed one by one in `facts.excluded` with the cells and the scope, and (for the first forty) the reason: `why` is `negatives` when no constraint is involved and every test holding the tuple would need a second negative value, otherwise `constraints` with `blocked_by` naming each single constraint whose removal alone would make the tuple achievable (an empty list means only a combination of constraints excludes it; `why` and `blocked_by` stay on the page and are not sent to the model): these are combinations the constraints make unachievable, so they are removed from the coverage target rather than quietly missed. An excluded tuple the spec does not actually forbid is the signature of a constraint that is too strict. - a lower bound on the suite size (the largest product of the t biggest parameters), reported as `facts.summary.lower_bound`, with a warning when the generated suite meets it - which is the only case in which the suite is known to be as small as any t-wise suite over that model can be - the headline figures: `tests`, `product` (the exhaustive count), `reduction_pct`, `tuples_required`, `tuples_covered`, `tuples_excluded`, `coverage_pct`, `parameters`, `values`, `constraints`, `submodels`, `negative_values`, `negative_rows`, `largest_parameter`, `unused_values` - exports: the suite as CSV (`Pict.toCsv`), as a Markdown table (`Pict.toMarkdown`), and the facts as JSON; `Pict.normalize` prints the model back as the parser read it, and `Pict.applyOperations(text, operations)` applies a review lane's proposed changes and returns `{text, applied, rejected, errors}` Two metered lanes (gpt-terra, one system prompt with a task router): - `review` - a test designer judges whether the model is the right model for the feature the spec describes: which inputs, states, environments and roles vary, which values are equivalence classes, which combinations the product forbids and therefore need constraints, which error paths deserve a negative value, which parameters are really one parameter or really irrelevant. Changes are proposed as operations - `add_parameter`, `add_value`, `remove_value`, `mark_negative`, `add_constraint`, `remove_constraint`, `rename_parameter` - never as a rewritten model, at most twelve of them, and every constraint in the model is judged exactly once. Verdict: complete / gaps / rework / insufficient_spec. Body: reading, operations, constraint_review, coverage_notes. - `cases` - every generated row becomes a test case a tester can run: a title, what the row is a witness for, two to five steps and an expected result, each derived from that row's own values and the spec. A row holding a `~` value is a negative test and its expected result is the rejection the spec describes. Verdict: named / partial / insufficient_spec. Body: cases, gaps. Handoff: the review lane's operations are applied by the browser with `Pict.applyOperations`, the model is re-parsed and re-generated, and the before and after suites are shown side by side; the regenerated suite is then what the `cases` lane is handed, so a review turns into named tests without anyone retyping the model. Rows are named in batches of thirty, each batch its own run, with the engine's ids preserved so the cases line up with the rows. Exports: the suite as CSV, the numbers and the suite as Markdown, the result as Markdown and JSON. ## The contract Run body: `{ "task": "review" | "cases", "spec": string, "model": string, "facts": string, "rows": string }` posted as the body itself - never wrapped in `{"input": ...}`, which returns 200 and hides every field from the model. All fields are scalar strings; `task`, `spec`, `model` and `facts` are required, and `rows` is required in the `cases` lane and absent otherwise. `facts` is `JSON.stringify(Pict.analyze(model, {order: 2}))` with `rows`, `self_check` and `summary.value_counts` removed and `excluded` capped at forty entries (the count held back is reported as `excluded_omitted`); `rows` is `JSON.stringify(facts.rows.slice(0, 30))`. An optional `retry_note` is sent only on the automatic reformat retry. The reply is one JSON object - no prose, no code fences, no Markdown inside the strings - with `lane`, `title`, `headline`, `verdict`, `summary`, `notes_on_input`, `risks`, `next_steps` and the lane body. An empty list is `[]`, never omitted. - `review`: `reading` (3-6 sentences, quoting `facts.summary.tests` and `facts.summary.parameters` exactly), `operations[] {op, ...fields for that op, why}`, `constraint_review[] {constraint, verdict: keeps|too_strict|too_loose|redundant, note}`, `coverage_notes[]`. A verdict of `complete` carries zero operations; `gaps` and `rework` carry at least one. - `cases`: `cases[] {id, title, kind: positive|negative, exercises, steps, expected}`, `gaps[]`. One case per row, same id, same order. Reconciliation, done by the browser on every run and worth doing in any pipeline: (1) the model computes nothing, so every figure it writes is meant to be a verbatim copy out of `facts` - the numbers are pulled back out of the prose and compared with the engine, and a test count, a percentage or a parameter count that is not in `facts` is shown as a disagreement rather than as a finding; (2) operations are applied by the engine, not pasted as text, and an operation the engine rejects - a parameter that does not exist, a value already listed, a constraint that will not parse - comes back in `rejected` with the reason and is shown as a disagreement; (3) in the `cases` lane the set of `cases[].id` must equal the set of `rows[].id` exactly, and a case must name only values its own row holds. ## Limits The suite is greedy. It is proven complete against its own coverage target on every run, and that proof is `facts.self_check`, but it is not proven minimal except in the case where it meets the reported lower bound. Microsoft PICT itself may produce a suite of a different size and a different row order from the same model; this is an independent implementation of the model language and of t-wise generation, not a port of PICT. Coverage is combinatorial, not semantic: a pairwise suite proves that every achievable pair of values appears together somewhere, and nothing more - a defect that needs three specific values at once can sit inside a complete pairwise suite. And the desk designs tests; it does not run any test, execute any step, or see any result. ## Sources Derived from the agent skill @sickn33/pypict-skill (https://skillsafe.ai/skill/@sickn33/pypict-skill), the PICT Test Designer workflow, whose upstream is omkamal/pypict-claude-skill (https://github.com/omkamal/pypict-claude-skill) - the test-design judgement both lanes follow. The model language and its semantics, including the treatment of negative values, aliases, weights, sub-models and constraints, are Microsoft PICT, microsoft/pict (https://github.com/microsoft/pict). Pairwise and t-wise combinatorial testing as a method: Kuhn D.R., Kacker R.N., Lei Y. (2010) Practical Combinatorial Testing, NIST Special Publication 800-142. Not affiliated with the skill's author or with Microsoft.