Skip to content

Torque blueprint schema alignment + tests - #184

Open
dannykQuali wants to merge 24 commits into
masterfrom
dannyk/schema_alignment
Open

dannykQuali wants to merge 24 commits into
masterfrom
dannyk/schema_alignment

Conversation

@dannykQuali

Copy link
Copy Markdown

No description provided.

dannykQuali and others added 24 commits August 24, 2026 19:49
…onal inputs

Sync with cs2018 (39042894f2) and cs2018-ui:
- resources: "reference" long syntax ({ids: ...}) alongside the string form
- outputs: "sensitive" flag
- grains: shell file "tag", OpenTofu script hooks + opentofuvars-files,
  stack-name-prefix, env_configuration, target/deployment-engine short syntax,
  source name/chart-version/path-in-archive, host docker permissions,
  remote/cloud backend fields
- new top-level sections: api_access, family, template
- environment: description/tags/env_references_values/labels; workflow: enabled
- inputs: "dictionary" type added, legacy "execution-host"/"env" removed
  (server rejects them), object defaults, vCenter + target_name overrides
- grain kinds: "arm" removed (not in GrainKind)
- customization: launch-form image/actions/params, steps general/inputs,
  category/section image+actions, input display_name/allowed-values-details/
  display-as/columns, legacy array form, customization.inputs/params/layout/
  grains-map

Consolidate duplicated definitions:
- one GrainSpecSourceObject for all source objects (ScriptSource and
  TfVarsFileSourceObject deleted; script sources now require "path"),
  SourceFileObject wrapper for values/workspace/tfvars/opentofuvars files
- StoreFileSourceObject (instructions+layout), EnvironmentLabelsArray,
  KeyValuePairs, StringMap, TemplateParamsObject
- dead-weight cleanup (always-true oneOf, $ref siblings, stray keywords)

Document optional-input semantics in descriptions: an input is required iff
it has no pattern; blank-allowed needs an empty-matching pattern (^$|...);
default: "" does not make an input optional; sensitive inputs pre-fill empty;
all-hidden launch-form sections still render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documentation pass over every schema field, aimed at humans and AI agents
authoring blueprints (surfaced by VS Code as hover/completion docs):
- 126 new descriptions: a Liquid-templating primer on the root, per-section
  explanations, per-input-type and per-grain-kind semantics, source pinning,
  resource requirement shapes (quantity: single vs list), launch-form
  customization, grain-map authoring rules
- wording aligned with the customer-facing docs (docs.qtorque.io) where they
  cover the field; source-derived wording kept for features the public docs
  do not cover yet (resources, env_references, target/resource/dictionary
  inputs, api_access)
- optional-input semantics documented where authors make mistakes: required
  iff no pattern, '^$|' convention for blank-allowed, default: "" gotcha,
  sensitive inputs are write-only after launch (no copy / no Relaunch
  carry-over / re-enter on edit)
- every non-obvious claim audited against cs2018 source; corrected findings:
  auto-approve/auto-retry default true, auto-approve: false requires runner
  storage, mode is per-kind (terraform: managed/no-termination, argocd: data),
  spec.region is CloudFormation-only, space-scoped workflows are manual-only,
  env references target published environments

Removed dead fields the server ignores, so the extension flags them instead
of silently accepting them:
- grains.<name>.spec.host (GrainSpecYaml has no such member)
- agent.image (parsed but consumed nowhere; runner images come from
  RunnerMgmt image mapping)

Also fixed two title typos ("Envrironment Variable", "LaunchForm Object").
Validation behavior is otherwise unchanged: with description/title stripped,
the schema is structurally identical to the previous commit except the two
removed fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Torque server parses neither grains.<name>.spec.host (GrainSpecYaml has no such member) nor agent.image (parsed but consumed nowhere). The JSON schema already rejects both since 4852bf2; removing them from the language server's tree model keeps the two models of the language consistent. No references to either field existed in server code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, robustness

The Python language server's spec2 support lagged years behind the schema:
its tree model knew only spec_version/description/inputs/outputs/grains, so
every modern blueprint got false "Parent node does not have child" errors,
and its expression validator rejected current Liquid.

Tree model (server/ats/trees/blueprint_v2.py, rewritten):
- mirrors the audited JSON schema: all top-level sections (metadata,
  environment, workflow, instructions, layout, labels, env_references,
  resources, api_access, customization, family, template) and the full
  grain/spec/agent/scripts/source/input/output field set
- scalar short forms (target, deployment-engine, reference, blueprint-labels,
  self-service) via Union annotations; new FreeFormNode/FreeFormProperty
  primitives for opaque content (customization, inventory-file, provider
  attributes, string maps)
- dead fields stay rejected (spec.host, agent.image, input display-style);
  fixed the bogus display-style -> style input field
- framework (common.py): Union-aware get_child and PropertyNode.__getattr__,
  guarded _get_seq_nodes, per-class field-mapping cache (parser hot path)

Expression validation (server/validation/bp_v2_validator.py):
- prefixes: + .resources, .env_references (cs2018's tests use both)
- reserved variables: + envId, environmentName, blueprintName, ownerEmail,
  accountName, spaceName; filters: + key_access, strip, with argument
  handling; bare .grains.<g>.outputs accepted (valid all-outputs form)
- free-form subtrees are skipped: customization uses the UI's template
  dialect and inventories may hold Jinja - not grain Liquid

New semantic validations, each mirroring a cs2018 server rule:
- resource requirement: exactly one of selector/reference
- grain: agent and target are mutually exclusive
- mode per kind (terraform: managed/no-termination; argocd: data, mandatory)
- auto-approve: false requires runner storage (use-storage)
- workflow: scope enum, space scope is manual-triggers-only, per-trigger
  event/cron shape, timeout integer >= 5 minutes

Robustness (a validator exception wipes ALL diagnostics for the file):
- fixed crashes on empty spec:, variable-like keys in free-form sections,
  .grains.<g>.scripts refs to grains without scripts, Union-typed property
  access; loop aborts no longer suppress later grains' diagnostics;
  orphaned short-form nodes (commands, blueprint-labels) now surface errors;
  verified by a 5000+-prefix typing simulation with zero exceptions

Schema parity fixes discovered by this work:
- aws-cdk script hooks are ScriptOutputsObject (match ScriptsYaml)
- provider-overrides items closed to name/source/version/attributes
- version-like fields (spec version, tf-version, estimated-ccu,
  chart-version, provider version) accept unquoted YAML numbers, with a
  lossy-float authoring warning on the Terraform version fields

Tests: 48 new tests (tests/test_spec2.py, tests/test_spec2_semantics.py)
covering tree coverage, expression rules, semantic rules, and robustness;
fixed tests/test_ast.py posixpath.dirname import that broke the suite on
Windows. Full suite: 71 tests, green on the pinned Python 3.7 stack.

Docs: docs/spec2-language-support.md records the two-layer design, the
dead-field policy, the rule tables with their cs2018 provenance, and the
test setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validated both layers (JSON schema + Python language server) against every
spec_version: 2 file in six production blueprint repos, then fixed each
divergence from actual Torque server behavior (all verified in cs2018
source). Result: fully-clean files 61 -> 349 of 452, crashes 1 -> 0,
language-server errors 3301 -> 375, warnings 1058 -> 16 - and everything
still flagged was individually confirmed to be a genuine blueprint defect
(dead keys the server drops, undeclared outputs, misspelled context members).

Parser (server/ats/parser.py):
- flow-style YAML support: [a, b] sequences and {k: v} mappings are
  translated to the equivalent block token stream (162 real files
  previously failed wholesale with "Scalar cannot be accepted here")
- unprintable characters (mojibake C1 bytes) are replaced 1:1 instead of
  crashing pyyaml; server.py's raw yaml.load path sanitizes the same way
- fixed two unmasked pre-existing bugs: a block sequence at the key's
  indentation corrupted the node stack, and the branch never closed the
  map element so the next key silently erased the previous one

Expression validation (server/validation/bp_v2_validator.py):
- proper path tokenizer: bracket access (.inputs["Name With Spaces"],
  .inputs.["X"]), dotless forms ({{ inputs.X }}), and leading-dot reserved
  variables ({{ .envId }}) - DotLiquid's path scanner drops the dot
- .grains.<g>.activities.<activity>.commands.<cmd>.outputs.<out> validated
  as a first-class path (cs2018 CreateActivitiesContext), with output-name
  checking against the command
- depends-on closure is transitive (cs2018 GetAllDependentGrains recurses)
- grain members: outputs/scripts/activities/is_active; prefixes gained
  management_server, and bindings for workflow-scoped blueprints
- filters: the full DotLiquid standard set plus Torque customs
  (key_access, json, json_escape, resource_property, resource_object),
  chained pipes, arguments after ':'
- unused-input warning matches all real usage spellings

Tree model (server/ats/trees/blueprint_v2.py):
- authentication entries and source store/path/files paths take Liquid
  (the server resolves them); grain spec input values may be YAML lists
  or mappings (the server JSON-ifies them)

Schema (client/schemas/blueprint-spec2-schema.json):
- grain input values accept arrays; blueprint-label values accept
  numbers/booleans; spec_version accepts 2, "2" and "2-preview"
  (all mirror the cs2018 parser)

Tests: tests/test_spec2_real_world.py (20 tests) pins every rule above;
full suite 91 tests green. docs/spec2-language-support.md updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OpenShift target support adds no blueprint YAML fields - targets are referenced by name - so only value lists change:

- allowed-credential-providers: add 'kubernetes' and 'sourceControl' (pre-existing gaps vs CredentialCloudTypes.Supported). Deliberately NOT 'openshift': openshift targets authenticate with kubernetes credentials (TargetCapabilityValidatorBase mapping), and the description now says so.

- resource selector provider-type: 'openshift' added to the soft enum (new __openshift__ built-in inventory provider type).

- target-filters cloud-providers: items upgraded to the same soft-enum pattern with the six server-validated target types (aws, azure, vcenter, intersight, kubernetes, openshift).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ExpressionValidationVisitor computed a diagnostic position as (node line, node column + offset-in-value), which is only correct for a single-line scalar. In a block scalar (command: |) the offset indexes the joined text, so errors landed on the block header's line with an impossible column - a real corpus file reported 236:154 where line 236 is 'command: |' (24 chars) and the expression was on line 239. Shell grains are the most common case, so most expression squiggles pointed nowhere.

The visitor now receives the document and, for multi-line values, locates each match by scanning the document lines within the node's line span, consuming occurrences in order so repeated expressions map to successive lines. Single-line positions are unchanged, and the old arithmetic remains the fallback when no document is available or a match cannot be located (a validator exception would wipe every diagnostic for the file).

Found by validating 454 real blueprints; that finding now reports 239:20, the exact column of the '{{'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two human-operated scripts for testing both validation layers against
customer blueprint corpora, which carry proprietary data and must never be
read by an AI.

tools/blueprint-corpus/sanitize_blueprints.py (stdlib only, no install):
- text-based redaction, so comments, flow style, indentation quirks and
  even mojibake survive - a load-and-dump would normalize away the very
  defects the validators must be tested against, and would fail on files
  that do not parse
- two mechanisms: secret-ish key names redact the whole value (including
  default:/value: under such a key, the most common real leak), and value
  patterns redact substrings, including inside shell command bodies
- keeps the corpus faithful: keys are never rewritten, pure-Liquid values
  and Liquid spans are never touched, pattern:/validation-description: are
  exempt from both mechanisms (a pattern decides Torque's required-vs-
  optional semantics), and content-addressed keys (commit, image, digest,
  tag, version, path, store, ...) are exempt from the entropy heuristics
- never modifies the input: sources open read-only, output-inside-input is
  refused, and every source file's sha256 is asserted unchanged; non-YAML
  files are never copied, so a sanitized tree cannot hide a raw file
- reports what was redacted by location, category, length and a hash
  prefix - never the value - plus a residual scan of the output and a
  review list of near misses; --self-test (94 checks) lets the operator
  verify the tool on their own machine

tools/blueprint-corpus/validate_blueprints.py (pyyaml + jsonschema):
- runs the JSON schema layer (with oneOf drill-down so messages are
  readable) and the language-server layer (parser, tree errors, semantic
  diagnostics, crashes) over a corpus
- report levels: full reports are confidential; safe-summary.txt is
  designed to be shareable and always uses anonymous file ids resolved
  locally via path-map.txt, so it cannot leak identifying file names
- triage against known_findings.json (43 verified entries): findings are
  labeled KNOWN-BLUEPRINT-DEFECT / KNOWN-TOOL-LIMITATION, and NEW clusters
  are surfaced first because those are the likely tool bugs
- runs on any modern Python: falls back to a minimal pygls stub, verified
  to produce byte-identical reports to real pygls 0.11.3 on Python 3.7
  over the 454-file reference corpus

Verified on the six internal ZeroTouch repos: 454 spec2 files, 350 fully
clean, 0 crashes, 790 findings all KNOWN. Sanitization is validation-
neutral - all 454 files produce identical finding sets before and after,
line numbers included - and git reports zero modified files in all six
source repos.

Also documents six newly verified blueprint-defect classes found this way,
including env-vars under activities.destroy (silently dropped, so those
variables never reach the commands) and {{ .outputs[...] }} in workflows
(the correct form is {{ .bindings.outputs[...] }}).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both workflows have been failing on every push, including on master, since well before the current work.

ci.yml: the test job targeted runs-on ubuntu-20.04, an image GitHub retired, so both matrix legs were cancelled within seconds and the Python suite has not actually run in CI for months. Now ubuntu-22.04, the newest image still offering a Python 3.7 build (the server pins pygls==0.11.3, which predates the lsprotocol API). The 3.6 leg is dropped: no 3.6 build exists on 22.04 and 3.6 has been EOL since 2021.

vscode_build.yml / release.yml: 'vsce package' died with 'ReferenceError: ReadableStream is not defined' - the workflows pinned Node 16 while an unpinned 'npm install -g vsce' pulled transitive deps (undici 7, cheerio 1.2.0, whatwg-*) that require Node >= 18, where ReadableStream became a global. Now Node 20, the renamed and maintained @vscode/vsce pinned to ^3, and the redundant 'npm install' after 'npm ci' removed - that re-resolution is what let the drift in. release.yml had no npm ci at all, so its npm install became npm ci.

Action versions bumped off the deprecated Node runtimes (checkout@v2 -> v4, setup-node@v1 -> v4, setup-python@v2 -> v5).

Verified locally on Node 22 before changing anything: npm ci, npm run compile and npm run webpack all succeed (webpack 5.64 has no OpenSSL regression on modern Node), and the failing step packages a 235 KB vsix of 54 files with both vsce@2.15.0 and @vscode/vsce 3.9.2. Also confirmed super-linter v4 disables trailing-spaces and treats document-start and line-length as warnings, so touching these files cannot turn the green lint job red.

Still broken and deliberately untouched: release.yml uses ::set-output (disabled by GitHub in 2023) and the archived create-release@v1 / upload-release-asset@v1. Whether setup-python@v5 can provision 3.7 on ubuntu-22.04 can only be confirmed by a real run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds docs/schema-removals.md: what was removed from the spec2 schema, the evidence each removed item is dead in the Torque server, the measured blast radius, options, and rollback. Written for a manager to approve before the change reaches production.

The investigation behind it corrected a wrong assumption of mine: the schema is NOT authoring-only-until-released. The Torque web UI fetches this exact file live from raw.githubusercontent master and feeds it to monaco-yaml in six editor surfaces (both designers, the setup wizard blueprint viewer, the grain inventory and reserved-resources tabs, and the custom-workflow YAML view), so the change reaches every Torque user within minutes of the merge, with no release and no version pin. Conversely the extension itself stopped registering the schema in 0.3.3, so the UI is now its primary consumer.

What Torque accepts is unchanged: POST /validations/blueprints runs the C# V2BlueprintValidation validators, and cs2018 has no JSON-schema-based blueprint validation in any .cs source. No deployment behavior changes and nothing needs un-deploying.

Measured over 527 real spec2 blueprints: 12 files (2.3%) will show a new editor error, and in all 12 the error is correct - it points at a key Torque already ignores. Also records the one place the schema is deliberately stricter than the server (script source requires path), the one place the consolidation loosened (tfvars source gained keys), and a separate recommendation to pin the UI to a tag or SHA instead of master.

known_findings.json: corpus figure corrected to the 454-file calibration set plus the 73-file out-of-sample check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It was written for a manager's one-time approval decision and is delivered as a shared page, not as repository documentation. The corpus-count fix to known_findings.json from the same commit stays.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…open schema maps

Re-verified against cs2018 origin/main c0f49bd04d (2026-09-14) and cs2018-ui
9fe9e4b01b. The schema had been synced to a source snapshot from 2026-08-24;
three backend changes landed after it and are now mirrored in both layers
(JSON schema and the Python language server):

- inputs.<name>.optional (cs2018 7571846285, 2026-08-30): bool?, only
  meaningful for string and dictionary inputs. 'optional: true' with a pattern
  that rejects the empty string is a blueprint validation error, which the
  language server now reports too, evaluating the pattern the way the server's
  PatternEmptyValueEvaluator does (regex-literal unwrapping, no added anchors,
  an unevaluable pattern never fails). The launch form's required-ness rule
  changed the same day - a default, even "", now makes an unset-optional input
  optional unless its pattern rejects "" - so the default/sensitive/pattern/
  validation-description descriptions that stated the old rule were corrected.
- Server-side pattern enforcement at launch (cs2018 8d62b1be5d, 2026-09-09),
  with Liquid allowed in pattern and validation-description.
- inputs.<name>.target-filters.labels[].values (cs2018 eec4e2859a): a list
  alternative to value, any-of match; value and values together is an error in
  the schema (not/required) and a language-server diagnostic.

Name maps: the server constrains grain names to ^[a-zA-Z0-9 \-_]+$ with no
length limit and does not constrain input or output names at all. The schema
demanded 3-45 characters and, with no additionalProperties on the inputs,
grains, outputs, env_references and resources maps, silently skipped every
off-pattern entry instead of validating it - real blueprints name inputs OS
and 'Ethernet1/1 Port Group', grains db, outputs id. Inputs/outputs/
env_references/resources now accept any name; grains mirror the server regex
and reject the rest.

Script hook objects (ScriptObject, ScriptOutputsObject) are now closed: the
server's ScriptYaml reads only source/arguments(/outputs), so 'files' under a
hook - present in 13 local blueprints - was accepted while the listed files
were never delivered.

Catalog: the two entries that called 'optional' a dead key are removed (true
until 2026-08-30), and the schema-layer twin of the files-under-hook finding
is added. Corpus re-run over 528 local blueprints from ten sources: 412 fully
clean, 0 crashes, 727 findings, all KNOWN, 0 NEW.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four spelling and hyphenation defects in the schema's hover text reached a
colleague's PR because nothing in the pipeline reads prose: the tests are
behavioral, super-linter checks JSON syntax, and a misspelled identifier such
as EnvironementVariable is internally consistent so every $ref resolves.

codespell 2.4.3 is configured in .codespellrc so a bare `codespell` from the
repo root behaves identically locally and in CI. Skipped on purpose: the two
CHANGELOGs (shipped release history, quoted verbatim in the docs) and the
gitignored client/ packaging copies. No ignore-words-list: a one-off false
positive gets an inline `codespell:ignore` on its own line instead, so a real
misspelling can never be whitelisted repo-wide.

Fixed: README 'opeation'; a parser comment 'doesnt'; 'unparseable' in the
sanitizer's docstring and self-test labels; a test grain named 'loner'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… real ones

tests/schema_fixtures holds 28 valid and 26 invalid spec2 blueprints written
from scratch with generic content - no real hosts, repositories, products or
credentials - each exercising one construct family. The test contract:

- every valid fixture validates with zero errors;
- every invalid fixture produces the errors named in its `# expect-error:`
  header lines, matched against all nested oneOf/anyOf messages, and an
  invalid fixture that is silently accepted is a failure - that is exactly the
  defect class (dead keys, off-pattern names, open hook objects) this corpus
  exists to catch;
- the union of key paths across the valid fixtures covers every one of the
  232 schema-accepted paths that 528 local blueprints actually use
  (required-paths.txt), so the corpus stays representative of real usage in a
  checkable way.

tools/blueprint-corpus/classify_corpus_paths.py is the read-only classifier
that produced required-paths.txt by walking documents alongside the schema; the
test reuses its normalization, and the README documents how to regenerate the
list when the corpus or the schema changes. Runs on Python 3.7 and current
Pythons with only pyyaml and jsonschema; 100 consecutive runs, no flakes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… name rules and fixture corpus

ci.yml gains a `spellcheck` job (codespell 2.4.3 on Python 3.12) and a
`schema-fixtures` job that runs tests/test_schema_fixtures.py on Python 3.12
with nothing installed but pyyaml and jsonschema, proving the fixture corpus
needs none of the legacy language-server stack. The Python 3.7 `test` job now
installs jsonschema, which the schema-layer test modules import; pip resolves
4.17.3 there, the last release supporting 3.7.

docs/spec2-language-support.md: a "Source freshness" rule (fetch and compare
to origin before trusting a local cs2018/cs2018-ui clone, record the SHA, treat
a negative grep as evidence only with a positive control), the September
changes to inputs (optional, the launch-form required-ness rule, server-side
pattern enforcement, target-filter label values), what a name may be, the
spell-check gate, and the fixture corpus contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o draft-07

Every rule below was read from cs2018 origin/main c0f49bd04d and is pinned by
tests/test_spec2_server_parity.py and tests/test_spec2_required_parity.py.

Draft upgrade. The schema declared draft-06 but used if/then/else in five
places (approval channels, tolerations) - keywords that do not exist before
draft-07, so compliant validators ignored them and those rules were dead: an
approval channel with no approvers and a toleration 'Equal' with no key/value
produced zero errors. $schema is now draft-07 and every validator in tests and
tools is Draft7Validator. Both consumers (yaml-language-server behind
redhat.vscode-yaml, and cs2018-ui's monaco-yaml) support draft-07.

Closed objects the deserializer would silently prune: Backend (and its
workspaces items: name, prefix, project, tags), TemplateStorage, GrainTag,
SourceFileObject. Backend 'type' is the server's BackendType enum.

Mandatory-field parity: a source needs store or path (family member: both);
agent needs name; instructions path must end .md and layout path .yaml, case-
insensitively; template placeholders need a non-blank path; approval channel
approver lists need at least one entry; a parameter input needs parameter-name;
a cloudformation grain needs region and one of authentication/agent/target;
backend needs type and, per type, s3 bucket+region, azurerm storage-account-
name+container-name, gcs bucket, http base-address, remote organization plus a
non-empty workspaces list whose entries carry name or prefix (hostname and
token are optional on the server), cloud nothing further. workflow.scope is the
EntityType enum (space, env, env_resource); workflow.timeout is an integer of
at least 5 minutes, a numeric string, or a Liquid expression; the trigger event
list gains 'Tag Updates Detected'.

Audits that changed nothing: the two-way key audit found only dead server
constants with no YamlMember (compute-service, cloud-account, role-arn,
external-id); all 32 input-source override keys match provider constants; no
unreachable definitions; every other enum already exact. GrainKind's internal
mock-terraform/mock-helm are deliberately not added.

Evidence: 528 sanitized local blueprints re-validated after every change -
412 fully clean, 0 crashes, 727 findings all KNOWN, 0 NEW. None of the new
rules rejects a working blueprint. README: the supported Python floor is 3.7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coverage contract is raised from "the 232 key paths the real blueprints use"
to everything the schema declares: every property of every definition (352),
every enum value (115) and every definition (58), plus the 232 real-world paths.
The sets are computed from the schema itself at test time, so a property added
without a fixture fails CI; the exclusion lists exist and are empty.

Fixtures: 46 valid (was 28) and 50 invalid (was 26), still minimal and generic.
New valid fixtures cover the environment section, full source objects at every
position, all eleven script hooks, on-destroy, agent docker/kubernetes and
tolerations, runner-configuration-override, all six backend types, template
storage, approval channels, every input-source override, every provider enum,
all workflow trigger events and timeout forms, and the whole customization
tree. New invalid fixtures pin each server rule from the parity pass.

The classifier now carries the owning definition through $ref/allOf/oneOf so a
walk records visited properties, enum values and definitions, and a
SchemaEnumerator computes the full sets by the same rules. Invalid fixtures pin
offending values rather than library wording, because jsonschema 4.17 (Python
3.7) and 4.18+ phrase minItems/minLength failures differently.

Verified on Python 3.7 and on fresh 3.13/3.14 venvs with only pyyaml and
jsonschema; 30 consecutive runs without a flake; no real content in any fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/spec2-language-support.md gains section 10: the draft-07 upgrade with the
proof that the if/then rules were dead under draft-06, the required-field
parity table with its server sources, the Terraform backend rules per type,
the closed objects and their server classes, the audits that found nothing to
change, and the 528-blueprint evidence that no new rule rejects a working
blueprint. Stale statements fixed: the "scope has no enum" example (it does
now), the workflow rules the schema can now express itself, and the testing
section's list of schema-layer modules and the coverage surface (352 properties,
115 enum values, 58 definitions). The fixtures README states the raised
coverage contract and the jsonschema wording caveat; one fixture header comment
no longer overstates what it pins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent every property

All 177 existing descriptions were checked claim by claim against cs2018
origin/main c0f49bd04d and the cs2018-ui source. Thirteen were corrected:

- the input object's summary still described the pre-2026-08-30 required-ness
  rule; it now defers to optional / default / pattern
- input depends-on also supports string inputs (InputTypesWithDependsOnSupport)
- allowed-values: a single value is auto-selected unless the style is multi-select
- resource-selector is optional on resource inputs; the server never requires it
- workflow label-selector is a legacy alias of resource-types (AutoMapper reads
  ResourceTypes = LabelSelector ?? ResourceTypes), not of labels-selector
- target has no scalar short form: the property carries only [YamlMember], there
  is no converter, and 607 of 607 real usages write the object - the schema's
  string branch is removed
- spec.version is the same setting as tf-version (both set is an error) and is
  exclusive with binary; when skips the grain and every dependent; auto-tag
  defaults to true; namespace vs target-namespace; the launch-form input types
  are spelled out

Every property now has a description (136 had none): the Environment-as-Code
section, all backend and template-storage fields, kubernetes and docker runner
permissions, runner overrides, approval conditions, shell file entries, every
input-source override, the resource-selector stubs, and the customization UI
contract. The UI's layout.exclude.resources (resources_layout_section.tsx) is
added to the schema with a fixture.

Corpus unchanged: 528 blueprints, 412 fully clean, 0 crashes, 727 findings all
KNOWN, 0 NEW.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o longer aborts the parse

The schema dropped its scalar branch for grain spec target (the server declares
GrainSpecYaml.Target with a plain [YamlMember] - no short syntax, no converter),
so the language-server tree model follows: target is GrainSpecTargetObject only,
declared like agent. tests/test_spec2_server_parity.py pins both layers.

Making that change exposed a pre-existing parser defect: a scalar given to any
object-only property (target, agent, backend, scripts, ...) raised a
ParserError, which server.py turned into a single diagnostic while discarding
the whole tree - every other diagnostic in the file vanished. The parser now
records a node error ("Scalar cannot be accepted here. Object expected"), keeps
the property on the stack the way the accepted-scalar path does, and continues
with the next key, so the rest of the file is still validated.

docs/spec2-language-support.md: the shorthand examples no longer cite target
(only [YamlShortSyntax] properties have one); a note on why the parser records
rather than raises; and section 11 recording the descriptions pass - the 13
corrected claims, the 136 newly described properties, and the rule that any
description stating a default, an "ignored"/"required"/"only" claim or a number
must cite its server source or be re-verified at the next sync.

Corpus unchanged: 528 blueprints, 412 fully clean, 0 crashes, 727 findings all
KNOWN, 0 NEW.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The schema is public: the Torque web UI fetches it live from this repository's
master branch and shows descriptions on hover, and anyone can read the raw file.
Its text must read as product documentation for blueprint authors, not as notes
between engineers.

Until now the $comments deliberately cited server validator classes, error
codes, source files and a corpus count as provenance. Of the 26 comments, 13 are
deleted (the description or the shape already said it) and 10 are rewritten in
plain language stating the rule and why; the three that only describe JSON
Schema mechanics stay. Eight descriptions are reworded - "the server" becomes
"Torque", an instruction aimed at a UI operator becomes a description of the
field, two ungrammatical launch-form texts are fixed.

Nothing is lost: docs/spec2-language-support.md gains section 12, a 26-row table
mapping each schema rule to the server class, method or error code that defines
it, and section 11's rule now points there.

tests/test_schema_public_voice.py enforces the policy - no server class or
method names, error codes, source paths, internal tooling or repository names,
corpus figures, or R&D voice anywhere in the schema's titles, descriptions and
comments, and no JSON Schema jargon or "the server" in descriptions. The
schema-fixtures CI job runs it alongside the fixture corpus on modern Python.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Backend definition carried two "$comment" keys in one object - one about the
closed key set, one about the per-type required fields. JSON parsers accept that
and keep the last one silently, so json.load, Draft7Validator.check_schema, every
test and the corpus run all passed while any consumer that keeps the first
occurrence saw different text. The two comments are merged into one.

tests/test_schema_syntax.py makes this class of defect visible: it parses the
schema with an object_pairs_hook and fails on any duplicate key in any object,
and also pins UTF-8 without BOM, LF endings, the declared draft-07 with a
meta-validation pass, and that every $ref resolves to a definition. The
schema-fixtures CI job runs it on modern Python alongside the fixture corpus and
the customer-facing wording gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e secrets, release.yml quoting

On a pull request super-linter lints every file changed against master, so PR
#184's "lint and format" job failed across nine linters that the per-push runs
never exercised. Everything was reproduced locally at the tool versions
super-linter v4 pins, with positive controls, and is now clean.

The one finding that mattered: the corpus sanitizer's self-test planted its
synthetic secrets as literals (an AWS key, a GitHub token, a Slack token, a
Google key, a JWT, a PEM header), and gitleaks flagged the token twice - any
scanner or GitHub push protection would. The fakes are now assembled at runtime
by concatenation, so no literal in the public repository matches a secret
pattern, and the self-test still exercises every detector (94/94).

pylint reported a "syntax error" in the same file because it parses with type
comments enabled and an example comment line read "type: password"; the comment
is reworded. Three pre-existing pylint false positives in old server code are
disabled narrowly, inline, with the reason stated.

Formatting: black and isort (profile black) over the 20 Python files in the
change, with .github/linters configs so CI and local runs agree; flake8 fixes by
hand where black does not reach (unused imports and locals, a raw-string regex,
ambiguous names, wrapped long lines; the sanitizer's typing imports are kept -
its PEP 484 type comments use them). Two jscpd clones factored into helpers.
Markdown: fence languages added, emphasis style and table-length settings
configured, terminology aligned with textlint. release.yml: shell expansions
quoted and the disabled ::set-output replaced by $GITHUB_OUTPUT.

Behaviour unchanged: 173 tests green three times; 528 sanitized blueprints still
412 fully clean, 0 crashes, 727 findings all KNOWN, 0 NEW.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant