Skip to content

S008 Interface Requirements

JSON Output Schema

S008-IF-001 [P1] When --format=json is used, the output MUST conform to this schema:

json
{
  "passed": boolean,
  "global_coverage": number | null,
  "min_coverage_global": number | null,
  "structures": [
    {
      "name": string,
      "source_path": string,
      "test_path": string,
      "files": [
        {
          "source": string,
          "test": string,
          "passed": boolean,
          "rules": {
            "<rule-name>": {
              "passed": boolean,
              "value": string | null,
              "error": string | null
            }
          }
        }
      ]
    }
  ]
}

S008-IF-001.a The root object MUST contain exactly four keys: passed, global_coverage, min_coverage_global, structures. No additional keys are permitted at the root level.

S008-IF-001.b passed MUST be a JSON boolean. It is the logical conjunction of: (1) all file-level passed values across all structures, and (2) global coverage meeting threshold (when min_coverage_global is non-null).

S008-IF-001.c global_coverage MUST be a JSON number (float) representing the project-wide coverage percentage, or JSON null if global coverage data is not available.

S008-IF-001.d min_coverage_global MUST be a JSON number (float) representing the configured threshold, or JSON null if the threshold is not configured.

S008-IF-001.e structures MUST be a JSON array. It MUST contain one entry per structure block in parity.yaml, in the same order as they appear in config.

S008-IF-001.f Each structure entry MUST contain exactly four keys: name (string), source_path (string), test_path (string), files (array).

S008-IF-001.g name MUST match the name field from the structure's config entry (e.g., "Unit Services").

S008-IF-001.h source_path MUST be the resolved source path string (e.g., "app/Services").

S008-IF-001.i test_path MUST be the resolved test path string (e.g., "tests/Unit/Services").

S008-IF-001.j files MUST be a JSON array containing one entry per source file discovered in that structure's source directory.

S008-IF-001.k Each file entry MUST contain exactly four keys: source (string), test (string), passed (boolean), rules (object).

S008-IF-001.l source MUST be the full relative path from the project root (e.g., "app/Services/Auth/LoginService.php"), not a basename or structure-relative path.

S008-IF-001.m test MUST be the expected test file path relative to the project root (e.g., "tests/Unit/Services/Auth/LoginServiceTest.php"), regardless of whether the test file exists on disk.

S008-IF-001.n passed at the file level MUST be true only when every enforced rule passes for that file; false when any enforced rule fails.

S008-IF-001.o rules MUST be a JSON object keyed by the string returned by RuleInterface::name() for each rule evaluated against that file (e.g., "test-exists", "minimum-coverage", "coverage-attribution").

S008-IF-001.p Each rule value MUST contain exactly three keys: passed (boolean), value (string or null), error (string or null).

S008-IF-001.q passed in a rule entry MUST reflect RuleResult::$passed.

S008-IF-001.r value MUST reflect RuleResult::$value. Typical values include: "Y" / "N" for test-exists, "85%" for minimum-coverage, "3|1" for coverage-attribution.

S008-IF-001.s error MUST reflect RuleResult::$error. Non-null only when the rule fails. Example: "Coverage 45% is below minimum 80%".

S008-IF-001.t The JSON MUST be encoded with JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES.

S008-IF-001.u The JSON document MUST be the only content written to stdout. No ANSI codes, no warnings, no titles, no blank lines before/after.

Table Column Contract

S008-IF-002 [P1] The table header row MUST follow this structure:

| Source | Test | {rule1_header} | {rule2_header} | ... | OK |

S008-IF-002.a Source and Test are always the first two columns. S008-IF-002.b Rule columns appear in the order rules are resolved for the structure. S008-IF-002.c Only rules with non-null columnHeader() produce columns. S008-IF-002.d CoverageAttributionRule produces two adjacent columns: # and Other. S008-IF-002.e OK is always the last column.

S008-IF-002.f File row cells MUST follow this mapping:

ColumnCell Content
SourceBasename of source file (indented with 2 spaces if in a subdirectory)
TestBasename of expected test file (indented with 2 spaces if in a subdirectory)
Rule columnsOutput of RuleInterface::formatCell(RuleResult) (ANSI-tagged)
CoverageAttribution "Other"Output of CoverageAttributionRule::formatOtherCell(RuleResult)
OK<fg=green>Y</> if all enforced rules pass; <fg=red>N</> otherwise

S008-IF-002.g Directory separator rows MUST follow this mapping:

ColumnCell Content
Source<fg=gray>{dir}/</>
Test<fg=gray>{dir}/</>
Rule columns<fg=gray>-</> each
OK<fg=gray>-</>

Exit Code Contract

S008-IF-003 [P1] The check command MUST use exactly two exit codes for the output-relevant path:

CodeConstantCondition
0Command::SUCCESS$hasFailure === false
1Command::FAILURE$hasFailure === true

S008-IF-003.a $hasFailure is initialized to true if global_coverage < min_coverage_global (when both are non-null). S008-IF-003.b $hasFailure is set to true if any file has $allPassed === false (i.e., any enforced rule failed). S008-IF-003.c $hasFailure is never set to true by informational rule results. S008-IF-003.d The same $hasFailure boolean drives both the return statement and the JSON passed field (inverted: passed = !$hasFailure).

Coverage Summary Table Schema

S008-IF-004 [P1] The coverage summary table in table mode MUST have this structure:

| Coverage                         | Value   | Required | Status |
|----------------------------------|---------|----------|--------|
| Global                           | {n}%    | {m}%     | OK/FAIL|
| Per-file avg (all tests)         | {n}%    | --       | --     |
| Per-file min (all tests)         | {n}%    | Per rule | OK/FAIL|
| Per-file min (matching test only)| {n}%    | Per rule | OK/FAIL|  <-- only if a matched-coverage rule is evaluated

S008-IF-004.a Values MUST be formatted with sprintf('%.2f%%', $value) for percentages. S008-IF-004.b The global Required value MUST be formatted with sprintf('%.0f%%', $value). Rule-specific aggregate rows MUST display the literal Per rule. S008-IF-004.c Missing or non-applicable values MUST display -- (em dash character). S008-IF-004.d Status cells MUST display <fg=green>OK</> when the value meets the requirement, <fg=red>FAIL</> when below, or <fg=gray>--</> when not applicable. S008-IF-004.e Per-file status cells MUST aggregate the corresponding enforced RuleResult::$passed values rather than compare every file to a single top-level default.

Rule-to-Column Mapping Contract

S008-IF-005 [P1] Each built-in rule produces the following column behavior:

Rule NamecolumnHeader()isEnforced()Column Behavior
test-exists"∃"trueGreen Y / Red N
enforce-coverage-linkvariestrueGreen pass label / Red fail label
minimum-coverage"Cov"trueGreen {n}% / Red {n}%
matched-coveragevariestrueGreen {n}% / Red {n}%
coverage-attribution"#"falseCount (main) + Yellow/Gray count (Other)

S008-IF-005.a Plugin rules that implement RuleInterface MUST integrate identically: if columnHeader() is non-null, they get a column; their formatCell() controls rendering; isEnforced() determines OK column participation. S008-IF-005.b In JSON output, all evaluated rules appear in the rules object regardless of their columnHeader() return value.