specular - deterministic spec-driven development

Verify a repository against a machine-readable JSON spec. specular reads the spec,
checks repository state, and reports one evidence object per item.

USAGE
  specular lint   <spec.json>   lint the spec file for correctness
  specular verify <spec.json>   check the repository against the spec
  specular --help               print this text (also: -h, help)
  specular --version            print the package version (also: -v)

EXIT CODES
  0  spec valid (lint) / repository conforms (verify)
  1  repository does not conform (verify only)
  2  spec invalid, verifier error, protocol error, timeout, or runtime error
  Other flags are not supported. lint and verify output is always JSON.

SPEC SHAPE
  Strict JSON, no comments. Version 2 uses block-level verifier commands.
  top-level verifiers are not supported.

  A verifier is one command encoded as argv:

    "verifier": ["builtin:tree"]
    "verifier": ["scripts/verify_deps.py"]
    "verifier": ["python3", "scripts/verify_deps.py", "--workspace"]

  The first argv token selects dispatch:

    builtin:<name>  run an internal Specular verifier
    anything else   run as an external command without a shell

  Initial built-ins:

    builtin:tree     tree only
    builtin:content  content only

THREE WAYS TO USE A SPEC
  1. Predefined category with a built-in verifier.

     Use tree or content and choose the matching builtin explicitly.

       "requirements": {
         "tree": {
           "verifier": ["builtin:tree"],
           "required": ["src/lib.rs"],
           "forbidden": ["tests/**"]
         },
         "content": [
           {
             "verifier": ["builtin:content"],
             "files": ["**/*.rs"],
             "forbidden": ["#[test]"]
           }
         ]
       }

  2. Predefined category with an external verifier.

     Use dependencies, exports, or enumerations when the fixed fields fit.
     Choose one script command for each block.

       "requirements": {
         "dependencies": [
           {
             "verifier": ["scripts/verify_deps.py"],
             "manifests": ["Cargo.toml"],
             "required": ["serde"]
           }
         ]
       }

  3. Opaque custom checks.

     Use custom when no predefined category fits. Each entry can contain any
     JSON object fields except that verifier is reserved by Specular.

       "requirements": {
         "custom": [
           {
             "verifier": ["scripts/verify_custom.py"],
             "check": "version-sync",
             "files": ["README.md", "Cargo.toml"]
           }
         ]
       }

SPEC FORMAT
  This example uses every category; copy and delete what you do not need.
  Any category may be omitted. List fields default to [].

  {
    "version": 2,
    "requirements": {
      "tree": {
        "verifier": ["builtin:tree"],
        "reason": "plan: repository layout",
        "required": ["src/lib.rs", "src/main.rs"],
        "forbidden": ["target/**"]
      },
      "content": [
        {
          "verifier": ["builtin:content"],
          "files": ["**/*.rs"],
          "required": ["// SPDX"],
          "forbidden": ["#[test]"],
          "reason": "plan: licensing, test policy"
        },
        {
          "verifier": ["builtin:content"],
          "files": ["docs/*.txt"],
          "exists": ["INVOICE"]
        }
      ],
      "dependencies": [
        {
          "verifier": ["scripts/verify_deps.py"],
          "manifests": ["Cargo.toml"],
          "required": ["serde"],
          "forbidden": ["openssl", "guardrail*", "g3*"]
        }
      ],
      "exports": [
        {
          "verifier": ["scripts/verify_exports.py"],
          "package": "specular",
          "required": ["Spec", "lint", "verify"]
        }
      ],
      "enumerations": [
        {
          "verifier": ["scripts/verify_enums.py"],
          "name": "Status",
          "values": ["Pass", "Fail"]
        }
      ],
      "custom": [
        {
          "verifier": ["scripts/verify_custom.py"],
          "check": "version-sync",
          "files": ["README.md", "Cargo.toml"],
          "reason": "plan: release process"
        }
      ]
    }
  }

FIELDS
  tree          verifier, reason, required, exists, forbidden
  content       verifier, files, reason, required, exists, forbidden
  dependencies  verifier, manifests, reason, required, exists, forbidden
  exports       verifier, package, reason, required, exists, forbidden
  enumerations  verifier, name, reason, values
  custom        verifier plus script-owned object fields

SEMANTICS
  required   each item is present in every matched place
  exists     each item is present in at least one matched place
  forbidden  each item is present in no matched place

  required and exists items are exact strings.
  forbidden items may be globs in tree and dependencies.
  content items are fixed substrings for all quantifiers.
  enumerations are exact sets: the named set's values equal values.
  Zero matched places plus non-empty required or exists fails.
  exists is rejected by lint on tree and exports because each has one place.

VERIFIER PROTOCOL
  Every non-empty block is judged by exactly one verifier command.

  External typed verifier blocks run once per block:

    <command...> <spec.json> <category> <blockIndex>

  The executable reads requirements.<category>[blockIndex] from the spec and
  prints one JSON object per item in that block. Required keys: item, status.

    {"item":"serde","status":"pass"}
    {"item":"guardrail*","status":"fail","message":"guardrail3 declared","path":"Cargo.toml"}

  External custom verifier entries run once per entry:

    <command...> <spec.json> custom <blockIndex>

  The executable reads requirements.custom[blockIndex] and prints exactly one
  evidence object. Required key: status. All other keys are copied into the report.

    {"check":"version-sync","status":"pass"}
    {"check":"version-sync","status":"fail","message":"README and Cargo.toml differ"}

  Script exit code signals health only. Nonzero exit, invalid JSON, missing item,
  duplicate item, unknown item, wrong custom line count, or a 60 second timeout
  is a runtime error with exit 2. Pass/fail verdicts travel only in evidence.

COMPLETE TYPED VERIFIER
  This Python example accepts the protocol and returns pass evidence for every
  item in one typed block.

  #!/usr/bin/env python3
  import json
  import sys
  from pathlib import Path

  spec_path = Path(sys.argv[1])
  category = sys.argv[2]
  block_index = int(sys.argv[3])

  spec = json.loads(spec_path.read_text())
  block = spec["requirements"][category][block_index]

  for item in block.get("required", []):
      print(json.dumps({"item": item, "status": "pass"}))
  for item in block.get("exists", []):
      print(json.dumps({"item": item, "status": "pass"}))
  for item in block.get("forbidden", []):
      print(json.dumps({"item": item, "status": "pass"}))

COMPLETE CUSTOM VERIFIER
  This Python example reads one opaque custom entry and returns one evidence line.

  #!/usr/bin/env python3
  import json
  import sys
  from pathlib import Path

  spec_path = Path(sys.argv[1])
  block_index = int(sys.argv[3])
  spec = json.loads(spec_path.read_text())
  entry = spec["requirements"]["custom"][block_index]

  print(json.dumps({
      "check": entry.get("check", "custom"),
      "status": "pass"
  }))

LINT ERRORS
  JSON_SCHEMA                wrong shape, unknown field, wrong type, or bad version
  VACUOUS_SPEC               no required, exists, or values item anywhere
  VERIFIER_MISSING           block has requirements but no verifier field
  VERIFIER_COMMAND_EMPTY     verifier argv is empty or contains an empty string
  UNKNOWN_BUILTIN_VERIFIER   builtin verifier name is unknown
  BUILTIN_CATEGORY_MISMATCH  builtin verifier cannot check that category
  PATH_RULE / GLOB           bad path, or a glob that will not compile
  DUPLICATE_TARGET           two blocks in one category have the same target
  DUPLICATE_ITEM             same item appears twice in one block
  CONTRADICTION              same item is required and forbidden in one block
  REDUNDANT                  same item is both required and exists in one block
  ITEM_FORMAT                required/exists item is empty, untrimmed, or a glob
  EXISTS_SINGLE_PLACE        exists used on tree or exports

WORKFLOW
  1. Write the plan in prose.
  2. Extract a JSON spec for checkable items.
  3. Choose one verifier for every non-empty block.
  4. Write script verifiers where no builtin fits.
  5. Run specular verify before coding and confirm it fails where expected.
  6. Build until specular verify exits 0.
  7. Spot-check by hand; the spec is not exhaustive.

REPORT
  lint:

    {"result":"pass"}

    {"result":"fail","violations":[{"code":"VACUOUS_SPEC","message":"..."}]}

  verify:

    {
      "specular_version": "0.3.0",
      "spec": {"path": "spec.json", "sha256": "..."},
      "verifier_files": [{"path": "scripts/verify_deps.py", "sha256": "..."}],
      "git": [{"path": "spec.json", "state": "clean"}],
      "evidence": [
        {
          "category": "tree",
          "polarity": "required",
          "item": "src/main.rs",
          "source": "builtin",
          "verifier": "builtin:tree",
          "status": "fail",
          "message": "missing required path: src/main.rs"
        },
        {
          "category": "dependencies",
          "target": ["Cargo.toml"],
          "item": "guardrail*",
          "source": "script",
          "verifier": "scripts/verify_deps.py",
          "status": "pass"
        },
        {
          "category": "custom",
          "check": "version-sync",
          "source": "script",
          "verifier": "scripts/verify_custom.py",
          "status": "fail",
          "message": "README and Cargo.toml differ"
        }
      ],
      "conforms": false
    }
