{
  "name": "ScopeSpec 0.1 conformance rules",
  "description": "The presence, type and allowed-value rules ScopeSpec 0.1 states, extracted from the published JSON Schema so they cannot drift from the standard they describe.",
  "specVersion": "0.1",
  "schemaId": "https://www.bidbro.com/data/scopespec/scopespec-0.1.schema.json",
  "schemaDialect": "http://json-schema.org/draft-07/schema#",
  "generatedOn": "2026-09-23",
  "license": "CC BY 4.0",
  "checks": [
    "required field present",
    "JSON type",
    "allowed values (enum)"
  ],
  "doesNotCheck": [
    "format",
    "minLength",
    "minimum",
    "pattern",
    "whether the described project is the project the reader means",
    "whether a stated quantity, price or date is true"
  ],
  "documents": {
    "brief": {
      "ruleCount": 71,
      "requiredCount": 58,
      "rules": [
        {
          "id": "spec",
          "path": "spec",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "Says which version of the standard a reader is holding, so a brief written today is still interpretable when 0.2 exists."
        },
        {
          "id": "spec.specVersion",
          "path": "spec.specVersion",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "The ScopeSpec version this brief conforms to. `0.1` for this version."
        },
        {
          "id": "spec.briefId",
          "path": "spec.briefId",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "An identifier unique within whoever issued the brief. Opaque; carries no personal information."
        },
        {
          "id": "spec.created",
          "path": "spec.created",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "ISO 8601 date the brief was first issued."
        },
        {
          "id": "spec.revision",
          "path": "spec.revision",
          "required": true,
          "type": "integer",
          "allowedValues": null,
          "description": "Increments every time the brief changes after it has been sent to anyone. Bidders quote a revision, not a brief."
        },
        {
          "id": "spec.locale",
          "path": "spec.locale",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": "BCP 47 language tag for the prose fields."
        },
        {
          "id": "project",
          "path": "project",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "What is being asked for, in the words of the person asking, plus the timing constraints that change a price."
        },
        {
          "id": "project.tradeProfile",
          "path": "project.tradeProfile",
          "required": true,
          "type": "string",
          "allowedValues": [
            "roofing-replacement",
            "bathroom-remodel",
            "kitchen-remodel",
            "hvac-replacement",
            "exterior-painting",
            "other"
          ],
          "description": "The trade profile whose additional required fields apply. `other` is valid and imposes no extra fields."
        },
        {
          "id": "project.summary",
          "path": "project.summary",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "One to three sentences describing the work in plain language. Not a specification — the fields below are the specification."
        },
        {
          "id": "project.motivation",
          "path": "project.motivation",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": "Why the work is being done. Changes what a good contractor proposes: an active leak and a planned sale lead to different recommendations."
        },
        {
          "id": "project.desiredStart",
          "path": "project.desiredStart",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": "Earliest acceptable start date, if there is one."
        },
        {
          "id": "project.schedulingFlexibility",
          "path": "project.schedulingFlexibility",
          "required": true,
          "type": "string",
          "allowedValues": [
            "urgent",
            "firm-date",
            "flexible",
            "seasonal"
          ],
          "description": "How much the start date can move. A hard deadline is a price input and must not be discovered at contract stage."
        },
        {
          "id": "project.budgetStated",
          "path": "project.budgetStated",
          "required": true,
          "type": "boolean",
          "allowedValues": null,
          "description": "Whether the brief states a budget at all. Stating `false` is a legitimate and common answer."
        },
        {
          "id": "property",
          "path": "property",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "The physical facts that change the work, at a granularity that cannot identify the household."
        },
        {
          "id": "property.propertyType",
          "path": "property.propertyType",
          "required": true,
          "type": "string",
          "allowedValues": [
            "single-family-detached",
            "townhouse",
            "condominium",
            "duplex",
            "manufactured",
            "other"
          ],
          "description": "The kind of dwelling."
        },
        {
          "id": "property.postalCode",
          "path": "property.postalCode",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "Postal code only. Never a street address — see conformance rule C6."
        },
        {
          "id": "property.locality",
          "path": "property.locality",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "City or town name."
        },
        {
          "id": "property.region",
          "path": "property.region",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "State or province code."
        },
        {
          "id": "property.yearBuilt",
          "path": "property.yearBuilt",
          "required": false,
          "type": "integer",
          "allowedValues": null,
          "description": "Year of original construction, if known. Drives code, material and hazard assumptions."
        },
        {
          "id": "property.stories",
          "path": "property.stories",
          "required": false,
          "type": "integer",
          "allowedValues": null,
          "description": "Number of storeys above grade. An access and labour input on every exterior trade."
        },
        {
          "id": "property.occupiedDuringWork",
          "path": "property.occupiedDuringWork",
          "required": true,
          "type": "boolean",
          "allowedValues": null,
          "description": "Whether the household will be living in the property while the work runs. Changes sequencing, dust control and working hours."
        },
        {
          "id": "property.petsOnSite",
          "path": "property.petsOnSite",
          "required": false,
          "type": "boolean",
          "allowedValues": null,
          "description": "Whether animals will be present. A gate-and-containment question, not a courtesy field."
        },
        {
          "id": "scope",
          "path": "scope",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "The heart of the standard. Comparability fails here more often than anywhere else, and it fails silently."
        },
        {
          "id": "scope.inclusions",
          "path": "scope.inclusions",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Every item of work the homeowner intends to be part of the price. One item per entry, written as a deliverable rather than a task."
        },
        {
          "id": "scope.exclusions",
          "path": "scope.exclusions",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Work explicitly NOT in the price. Required even when empty, because an empty exclusions list is itself a statement — see conformance rule C3."
        },
        {
          "id": "scope.allowances",
          "path": "scope.allowances",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Budgets for selections not yet made. Required even when empty."
        },
        {
          "id": "scope.allowances[].item",
          "path": "scope.allowances[].item",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "What the allowance is for."
        },
        {
          "id": "scope.allowances[].amount",
          "path": "scope.allowances[].amount",
          "required": true,
          "type": "number",
          "allowedValues": null,
          "description": "The budgeted amount."
        },
        {
          "id": "scope.allowances[].currency",
          "path": "scope.allowances[].currency",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "ISO 4217 currency code."
        },
        {
          "id": "scope.allowances[].covers",
          "path": "scope.allowances[].covers",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "What the amount covers — material only, material and labour, and so on."
        },
        {
          "id": "scope.siteConditions",
          "path": "scope.siteConditions",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Known conditions a bidder would otherwise discover on site: soft decking, prior repairs, drainage, slope, known asbestos or lead, tight side yards."
        },
        {
          "id": "scope.accessConstraints",
          "path": "scope.accessConstraints",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Anything limiting how crews, materials or a dumpster reach the work. Required even when empty."
        },
        {
          "id": "scope.debrisHandling",
          "path": "scope.debrisHandling",
          "required": true,
          "type": "string",
          "allowedValues": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ],
          "description": "Who removes and disposes of debris. The most commonly assumed-both-ways line item in residential work."
        },
        {
          "id": "scope.cleanupStandard",
          "path": "scope.cleanupStandard",
          "required": false,
          "type": "string",
          "allowedValues": [
            "broom-clean",
            "vacuumed",
            "as-found",
            "unstated"
          ],
          "description": "The condition the site is returned in."
        },
        {
          "id": "measurements",
          "path": "measurements",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "Quantities with units and provenance. A quantity whose origin is unstated cannot be checked, and an unchecked quantity is the usual reason two prices differ by a third."
        },
        {
          "id": "measurements.quantities",
          "path": "measurements.quantities",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Each measurement the trade profile requires, plus any others that matter. Every entry carries name, value, unit, measurementMethod and confidence."
        },
        {
          "id": "measurements.quantities[].name",
          "path": "measurements.quantities[].name",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "What is being measured."
        },
        {
          "id": "measurements.quantities[].value",
          "path": "measurements.quantities[].value",
          "required": true,
          "type": "number",
          "allowedValues": null,
          "description": "The measured value."
        },
        {
          "id": "measurements.quantities[].unit",
          "path": "measurements.quantities[].unit",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "The unit, stated explicitly."
        },
        {
          "id": "measurements.quantities[].measurementMethod",
          "path": "measurements.quantities[].measurementMethod",
          "required": true,
          "type": "string",
          "allowedValues": [
            "tape-measured",
            "laser-measured",
            "aerial-report",
            "from-drawings",
            "estimated",
            "unknown"
          ],
          "description": null
        },
        {
          "id": "measurements.quantities[].confidence",
          "path": "measurements.quantities[].confidence",
          "required": true,
          "type": "string",
          "allowedValues": [
            "measured",
            "approximate",
            "rough"
          ],
          "description": null
        },
        {
          "id": "measurements.quantitiesVerifiedBy",
          "path": "measurements.quantitiesVerifiedBy",
          "required": true,
          "type": "string",
          "allowedValues": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ],
          "description": "Who is responsible for confirming the quantities before the price becomes binding."
        },
        {
          "id": "materials",
          "path": "materials",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "Whether the selections are made, who buys them, and what remains an allowance."
        },
        {
          "id": "materials.selectionsComplete",
          "path": "materials.selectionsComplete",
          "required": true,
          "type": "boolean",
          "allowedValues": null,
          "description": "Whether every material selection has been made. `false` is normal and must be paired with allowances."
        },
        {
          "id": "materials.specifiedItems",
          "path": "materials.specifiedItems",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Items already chosen, named specifically enough to price. Required even when empty."
        },
        {
          "id": "materials.specifiedItems[].item",
          "path": "materials.specifiedItems[].item",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "materials.specifiedItems[].specification",
          "path": "materials.specifiedItems[].specification",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "materials.specifiedItems[].suppliedBy",
          "path": "materials.specifiedItems[].suppliedBy",
          "required": true,
          "type": "string",
          "allowedValues": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ],
          "description": null
        },
        {
          "id": "materials.ownerSuppliedItems",
          "path": "materials.ownerSuppliedItems",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Anything the homeowner will supply. Required even when empty — an unstated owner-supplied item is a schedule risk nobody priced."
        },
        {
          "id": "permits",
          "path": "permits",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "Permit assumptions stated up front, because a permit assumption discovered late moves both price and schedule."
        },
        {
          "id": "permits.permitAssumedRequired",
          "path": "permits.permitAssumedRequired",
          "required": true,
          "type": "string",
          "allowedValues": [
            "yes",
            "no",
            "unknown"
          ],
          "description": "Whether the brief assumes a permit is required. `unknown` is an honest and common answer, and is treated as a question for the bidder rather than a defect."
        },
        {
          "id": "permits.permitPulledBy",
          "path": "permits.permitPulledBy",
          "required": true,
          "type": "string",
          "allowedValues": [
            "homeowner",
            "contractor",
            "shared",
            "undecided"
          ],
          "description": "Who applies for and pays for the permit under this brief's assumptions."
        },
        {
          "id": "permits.jurisdiction",
          "path": "permits.jurisdiction",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": "The permitting authority, where the homeowner knows it."
        },
        {
          "id": "permits.hoaApprovalRequired",
          "path": "permits.hoaApprovalRequired",
          "required": true,
          "type": "string",
          "allowedValues": [
            "yes",
            "no",
            "unknown"
          ],
          "description": "Whether an HOA or architectural review approval is needed."
        },
        {
          "id": "permits.inspectionsExpected",
          "path": "permits.inspectionsExpected",
          "required": false,
          "type": "array",
          "allowedValues": null,
          "description": "Inspections the homeowner expects, where known."
        },
        {
          "id": "documents",
          "path": "documents",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "What evidence accompanies the brief. A brief with no photographs is quoted more cautiously, and the caution is priced."
        },
        {
          "id": "documents.photos",
          "path": "documents.photos",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Photographs supplied. Each entry states what the photo shows; a URL is optional and may be omitted for a brief that travels outside the issuing platform."
        },
        {
          "id": "documents.photos[].documentType",
          "path": "documents.photos[].documentType",
          "required": true,
          "type": "string",
          "allowedValues": [
            "photo",
            "drawing",
            "report",
            "prior-estimate",
            "other"
          ],
          "description": null
        },
        {
          "id": "documents.photos[].describes",
          "path": "documents.photos[].describes",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "documents.photos[].url",
          "path": "documents.photos[].url",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "documents.drawings",
          "path": "documents.drawings",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Plans, sketches or measured drawings. Required even when empty."
        },
        {
          "id": "documents.drawings[].documentType",
          "path": "documents.drawings[].documentType",
          "required": true,
          "type": "string",
          "allowedValues": [
            "photo",
            "drawing",
            "report",
            "prior-estimate",
            "other"
          ],
          "description": null
        },
        {
          "id": "documents.drawings[].describes",
          "path": "documents.drawings[].describes",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "documents.drawings[].url",
          "path": "documents.drawings[].url",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "documents.reports",
          "path": "documents.reports",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Inspection reports, engineer's letters, prior estimates being replaced. Required even when empty."
        },
        {
          "id": "documents.reports[].documentType",
          "path": "documents.reports[].documentType",
          "required": true,
          "type": "string",
          "allowedValues": [
            "photo",
            "drawing",
            "report",
            "prior-estimate",
            "other"
          ],
          "description": null
        },
        {
          "id": "documents.reports[].describes",
          "path": "documents.reports[].describes",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "documents.reports[].url",
          "path": "documents.reports[].url",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": null
        },
        {
          "id": "assumptions",
          "path": "assumptions",
          "required": true,
          "type": "object",
          "allowedValues": null,
          "description": "The list every bidder must accept or restate. This is the mechanism that converts a difference in reading into a visible difference on the page."
        },
        {
          "id": "assumptions.assumptions",
          "path": "assumptions.assumptions",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "One assumption per entry, written so it can be accepted or contradicted without ambiguity."
        },
        {
          "id": "x-extensions",
          "path": "x-extensions",
          "required": false,
          "type": "object",
          "allowedValues": null,
          "description": "Implementation-specific additions. Nothing here is part of the standard, and a consumer may ignore all of it without becoming non-conforming."
        }
      ]
    },
    "quote": {
      "ruleCount": 15,
      "requiredCount": 13,
      "rules": [
        {
          "id": "quote.priceBasis",
          "path": "priceBasis",
          "required": true,
          "type": "string",
          "allowedValues": [
            "fixed-price",
            "cost-plus",
            "time-and-materials",
            "not-to-exceed",
            "allowance-based"
          ],
          "description": "How the price is constructed. Two prices on different bases are not comparable and must not be presented as if they were."
        },
        {
          "id": "quote.priceTotal",
          "path": "priceTotal",
          "required": true,
          "type": "number",
          "allowedValues": null,
          "description": "The total for the scope as quoted, in the stated currency, exclusive of allowances listed separately."
        },
        {
          "id": "quote.currency",
          "path": "currency",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "ISO 4217 currency code."
        },
        {
          "id": "quote.allowancesCarried",
          "path": "allowancesCarried",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "The brief's allowances as carried in this quote, so identical allowances sit on identical base prices."
        },
        {
          "id": "quote.assumptionsAccepted",
          "path": "assumptionsAccepted",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "The brief's assumptions this quote accepts, verbatim."
        },
        {
          "id": "quote.assumptionsRestated",
          "path": "assumptionsRestated",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Assumptions the bidder replaced, with the replacement text. Required even when empty — this is where two apparently identical prices stop being identical."
        },
        {
          "id": "quote.exclusionsAdded",
          "path": "exclusionsAdded",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Anything the bidder excludes beyond the brief's own exclusions. Required even when empty."
        },
        {
          "id": "quote.validUntil",
          "path": "validUntil",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "The date the price expires. A quote without an expiry cannot be compared against one with a short expiry on equal terms."
        },
        {
          "id": "quote.startWindow",
          "path": "startWindow",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "When work could begin, as a stated window rather than a promise of a date."
        },
        {
          "id": "quote.durationEstimate",
          "path": "durationEstimate",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "Expected working duration, with the unit stated."
        },
        {
          "id": "quote.paymentSchedule",
          "path": "paymentSchedule",
          "required": true,
          "type": "array",
          "allowedValues": null,
          "description": "Each payment as a stated trigger and amount or percentage."
        },
        {
          "id": "quote.warranty",
          "path": "warranty",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "Workmanship warranty in plain words, stated separately from any manufacturer warranty."
        },
        {
          "id": "quote.changeOrderPolicy",
          "path": "changeOrderPolicy",
          "required": true,
          "type": "string",
          "allowedValues": null,
          "description": "How a change is priced and authorised. Unstated at quote stage, it is negotiated at the worst possible moment."
        },
        {
          "id": "quote.licenseNumber",
          "path": "licenseNumber",
          "required": false,
          "type": "string",
          "allowedValues": null,
          "description": "The contractor's licence number as issued by the relevant authority. Optional in the standard because licensing regimes differ; verification is the reader's responsibility, not the standard's."
        },
        {
          "id": "quote.insuranceOnFile",
          "path": "insuranceOnFile",
          "required": false,
          "type": "boolean",
          "allowedValues": null,
          "description": "Whether the bidder asserts current liability and workers' compensation cover. An assertion, not a verification — the standard never implies BidBro or anyone else has checked it."
        }
      ]
    }
  }
}
