Shoddy · Picker

Reference

What belongs to each level, every refusal with its level, the query gate, column scoping, DESCRIBE, the caps, doctor's lines and the exit codes.

This page holds the tables you look things up in. pick works at three levels, called tools, guardrails and screenings, and every refusal and every doctor line below says which level it belongs to. A refusal that belongs to the guardrails level can only happen when guardrails are on.

What belongs to each level

The file .picker.json holds up to three sections, one for each level, and nothing else at the top. The tools level is always on, and the file must name at least one server in its tools section. Each of the other two levels is on when the file has its section.

LevelSectionWhat it holdsWhich file may hold it
toolstoolsthe servers, and for each server the connect value that says how to reach it.picker.json, while the local file may supply connect and nothing else
guardrailsguardrailsthe servers, and for each server its databases. Each database has can and scopes, and each scope has object, can and columns..picker.json and no other file
screeningsscreeningsthe models folder, and screen, whose keys name a database, or a database, a slash and a scope's object pattern.picker.json, while the local file may supply models and nothing else

Here is a full file that uses all three levels:

{
  "tools":      { "servers": { "main": { "connect": "env:MAIN_SQL" } } },
  "guardrails": { "servers": { "main": { "databases": {
                    "Sales": { "can": ["list", "select", "describe"],
                               "scopes": [ { "object": "dbo.Customers", "can": ["list", "select"],
                                             "columns": ["-ssn", "-dob"] },
                                           { "object": "audit.*", "can": [] } ] } } } } },
  "screenings": { "models": "../screening-models",
                  "screen": { "Sales": ["identifiers"], "Sales/dbo.Customers": true } }
}

Configuration refusals

pick reads both files before every call, and if either file breaks a rule, pick refuses to load it. The call then ends with exit code 2, invalid, and the message names the file. An unknown word anywhere in a file is refused too, and the message lists the words that are allowed in that place.

At every level

What the file doesWhat pick says
It has servers or models at the top, which is the shape from before release 2.1.0.The file has the old shape, and the message ends with your own entries rewritten into the three sections, ready to paste.
It has a key at the top other than the three section names.The key is not something the file declares, because the file takes three sections, one per level: tools, guardrails and screenings.
.picker.local.json holds anything other than connect and models.The local file may supply models and connect only, so declare servers, databases and screens in .picker.json.

Tools level

What the file doesWhat pick says
It names no server at all.There is nothing to open, so declare a server under tools.
A server has no connect in either file.Name an environment variable as env:NAME, or put the connection string in .picker.local.json.
A connect in .picker.json holds a password.That file is checked in, so move the string to the local file or use env:NAME.
A server under tools declares databases.Databases go under guardrails, and the message shows the line to write.
.picker.local.json names a server that .picker.json does not declare.Declare every server in .picker.json.

Guardrails level

What the file doesWhat pick says
A server under guardrails has a connect.A connection belongs to the tools level, so put it under tools.
guardrails declares databases on a server that tools does not name.Declare the server under tools with its connect.
A scope has no can.Write the verbs the scope allows, where an empty list allows nothing.

Screenings level

What the file doesWhat pick says
A database under guardrails has a screen.A screen now goes under screenings, keyed by the database, and the message shows the line to write.
A scope under guardrails has a screen.A screen goes under screenings, keyed by the database, a slash and the scope's object pattern.
With guardrails on, a screen key names a database that no server declares.The screen names nothing that is declared under guardrails.
A key such as Sales/dbo.Orders names a scope that the database does not declare.Declare the scope under guardrails, or screen the whole database.
With guardrails off, a key such as Sales/dbo.Orders names a scope.Scopes belong to the guardrails level, which is off, so screen the whole database or declare the scope under guardrails.

The old shape, rewritten

Take a file in the shape that releases before 2.1.0 read:

{ "servers": { "main": { "connect": "env:MAIN_SQL",
    "databases": { "Sales": { "can": ["list", "select"], "screen": ["identifiers"] } } } } }

pick refuses it, and the message ends with the rewrite. The message is one line, which is shown here wrapped:

pick: .picker.json has "servers" at the top, which is the shape this version no longer
reads. Rewrite it with three sections, one per level. Your entries, rewritten:
{"tools":{"servers":{"main":{"connect":"env:MAIN_SQL"}}},"guardrails":{"servers":{"main":
{"databases":{"Sales":{"can":["list","select"]}}}}},"screenings":{"screen":{"Sales":["identifiers"]}}}
(C:\github\app\.picker.json)

The rewrite moves each connect under tools and each database under guardrails. It moves models and every screen under screenings, where a scope's screen gets a key made of the database, a slash and the scope's pattern.

How a name resolves

These rules decide which table a name in a query means. The first group applies at the tools level, so it always applies.

The second group applies at the guardrails level, so it applies only when guardrails are on.

The verbs, in detail

All five verbs belong to the tools level. They work the same way at the command line and through MCP, and the MCP answer equals the answer the command line gives with --json. The flag --config PATH names the file to use, and without it the nearest .picker.json at or above the current folder applies.

VerbTakesAnswers
catalogsnothingthe three level lines, then the servers, databases, verbs, screens and scopes, with each open server marked as one. It opens no connection.
objectsan optional pattern, either schema.object or a bare name, with * inside a partthe tables and views whose scope grants list, stopping at 500 and saying so
selectone statement, or --stdin to read it from input, with --limit N to set the row capthe rows with their types and the column list, or the gate's refusal
describean object namethe columns with their types and whether each may be empty, then the indexes, the foreign keys and a shape hash
doctornothingthe three level lines, and then the checks listed under doctor's lines

With --json, the catalogs answer adds a levels object. It also gives each server an open field, which is true when guardrails are off.

The query gate

The gate belongs to the tools level, so it is always on, and every refusal in the table below is a tools-level refusal. The gate parses every statement before anything reaches the server, which means it breaks the text into its parts and checks each one. It checks the parts against a list of the ones it knows, and anything else refuses the whole statement. So a new T-SQL feature is refused until the gate learns about it.

RefusedWhy
anything but one SELECTINSERT, UPDATE, DELETE, MERGE, EXEC, DECLARE, USE, transactions and a second statement are each refused, and each with its own reason.
SELECT … INTOIt writes a new table, so it is a write that looks like a read.
OPENROWSET, OPENQUERY, OPENDATASOURCEEach one carries a connection to some other data source inside a single function call.
user-defined functionsThe gate cannot see the code inside a function, so it cannot check it.
variables and table hintsThey are outside the parts the gate knows, and each one is refused by name.
four-part namesA four-part name reaches a linked server, which is a second server.
sys.*, INFORMATION_SCHEMA, OBJECT_DEFINITION and similar functionsThe system catalog would answer questions about tables outside the rules, while describe answers them inside the rules.

pick rewrites a statement that passes before it runs it. Every table and view becomes its full three-part name in square brackets, so the statement can only mean what the gate approved. The star in SELECT * becomes the list of columns you may read, and the answer shows that list. The gate walks CTEs, subqueries, derived tables, APPLY and set operations in the same way, and it approves a query whole or refuses it whole.

Column scoping, everywhere a column can appear

Column rules belong to the guardrails level. A scope's columns entry says which columns exist for the caller, and Guardrails shows how to write one. This section says where pick enforces the rule.

DESCRIBE, and what a view will not say

The describe verb belongs to the tools level. It answers from the system catalog with the columns, their types and whether each may be empty, and then the indexes and foreign keys. It never runs the object it describes. With guardrails on, hidden columns are missing from all of it. An index or key that uses a hidden column is left out whole, because a note saying "one column not shown" would show that the column exists.

Every answer carries a shape hash, a short code computed from the table's shape that changes when the shape changes. A view answers with its columns and never with its definition. The text of a view names the tables under it and how they are joined, which may be exactly what the scopes hide. So pick never returns the CREATE VIEW text of any view.

Caps, stated in the answer

The caps belong to the tools level, and the configuration file cannot change them. Each answer says so when a cap cut it short.

doctor's lines

The doctor verb prints one line for each check. A healthy line starts with ok and a failed one with !!, and the three level lines always come first. A level that is off gets one line, and doctor runs none of that level's checks.

LineLevelWhat it reports
toolstoolson, always, followed by the files that hold a tools section
guardrailsguardrailsoff, or on, from followed by the file that holds the section
screeningsscreeningsoff, or on, from followed by the files that hold the section
configurationtoolsthe path of the .picker.json that pick is using
server NAMEtoolswhether a short test connection opens. With guardrails off, the line says the server is open, and with guardrails on, it counts the declared databases. A failure shows the first line of the server's error.
modelsscreeningsthe models folder, or that the folder does not exist, shown only when screenings are on and a folder is named
burlerscreeningswhere the helper program is, or where pick expected to find it, shown only when screenings are on

The doctor verb never prints a connection string or a password. It says whether the test connection opened, and not how it signed in.

Exit codes

Each kind of failure has its own exit code, so a script can branch on $LASTEXITCODE without reading any text. pick uses fettle's numbers wherever the words match, so one script can drive both programs. The gaps in the numbers are fettle outcomes that pick cannot produce, such as a stale hash, and no number is reused for a different word. A check in this repository holds this table to the source code, row by row.

CodeOutcomeMeaning
0okit worked
1—an unexpected internal fault
2invalidthe request did not make sense: an unknown flag, a missing value, or a configuration that pick refuses to load
3not-foundnot there, or not there as far as anyone can learn. An object outside the grant answers exactly as one that does not exist
8refusedpick declined, for a stated reason: a verb the grant withholds, or a query the gate will not pass
9deniedthe server, the network or the system declined, and not pick: a login without rights, or a connection that will not open
10timed-outthe query ran past its 30 seconds and was stopped
13screenedthe answer would have disclosed regulated data from a screened database or scope, or the screen that would judge it could not run. See Screenings

The outcomes denied and refused are kept apart on purpose. If a login failure looked like a gate refusal, you would spend time debugging rules that are working.

What this defends against, and what it does not