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 3 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
    builtin:cargo-dependencies  dependencies only, Cargo.toml files

PRIORITY RULE
  Use the least custom spec that can express the plan.

  1. Use a predefined category when one fits.
  2. Use that category's builtin verifier when one exists.
  3. Use an external verifier only for a predefined category with no matching
     builtin.
  4. Use custom only when no predefined category fits.

  Do not put file-tree checks, content checks, or Cargo package checks in
  custom. Do not write a script for tree, content, or Cargo dependency checks
  that the built-ins can judge.

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

     This is the default. Use tree, content, or Cargo dependencies 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]"]
           }
         ],
         "dependencies": [
           {
             "verifier": ["builtin:cargo-dependencies"],
             "files": ["Cargo.toml"],
             "required": ["serde"],
             "forbidden": ["openssl"],
             "forbiddenGlobs": ["g3*"]
           }
         ]
       }

  2. Predefined category with an external verifier.

     Use this only when the category fields fit but no builtin can judge the
     ecosystem or data. Choose one script command for each block.

       "requirements": {
         "dependencies": [
           {
             "verifier": ["scripts/verify_deps.py"],
             "files": ["package.json"],
             "required": ["serde"]
           }
         ]
       }

  3. Opaque custom checks.

     Use this only 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": 3,
    "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": ["builtin:cargo-dependencies"],
          "files": ["Cargo.toml"],
          "required": ["serde"],
          "forbidden": ["openssl"],
          "forbiddenGlobs": ["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, files, reason, required, exists, forbidden, forbiddenGlobs
  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 are exact strings in content and dependencies; tree
  forbidden items are path globs.
  dependencies forbiddenGlobs items are Cargo package-name globs.
  content items are fixed substrings for all quantifiers.
  builtin:cargo-dependencies matches Cargo package identity, so renamed
  dependencies are checked by the `package` value, not the local key.
  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"}))
  for item in block.get("forbiddenGlobs", []):
      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                item is empty, untrimmed, or has the wrong glob form
  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. Put each item in the narrowest predefined category that fits.
  4. Choose the builtin verifier whenever that category has one.
  5. Write script verifiers only where no builtin fits.
  6. Use custom only where no predefined category fits.
  7. Run specular verify before coding and confirm it fails where expected.
  8. Build until specular verify exits 0.
  9. Spot-check by hand; the spec is not exhaustive.

REPORT
  lint:

    {"result":"pass"}

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

  verify:

    {
      "specular_version": "0.4.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": "builtin",
          "verifier": "builtin:cargo-dependencies",
          "status": "pass"
        },
        {
          "category": "custom",
          "check": "version-sync",
          "source": "script",
          "verifier": "scripts/verify_custom.py",
          "status": "fail",
          "message": "README and Cargo.toml differ"
        }
      ],
      "conforms": false
    }
